]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Add STEMlab discovery using Avahi
authorMarkus Großer <markus.grosser2+git@gmail.com>
Wed, 15 Nov 2017 22:00:53 +0000 (23:00 +0100)
committerMarkus Großer <markus.grosser2+git@gmail.com>
Wed, 15 Nov 2017 22:00:53 +0000 (23:00 +0100)
This is currently hidden behind a flag in the Makefile as it introduces
two dependencies, namely Avahi and libcurl.

The rationale for this addition is that the STEMlab (also known as Red
Pitaya, the name under which it was previously sold) does start SDR
applications installed on it automatically. To avoid having to open the
web interface in order to use it as an SDR (which requires knowing its
IP or hostname, provided hostname resolution works), this patch adds
functionality to pihpsdr to discover the STEMlab using Avahi, and to
start a selected SDR application using its web API.

Makefile
discovered.h
discovery.c
stemlab_discovery.c [new file with mode: 0644]
stemlab_discovery.h [new file with mode: 0644]

index b5029ed19083b3fc79ce29116bc247aead721701..93917ec179b2e3515e3a9e065dcc7b13b8ed07dc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,9 @@ FREEDV_INCLUDE=FREEDV
 # uncomment the line to below include support local CW keyer
 #LOCALCW_INCLUDE=LOCALCW
 
+# uncomment the line below to include support for STEMlab discovery
+#STEMLAB_DISCOVERY=STEMLAB_DISCOVERY
+
 CC=gcc
 LINK=gcc
 
@@ -165,15 +168,25 @@ ifeq ($(I2C_INCLUDE),I2C)
   I2C_OBJS=i2c.o
 endif
 
+ifeq ($(STEMLAB_DISCOVERY), STEMLAB_DISCOVERY)
+STEMLAB_OPTIONS=-D STEMLAB_DISCOVERY \
+  `pkg-config --cflags avahi-gobject` \
+  `pkg-config --cflags libcurl`
+STEMLAB_LIBS=`pkg-config --libs avahi-gobject` `pkg-config --libs libcurl`
+STEMLAB_SOURCES=stemlab_discovery.c
+STEMLAB_HEADERS=stemlab_discovery.h
+STEMLAB_OBJS=stemlab_discovery.o
+endif
+
 GTKINCLUDES=`pkg-config --cflags gtk+-3.0`
 GTKLIBS=`pkg-config --libs gtk+-3.0`
 
 AUDIO_LIBS=-lasound
 #AUDIO_LIBS=-lsoundio
 
-OPTIONS=-g -Wno-deprecated-declarations $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(RADIOBERRY_OPTIONS) $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) $(LOCALCW_OPTIONS) $(PSK_OPTIONS) -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3
+OPTIONS=-g -Wno-deprecated-declarations $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(RADIOBERRY_OPTIONS) $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) $(LOCALCW_OPTIONS) $(PSK_OPTIONS) $(STEMLAB_OPTIONS) -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3
 
-LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS)
+LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS)
 INCLUDES=$(GTKINCLUDES)
 
 COMPILE=$(CC) $(OPTIONS) $(INCLUDES)
@@ -391,10 +404,10 @@ led.o \
 ext.o \
 error_handler.o
 
-$(PROGRAM):  $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(RADIOBERRY_OBJS)  $(PURESIGNAL_OBJS)
-       $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(LIBS) $(RADIOBERRY_OBJS) $(PURESIGNAL_OBJS)
+$(PROGRAM):  $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(RADIOBERRY_OBJS)  $(PURESIGNAL_OBJS) $(STEMLAB_OBJS)
+       $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(LIBS) $(RADIOBERRY_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS)
 
-all: prebuild  $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(RADIOBERRY_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(RADIOBERRY_SOURCES) $(PURESIGNAL_SOURCES)
+all: prebuild  $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(RADIOBERRY_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(RADIOBERRY_SOURCES) $(PURESIGNAL_SOURCES) $(STEMLAB_SOURCES)
 
 prebuild:
        rm -f version.o
index c99f84ffac56afdc691f02a3c4b8dc938a9ad471..85976bcce06750ab1e21387e547da2e1f24ff8b8 100644 (file)
 #ifdef REMOTE
 #define REMOTE_PROTOCOL 4
 #endif
+#ifdef STEMLAB_DISCOVERY
+// A STEMlab discovered via Avahi will have this protocol until the SDR
+// application itself is started, at which point it will be changed to the old
+// protocol and proceed to be handled just like a normal HPSDR radio.
+#define STEMLAB_PROTOCOL 5
+// Since there are multiple HPSDR applications for the STEMlab, but not all
+// are always installed, we need to keep track of which are installed, so the
+// user can choose which one should be started.
+// The software version field will be abused for this purpose
+#define STEMLAB_PAVEL_RX 1
+#define STEMLAB_PAVEL_TRX 2
+#define STEMLAB_RP_TRX 4
+#endif
+
 
 struct _DISCOVERED {
     int protocol;
index 0d4239bea05e35d1ebc35fe52755890afd78fda6..b4f7caaf84bee4e84c00e1d4a3f561392a0f90bc 100644 (file)
 #ifdef REMOTE
 #include "remote_radio.h"
 #endif
+#ifdef STEMLAB_DISCOVERY
+#include "stemlab_discovery.h"
+#endif
 #include "ext.h"
 
 static GtkWidget *discovery_dialog;
 static DISCOVERED *d;
 
+#ifdef STEMLAB_DISCOVERY
+static GtkWidget *apps_combobox;
+#endif
+
 static gboolean start_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
 fprintf(stderr,"start_cb: %p\n",data);
   radio=(DISCOVERED *)data;
+#ifdef STEMLAB_DISCOVERY
+  // We need to start the STEMlab app before destroying the dialog, since
+  // we otherwise lose the information about which app has been selected.
+  if (radio->protocol == STEMLAB_PROTOCOL) {
+    stemlab_start_app(gtk_combo_box_get_active_id(GTK_COMBO_BOX(apps_combobox)));
+  }
+  stemlab_cleanup();
+#endif
   gtk_widget_destroy(discovery_dialog);
   start_radio();
   return TRUE;
@@ -102,6 +117,10 @@ fprintf(stderr,"discovery\n");
   }
 #endif
 
+#ifdef STEMLAB_DISCOVERY
+  status_text("STEMlab (Avahi) ... Discovering Devices");
+  stemlab_discovery();
+#endif
 
   status_text("Protocol 1 ... Discovering Devices");
   old_discovery();
@@ -228,6 +247,17 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name);
                                case RADIOBERRY_PROTOCOL:
                                        sprintf(text,"%s\n",d->name);
                                break;
+#endif
+#ifdef STEMLAB_DISCOVERY
+        case STEMLAB_PROTOCOL:
+          sprintf(text, "STEMlab (%02X:%02X:%02X:%02X:%02X:%02X) on %s",
+                         d->info.network.mac_address[0],
+                         d->info.network.mac_address[1],
+                         d->info.network.mac_address[2],
+                         d->info.network.mac_address[3],
+                         d->info.network.mac_address[4],
+                         d->info.network.mac_address[5],
+                         d->info.network.interface_name);
 #endif
       }
 
@@ -254,6 +284,42 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name);
         gtk_widget_set_sensitive(start_button, FALSE);
       }
 
+#ifdef STEMLAB_DISCOVERY
+      if (d->protocol == STEMLAB_PROTOCOL) {
+        if (d->software_version == 0) {
+          gtk_button_set_label(GTK_BUTTON(start_button), "Not installed");
+          gtk_widget_set_sensitive(start_button, FALSE);
+        } else {
+          apps_combobox = gtk_combo_box_text_new();
+          gtk_widget_override_font(apps_combobox,
+              pango_font_description_from_string("FreeMono 12"));
+          // We want the default selection priority for the STEMlab app to be
+          // RP-Trx > Pavel-Trx > Pavel-Rx, so we add in decreasing order and
+          // always set the newly added entry to be active.
+          if ((d->software_version & STEMLAB_PAVEL_RX) != 0) {
+            gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox),
+                "sdr_receiver_hpsdr", "Pavel-Rx");
+            gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox),
+                "sdr_receiver_hpsdr");
+          }
+          if ((d->software_version & STEMLAB_PAVEL_TRX) != 0) {
+            gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox),
+                "sdr_transceiver_hpsdr", "Pavel-Trx");
+            gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox),
+                "sdr_transceiver_hpsdr");
+          }
+          if ((d->software_version & STEMLAB_RP_TRX) != 0) {
+            gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox),
+                "stemlab_sdr_transceiver_hpsdr", "RedPitaya-Trx");
+            gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox),
+                "stemlab_sdr_transceiver_hpsdr");
+          }
+          gtk_widget_show(apps_combobox);
+          gtk_grid_attach(GTK_GRID(grid), apps_combobox, 4, i, 1, 1);
+        }
+      }
+#endif
+
     }
 #ifdef GPIO
     GtkWidget *gpio_b=gtk_button_new_with_label("Config GPIO");
diff --git a/stemlab_discovery.c b/stemlab_discovery.c
new file mode 100644 (file)
index 0000000..154d923
--- /dev/null
@@ -0,0 +1,326 @@
+/* Copyright (C)
+* 2017 - Markus Großer, DL8GM
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+*
+*/
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <avahi-gobject/ga-client.h>
+#include <avahi-gobject/ga-service-browser.h>
+#include <avahi-gobject/ga-service-resolver.h>
+
+#include <curl/curl.h>
+
+#include <glib.h>
+
+#include "discovered.h"
+#include "radio.h"
+
+#define ERROR_PREFIX "stemlab_discovery: "
+
+// As we only run in the GTK+ main event loop, which is single-threaded and
+// non-preemptive, we shouldn't need any additional synchronisation mechanisms.
+static bool discovery_done = FALSE;
+static int pending_callbacks = 0;
+static struct ifaddrs *ifaddrs = NULL;
+static bool curl_initialised = FALSE;
+
+static size_t app_list_cb(void *buffer, size_t size, size_t nmemb, void *data) {
+  // cURL does *not* make any guarantees for this data to be the complete
+  // However, as the STEMlab answers in one big chunk, we just hope for the
+  // answer to be the complete json object, and avoid the hassle of manually
+  // building up our buffer.
+  int *software_version = (int*) data;
+  // This is not 100% clean, but avoids requiring in a json library dependency
+  const gchar *pavel_rx_json = "\"sdr_receiver_hpsdr\":";
+  if (g_strstr_len(buffer, size*nmemb, pavel_rx_json) != NULL) {
+    *software_version |= STEMLAB_PAVEL_RX;
+  }
+  const gchar *pavel_trx_json = "\"sdr_transceiver_hpsdr\":";
+  if (g_strstr_len(buffer, size*nmemb, pavel_trx_json) != NULL) {
+    *software_version |= STEMLAB_PAVEL_TRX;
+  }
+  const gchar *rp_trx_json = "\"stemlab_sdr_transceiver_hpsdr\":";
+  if (g_strstr_len(buffer, size*nmemb, rp_trx_json) != NULL) {
+    *software_version |= STEMLAB_RP_TRX;
+  }
+  // Returning the total amount of bytes "processed" to signal cURL that we
+  // are done without any errors
+  return size * nmemb;
+}
+
+static void resolver_found_cb(GaServiceResolver *resolver, AvahiIfIndex if_index,
+    GaProtocol protocol, gchar *name, gchar *service, gchar *domain,
+    gchar *hostname, AvahiAddress *address, gint port, AvahiStringList *txt,
+    GaLookupResultFlags flags) {
+  pending_callbacks--;
+  // RedPitaya's MAC address block starts with 00:26:32
+  unsigned char mac_address[6] = {0x00, 0x26, 0x32};
+  if (3 != sscanf(name, "rp-%2hhx%2hhx%2hhx HTTP",
+      &mac_address[3], &mac_address[4], &mac_address[5])) {
+    return;
+  }
+
+  // Avahi uses interface indices, as defined in net/if.h
+  // Everything else in this codebase however uses ifaddrs.h, so we need to
+  // "translate" between the two (aka look through the latter until we found
+  // one with the correct name)
+  char if_name[IF_NAMESIZE] = {0};
+  const char *indextoname_res = if_indextoname(if_index, if_name);
+  if (indextoname_res == NULL) {
+    const int error_code = errno;
+    perror(ERROR_PREFIX "Failed translating interface index to name\n");
+    return;
+  }
+  if (ifaddrs == NULL) {
+    const int getifaddrs_res = getifaddrs(&ifaddrs);
+    if (getifaddrs_res == -1) {
+      const int error_code = errno;
+      perror(ERROR_PREFIX "Failed enumerating interfaces");
+      ifaddrs = NULL;
+      return;
+    }
+  }
+  struct ifaddrs *current_if = ifaddrs;
+  while (current_if != NULL) {
+    if (current_if->ifa_addr != NULL
+      && current_if->ifa_addr->sa_family == AF_INET
+      && (current_if->ifa_flags & IFF_UP) != 0
+      && (current_if->ifa_flags & IFF_RUNNING) != 0
+      && strcmp(current_if->ifa_name, if_name) == 0) {
+      break;
+    }
+    current_if = current_if->ifa_next;
+  }
+  if (current_if == NULL) {
+    fprintf(stderr, ERROR_PREFIX "Did not find interface given by Avahi\n");
+    return;
+    // ifaddrs struct will be cleared up in cache_exhausted_cb, so no need
+    // to do it here
+  }
+
+  // Both Avahi as well as the standard headers use network byte order, so
+  // there is no necessity to do any endianness conversion here
+  struct in_addr ip_address = { .s_addr = address->data.ipv4.address };
+  CURL *curl_handle = curl_easy_init();
+  if (curl_handle == NULL) {
+    fprintf(stderr, ERROR_PREFIX "Failed to create cURL handler\n");
+  }
+  // This is just a dummy string to ensure the buffer has the correct length
+  char app_list_url[] = "http://123.123.123.123/bazaar?apps=";
+  int app_list = 0;
+  sprintf(app_list_url, "http://%s/bazaar?apps=", inet_ntoa(ip_address));
+#define check_curl(description) do { \
+  if (curl_error != CURLE_OK) { \
+    fprintf(stderr, ERROR_PREFIX description ": %s\n", \
+        curl_easy_strerror(curl_error)); \
+    curl_easy_cleanup(curl_handle); \
+    return; \
+  } \
+} while (0)
+  CURLcode curl_error = CURLE_OK;
+  curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, app_list_url);
+  check_curl("Failed setting cURL URL");
+  curl_error = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, app_list_cb);
+  check_curl("Failed setting cURL callback");
+  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &app_list);
+  check_curl("Failed setting cURL callback parameter");
+  // This is a blocking callback, so we don't need to do any additional shenanigans here
+  curl_easy_perform(curl_handle);
+  check_curl("Failed getting app list");
+#undef check_curl
+  curl_easy_cleanup(curl_handle);
+
+  DISCOVERED *device = &discovered[devices];
+  devices++;
+  device->protocol = STEMLAB_PROTOCOL;
+  device->device = DEVICE_METIS;
+  strcpy(device->name, "STEMlab");
+  device->software_version = app_list;
+  device->status = STATE_AVAILABLE;
+  memcpy(device->info.network.mac_address, mac_address, 6);
+  // Since elsewhere the pointers are just casted anyways, I don't really see
+  // a point in solving this in any other way
+  device->info.network.address_length = sizeof(struct sockaddr_in);
+  device->info.network.address.sin_family = AF_INET;
+  device->info.network.address.sin_addr = ip_address;
+  device->info.network.address.sin_port = htons(1024);
+  device->info.network.interface_length = sizeof(struct sockaddr_in);
+  device->info.network.interface_address = * (struct sockaddr_in *) current_if->ifa_addr;
+  device->info.network.interface_netmask = * (struct sockaddr_in *) current_if->ifa_netmask;
+  strcpy(device->info.network.interface_name, if_name);
+  fprintf(stderr, "|> Added STEMlab %.9s\n", name);
+}
+
+void resolver_failure_cb(GaServiceResolver *resolver, GError *error, gpointer data) {
+  pending_callbacks--;
+}
+
+static void new_service_cb(GaServiceBrowser *browser, gint if_index, GaProtocol protocol,
+    gchar *name, gchar *service, gchar *domain, GaLookupResultFlags flags,
+    GaClient *client) {
+  // We aren't actually interested in it here, but otherwise we can't
+  // differentiate between matching success and matching failure, since we'd
+  // always get 0 matched and assigned arguments
+  int mac_buffer = 0;
+  if (1 != sscanf(name, "rp-%6x HTTP", &mac_buffer)) {
+    return;
+  }
+  if (protocol != AVAHI_PROTO_INET) {
+    fprintf(stderr, ERROR_PREFIX "found %.9s via IPv6, skipping ...\n", name);
+    return;
+  }
+  GaServiceResolver *resolver = ga_service_resolver_new(if_index, protocol,
+      name, service, domain, AVAHI_PROTO_INET, GA_LOOKUP_NO_FLAGS);
+  if (resolver == NULL) {
+    fprintf(stderr, ERROR_PREFIX "Failed creating Avahi resolver for %.9s\n", name);
+    return;
+  }
+  GError *attachment_error = NULL;
+  if (!ga_service_resolver_attach(resolver, client, &attachment_error)) {
+    fprintf(stderr, ERROR_PREFIX "Failed attaching resolver to Avahi client: %s\n",
+        attachment_error == NULL ? "(Unknown Error)" : attachment_error->message);
+    return;
+  }
+  const gulong resolver_found_handler =
+      g_signal_connect(resolver, "found", G_CALLBACK(resolver_found_cb), NULL);
+  if (resolver_found_handler <= 0) {
+    fprintf(stderr, ERROR_PREFIX "Failed installing resolver \"found\" signal handler\n");
+    return;
+  }
+  if (g_signal_connect(resolver, "failure", G_CALLBACK(resolver_failure_cb), NULL) <= 0) {
+    fprintf(stderr, ERROR_PREFIX "Failed installing resolver \"failure\" signal handler\n");
+    g_signal_handler_disconnect(resolver, resolver_found_handler);
+    return;
+  }
+  pending_callbacks++;
+}
+
+static void cache_exhausted_cb(GaServiceBrowser *browser, gpointer data) {
+  if (ifaddrs != NULL) {
+    freeifaddrs(ifaddrs);
+    ifaddrs = NULL;
+  }
+  discovery_done = TRUE;
+}
+
+void stemlab_discovery(void) {
+  discovery_done = FALSE;
+  GaClient * const avahi_client = ga_client_new(GA_CLIENT_FLAG_NO_FLAGS);
+  if (avahi_client == NULL) {
+    fprintf(stderr, ERROR_PREFIX "Failed creating Avahi client\n");
+    return;
+  }
+  GaServiceBrowser * const avahi_browser = ga_service_browser_new("_http._tcp");
+  if (avahi_browser == NULL) {
+    fprintf(stderr, ERROR_PREFIX "Failed creating Avahi browser\n");
+    return;
+  }
+  GError *avahi_error = NULL;
+  if (!ga_client_start(avahi_client, &avahi_error)) {
+    fprintf(stderr, ERROR_PREFIX "Failed to start Avahi client: %s\n",
+      avahi_error == NULL ? "(Unknown Error)" : avahi_error->message);
+    return;
+  }
+  if (!ga_service_browser_attach(avahi_browser, avahi_client, &avahi_error)) {
+    fprintf(stderr, ERROR_PREFIX "Failed attaching Avahi browser to client: %s\n",
+      avahi_error == NULL ? "(Unknown Error)" : avahi_error->message);
+    return;
+  }
+  const gulong new_service_handler =
+    g_signal_connect(avahi_browser, "new-service", G_CALLBACK(new_service_cb),
+        (gpointer) avahi_client);
+  if (new_service_handler <= 0) {
+    fprintf(stderr, ERROR_PREFIX "Failed installing browser \"new-service\" callback\n");
+    return;
+  }
+  if(g_signal_connect(avahi_browser, "cache-exhausted",
+      G_CALLBACK(cache_exhausted_cb), (gpointer) NULL) <= 0) {
+    fprintf(stderr, ERROR_PREFIX "Failed installing browser \"cache-exhausted\" callback\n");
+    g_signal_handler_disconnect(avahi_browser, new_service_handler);
+    return;
+  }
+  // We need neither SSL nor Win32 sockets
+  const CURLcode curl_error = curl_global_init(CURL_GLOBAL_NOTHING);
+  if (curl_error != CURLE_OK) {
+    fprintf(stderr, ERROR_PREFIX "Failed to initialise cURL: %s\n",
+        curl_easy_strerror(curl_error));
+    return;
+  }
+  curl_initialised = TRUE;
+  while (!discovery_done || pending_callbacks > 0) {
+    g_main_context_iteration(NULL, TRUE);
+  }
+  fprintf(stderr, "|> STEMlab discovery done\n");
+}
+
+// This is essentially a no-op curl callback
+static size_t app_start_callback(void *buffer, size_t size, size_t nmemb, void *data) {
+  if (strncmp(buffer, "{\"status\":\"OK\"}", size*nmemb) != 0) {
+    fprintf(stderr, "stemlab_start: Receiver error from STEMlab\n");
+    return 0;
+  }
+  return size * nmemb;
+}
+
+void stemlab_start_app(const char * const app_id) {
+  // Dummy string, using the longest possible app id
+  char app_start_url[] = "http://rp-ff00ff/bazaar?start=stemlab_sdr_transceiver_hpsdr";
+  sprintf(app_start_url, "http://rp-%02hhx%02hhx%02hhx/bazaar?start=%s",
+          radio->info.network.mac_address[3],
+          radio->info.network.mac_address[4],
+          radio->info.network.mac_address[5],
+          app_id);
+  CURL *curl_handle = curl_easy_init();
+  if (curl_handle == NULL) {
+    fprintf(stderr, "stemlab_start: Failed to create cURL handle\n");
+    exit(-1);
+  }
+  CURLcode curl_error = CURLE_OK;
+#define check_curl(description) do { \
+  if (curl_error != CURLE_OK) { \
+    fprintf(stderr, "stemlab_start: " description "%s\n", \
+        curl_easy_strerror(curl_error)); \
+    exit(-1); \
+  } \
+}  while (0);
+  curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, app_start_url);
+  check_curl("Failed setting cURL URL");
+  curl_error = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, app_start_callback);
+  check_curl("Failed install cURL callback");
+  curl_error = curl_easy_perform(curl_handle);
+  check_curl("Failed to start app");
+#undef check_curl
+  curl_easy_cleanup(curl_handle);
+  // Since the SDR application is now running, we can hand it over to the
+  // regular HPSDR protocol handling code
+  radio->protocol = ORIGINAL_PROTOCOL;
+}
+
+void stemlab_cleanup(void) {
+  if (curl_initialised) {
+    curl_global_cleanup();
+  }
+}
diff --git a/stemlab_discovery.h b/stemlab_discovery.h
new file mode 100644 (file)
index 0000000..ce70868
--- /dev/null
@@ -0,0 +1,22 @@
+/* Copyright (C)
+* 2017 - Markus Großer, DL8GM
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+*
+*/
+
+extern void stemlab_discovery(void);
+extern void stemlab_start_app(const char * const app_id);
+extern void stemlab_cleanup(void);