Index: gypsy/gypsy-control.h =================================================================== --- gypsy/gypsy-control.h (revision 134) +++ gypsy/gypsy-control.h (working copy) @@ -52,6 +52,8 @@ typedef struct _GypsyControlClass { GObjectClass parent_class; + void (*control_created) (GypsyControl *control, + char *path); } GypsyControlClass; GType gypsy_control_get_type (void); @@ -61,6 +63,11 @@ const char *device_name, GError **error); +gboolean gypsy_control_create_async (GypsyControl *control, + GCallback callback, + gpointer user_data, + GError **error); + G_END_DECLS #endif Index: gypsy/gypsy-marshal.list =================================================================== --- gypsy/gypsy-marshal.list (revision 134) +++ gypsy/gypsy-marshal.list (working copy) @@ -1,2 +1,4 @@ VOID:INT,INT,DOUBLE,DOUBLE,DOUBLE VOID:INT,DOUBLE,DOUBLE,DOUBLE +VOID:STRING,UINT,INT +VOID:STRING,STRING Index: gypsy/gypsy-control.c =================================================================== --- gypsy/gypsy-control.c (revision 134) +++ gypsy/gypsy-control.c (working copy) @@ -77,6 +77,7 @@ * */ +#include #include #include @@ -84,15 +85,42 @@ #include "gypsy-server-bindings.h" + +#define BLUEZ_SERVICE_CLASS_POSITIONING 65536 + +#define BLUEZ_DBUS_SERVICE "org.bluez" +#define BLUEZ_DBUS_PATH "/org/bluez" +#define BLUEZ_DBUS_MANAGER_INTERFACE "org.bluez.Manager" +#define BLUEZ_DBUS_ADAPTER_INTERFACE "org.bluez.Adapter" + +#define BLUEZ_MANAGER_DEFAULT_ADAPTER "DefaultAdapter" + +#define BLUEZ_ADAPTER_DISCOVER_WO_RESOLVE "DiscoverDevicesWithoutNameResolving" +#define BLUEZ_ADAPTER_HAS_BONDING "HasBonding" +#define BLUEZ_ADAPTER_LIST_CONNECTIONS "ListConnections" +#define BLUEZ_ADAPTER_GET_REMOTE_CLASS "GetRemoteClass" +#define BLUEZ_ADAPTER_DISCOVERY_COMPLETED "DiscoveryCompleted" +#define BLUEZ_ADAPTER_REMOTE_DEVICE_FOUND "RemoteDeviceFound" + typedef struct _GypsyControlPrivate { DBusGProxy *proxy; char *device_name; + + GSList *discovered_devices; + gint control_created_id; /*signal handler id, see create_async*/ } GypsyControlPrivate; +enum { + CONTROL_CREATED, + LAST_SIGNAL +}; + #define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GYPSY_TYPE_CONTROL, GypsyControlPrivate)) G_DEFINE_TYPE (GypsyControl, gypsy_control, G_TYPE_OBJECT); +static guint32 signals[LAST_SIGNAL] = {0, }; + GQuark gypsy_control_error_quark (void) { @@ -152,6 +180,16 @@ o_class->dispose = dispose; g_type_class_add_private (klass, sizeof (GypsyControlPrivate)); + + signals[CONTROL_CREATED] = g_signal_new ("control-created", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); } static void @@ -176,23 +214,256 @@ GYPSY_CONTROL_DBUS_SERVICE, GYPSY_CONTROL_DBUS_PATH, GYPSY_CONTROL_DBUS_INTERFACE); + + /* marshaller for AccuracyChanged */ + dbus_g_object_register_marshaller (gypsy_marshal_VOID__INT_DOUBLE_DOUBLE_DOUBLE, + G_TYPE_NONE, + G_TYPE_INT, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_INVALID); + /* marshaller for PositionChanged & CourseChanged */ dbus_g_object_register_marshaller (gypsy_marshal_VOID__INT_INT_DOUBLE_DOUBLE_DOUBLE, - G_TYPE_NONE, - G_TYPE_INT, - G_TYPE_INT, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_INVALID); - dbus_g_object_register_marshaller (gypsy_marshal_VOID__INT_DOUBLE_DOUBLE_DOUBLE, - G_TYPE_NONE, - G_TYPE_INT, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_INVALID); + G_TYPE_NONE, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_INVALID); + + priv->discovered_devices = NULL; } +/* callback for DiscoveryCompleted bluez signal */ +static void +gypsy_control_discovery_completed (DBusGProxy *adapter, + gpointer user_data) +{ + GypsyControl *control = (GypsyControl*)user_data; + GypsyControlPrivate *priv = GET_PRIVATE (control); + + char *path = NULL; + + if (priv->discovered_devices) { + GSList *tmp_list; + GError *error = NULL; + + /*pick the first available device */ + priv->device_name = priv->discovered_devices->data; + + /*free the other strings*/ + tmp_list = priv->discovered_devices->next; + while (tmp_list) { + g_free (tmp_list->data); + tmp_list = tmp_list->next; + } + g_slist_free (priv->discovered_devices); + priv->discovered_devices = NULL; + + g_debug ("Using discovered device: %s", priv->device_name); + org_freedesktop_Gypsy_Server_create (priv->proxy, + priv->device_name, + &path, + &error); + if (error) { + g_printerr ("Creating Gypsy server failed: %s\n", error->message); + g_free (path); + g_error_free (error); + } + } + g_signal_emit (control, + signals[CONTROL_CREATED], + 0, + path); + + g_signal_handler_disconnect (control, priv->control_created_id); + g_object_unref (adapter); +} + +/* callback for DeviceFound bluez signal */ +static void +gypsy_control_device_found (DBusGProxy *adapter, + const char *address, + const unsigned int dev_class, + const int rssi, + gpointer user_data) +{ + gboolean paired; + GError *error = NULL; + GypsyControlPrivate *priv; + + priv = GET_PRIVATE (user_data); + + if (!(dev_class & BLUEZ_SERVICE_CLASS_POSITIONING)) { + return; + } + + /* return if we have the address in list already */ + if (g_slist_find_custom (priv->discovered_devices, + address, + (GCompareFunc)g_ascii_strcasecmp) != NULL) { + return; + } + + /* address is a new positioning device. If it's paired, add to list */ + dbus_g_proxy_call (adapter, BLUEZ_ADAPTER_HAS_BONDING, &error, + G_TYPE_STRING, address, G_TYPE_INVALID, + G_TYPE_BOOLEAN, &paired, G_TYPE_INVALID); + if (error){ + g_warning ("Bluez D-Bus call 'HasBonding' failed: %s", + error->message); + g_error_free (error); + + } else if (paired) { + priv->discovered_devices = g_slist_append (priv->discovered_devices, + g_strdup (address)); + } +} + +static DBusGProxy* +gypsy_control_get_bt_adapter (GError **error) +{ + DBusGConnection *connection = NULL; + DBusGProxy *manager = NULL; + DBusGProxy *adapter = NULL; + char *adapter_name = NULL; + + connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, error); + if (!connection) { + return NULL; + } + + /* Get default bluez adapter */ + manager = dbus_g_proxy_new_for_name (connection, + BLUEZ_DBUS_SERVICE, + BLUEZ_DBUS_PATH, + BLUEZ_DBUS_MANAGER_INTERFACE); + dbus_g_proxy_call (manager, BLUEZ_MANAGER_DEFAULT_ADAPTER, error, + G_TYPE_INVALID, + G_TYPE_STRING, &adapter_name, G_TYPE_INVALID); + g_object_unref (manager); + if (*error) { + dbus_g_connection_unref (connection); + return NULL; + } + + adapter = dbus_g_proxy_new_for_name (connection, + BLUEZ_DBUS_SERVICE, + adapter_name, + BLUEZ_DBUS_ADAPTER_INTERFACE); + g_free (adapter_name); + dbus_g_connection_unref (connection); + return adapter; +} + +static char* +gypsy_control_get_connected_gps_address (DBusGProxy *adapter) +{ + char **connected_devices = NULL; + char **device_ptr = NULL; + char *device = NULL; + GError *error = NULL; + + dbus_g_proxy_call (adapter, BLUEZ_ADAPTER_LIST_CONNECTIONS, &error, + G_TYPE_INVALID, + G_TYPE_STRV, &connected_devices, G_TYPE_INVALID); + if (error) { + g_warning ("Failed to list connections: %s", error->message); + g_error_free (error); + return NULL; + } + for (device_ptr = connected_devices; *device_ptr; device_ptr++) { + guint dev_class; + dbus_g_proxy_call (adapter, BLUEZ_ADAPTER_GET_REMOTE_CLASS, &error, + G_TYPE_STRING, *device_ptr, G_TYPE_INVALID, + G_TYPE_UINT, &dev_class, G_TYPE_INVALID); + if (error) { + g_warning ("Failed to get remote class of connected device: %s", error->message); + g_error_free (error); + } else if (dev_class & BLUEZ_SERVICE_CLASS_POSITIONING) { + device = g_strdup (*device_ptr); + break; + } + + } + g_strfreev (connected_devices); + + return device; +} + +static gboolean +gypsy_control_start_device_discovery (GypsyControl *control, GError **error) +{ + GypsyControlPrivate *priv; + DBusGProxy *adapter = NULL; + + priv = GET_PRIVATE (control); + + adapter = gypsy_control_get_bt_adapter (error); + if (!adapter) { + return FALSE; + } + + /* Check if a connected device is usable */ + priv->device_name = gypsy_control_get_connected_gps_address (adapter); + if (priv->device_name) { + char *path = NULL; + + /* device found: create server, emit control-created */ + g_debug ("Found connected device: %s", priv->device_name); + org_freedesktop_Gypsy_Server_create (priv->proxy, + priv->device_name, + &path, + error); + if (*error) { + g_object_unref (adapter); + g_free (path); + return FALSE; + } + g_signal_emit (control, + signals[CONTROL_CREATED], + 0, + path); + } else { + /* If a connected device was no good, need to start device discovery */ + /* Connect to signals DiscoveryCompleted & RemoteDeviceFound */ + dbus_g_proxy_add_signal (adapter, + BLUEZ_ADAPTER_DISCOVERY_COMPLETED, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (adapter, + BLUEZ_ADAPTER_DISCOVERY_COMPLETED, + G_CALLBACK(gypsy_control_discovery_completed), + control, + NULL); + + dbus_g_object_register_marshaller (gypsy_marshal_VOID__STRING_UINT_INT, + G_TYPE_NONE, + G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID); + dbus_g_proxy_add_signal (adapter, + BLUEZ_ADAPTER_REMOTE_DEVICE_FOUND, + G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (adapter, + BLUEZ_ADAPTER_REMOTE_DEVICE_FOUND, + G_CALLBACK (gypsy_control_device_found), + control, + NULL); + + /* start device discovery */ + /* FIXME: I think this will fail if another device discovery is in progress */ + dbus_g_proxy_call (adapter, BLUEZ_ADAPTER_DISCOVER_WO_RESOLVE, error, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (*error) { + g_object_unref (adapter); + return FALSE; + } + } + return TRUE; +} + + /** * gypsy_control_get_default: * @@ -227,26 +498,67 @@ * shutdown after which any calls to the object at the returned path * are not guaranteed to work. * - * Return value: The path to the created object. + * Return value: The path to the created object or NULL on failure. */ char * gypsy_control_create (GypsyControl *control, - const char *device_name, - GError **error) + const char *device_name, + GError **error) { GypsyControlPrivate *priv; - char *path; + char *path = NULL; g_return_val_if_fail (GYPSY_IS_CONTROL (control), NULL); g_return_val_if_fail (device_name != NULL, NULL); priv = GET_PRIVATE (control); - - if (!org_freedesktop_Gypsy_Server_create (priv->proxy, device_name, - &path, error)) { - return NULL; + + if (org_freedesktop_Gypsy_Server_create (priv->proxy, device_name, + &path, error)) { + priv->device_name = g_strdup (device_name); } - - priv->device_name = g_strdup (device_name); return path; } + +/** + * gypsy_control_create_async: + * @control: The GypsyControl device + * @callback: Function that should be called when control is created + * @user_data: User data pointer that will be made available in callback + * @error: A GError to return errors in + * + * Starts asynchronous Gypsy control creation. Any paired bluetooth + * positioning device that is either connected or discoverable may be + * used as the device. The device path (or NULL on failure) will be + * provided using the callback once everything is complete. + * + * Return value: TRUE if control creation was started and callback was + * registered. + * . + */ +/* FIXME: the callback should return an error code too. Now it's + * impossible for the caller to tell if there was an error + * or if there just weren't any devices */ +gboolean +gypsy_control_create_async (GypsyControl *control, + GCallback callback, + gpointer user_data, + GError **error) +{ + GypsyControlPrivate *priv; + + g_return_val_if_fail (GYPSY_IS_CONTROL (control), FALSE); + g_return_val_if_fail (callback != NULL, FALSE); + + priv = GET_PRIVATE (control); + + priv->control_created_id = g_signal_connect (control, + "control-created", + callback, + user_data); + if (!gypsy_control_start_device_discovery (control, error)) { + g_signal_handler_disconnect (control, priv->control_created_id); + return FALSE; + } + return TRUE; +} Index: examples/simple-gps-gypsy-async.c =================================================================== --- examples/simple-gps-gypsy-async.c (revision 0) +++ examples/simple-gps-gypsy-async.c (revision 0) @@ -0,0 +1,111 @@ +/* + * Gypsy + * + * A simple to use and understand GPSD replacement + * that uses D-Bus, GLib and memory allocations + * + */ + +/* + * simple-gps-gypsy: A simple gps example using the libgypsy library. + * This one uses the automatic bluetooth device discovery feature + * (gypsy_control_create_async). + */ + +#include +#include +#include +#include + +GypsyControl *control = NULL; + +static void +position_changed (GypsyPosition *position, + GypsyPositionFields fields_set, + int timestamp, + double latitude, + double longitude, + double altitude, + gpointer userdata) +{ + g_print ("%d: %2f, %2f (%1fm)\n", timestamp, + (fields_set & GYPSY_POSITION_FIELDS_LATITUDE) ? latitude :-1.0, + (fields_set & GYPSY_POSITION_FIELDS_LONGITUDE) ? longitude :-1.0, + (fields_set & GYPSY_POSITION_FIELDS_ALTITUDE) ? altitude : -1.0); +} + +static void +course_changed (GypsyCourse *course, + GypsyCourseFields fields_set, + int timestamp, + double speed, + double direction, + double climb, + gpointer userdata) +{ + g_print ("%d: %2f, %2f, %2fm/s\n", timestamp, + (fields_set & GYPSY_COURSE_FIELDS_SPEED) ? speed : -1.0, + (fields_set & GYPSY_COURSE_FIELDS_DIRECTION) ? direction : -1.0, + (fields_set & GYPSY_COURSE_FIELDS_CLIMB) ? climb : -1.0); +} + +static void +control_create_finished (GypsyControl *control, + gchar *path, + gpointer user_data) +{ + GypsyDevice *device; + GypsyPosition *position; + GypsyCourse *course; + + GError *error = NULL; + + if (!path) { + g_warning ("Creating Gypsy device failed"); + return; + } + + device = gypsy_device_new (path); + + position = gypsy_position_new (path); + g_signal_connect (position, "position-changed", + G_CALLBACK (position_changed), NULL); + + course = gypsy_course_new (path); + g_signal_connect (course, "course-changed", + G_CALLBACK (course_changed), NULL); + + gypsy_device_start (device, &error); + if (error != NULL) { + g_warning ("Error starting device: %s", error->message); + g_error_free (error); + } +} + + +int +main (int argc, + char **argv) +{ + GMainLoop *mainloop; + GError *error = NULL; + + g_type_init (); + + control = gypsy_control_get_default (); + if (!gypsy_control_create_async (control, + G_CALLBACK (control_create_finished), + NULL, &error)) { + g_warning ("Error creating gypsy client: %s\n", error->message); + g_error_free (error); + return 1; + } + + mainloop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (mainloop); + + /* FIXME: Need some way to quit the mainloop */ + g_object_unref (control); + + return 0; +} Index: examples/Makefile.am =================================================================== --- examples/Makefile.am (revision 134) +++ examples/Makefile.am (working copy) @@ -1,6 +1,7 @@ noinst_PROGRAMS = \ simple-gps-dbus \ simple-gps-gypsy \ + simple-gps-gypsy-async \ simple-gps-satellites simple_gps_dbus_SOURCES = simple-gps-dbus.c @@ -11,6 +12,10 @@ simple_gps_gypsy_LDADD = $(GYPSY_LIBS) $(top_builddir)/gypsy/libgypsy.la simple_gps_gypsy_CFLAGS = $(GYPSY_CFLAGS) -I$(top_srcdir) +simple_gps_gypsy_async_SOURCES = simple-gps-gypsy-async.c +simple_gps_gypsy_async_LDADD = $(GYPSY_LIBS) $(top_builddir)/gypsy/libgypsy.la +simple_gps_gypsy_async_CFLAGS = $(GYPSY_CFLAGS) -I$(top_srcdir) + simple_gps_satellites_SOURCES = simple-gps-satellites.c simple_gps_satellites_LDADD = $(GYPSY_LIBS) $(top_builddir)/gypsy/libgypsy.la simple_gps_satellites_CFLAGS = $(GYPSY_CFLAGS) -I$(top_srcdir)