summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--binding/CMakeLists.txt28
-rw-r--r--binding/radio_impl_rtlsdr.c270
-rw-r--r--binding/radio_output_pulse.c (renamed from binding/radio_output.c)0
-rw-r--r--binding/rtl_fm_helper.c242
-rw-r--r--conf.d/cmake/config.cmake3
-rw-r--r--conf.d/wgt/config.xml.in4
6 files changed, 487 insertions, 60 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
index 321022a..d279945 100644
--- a/binding/CMakeLists.txt
+++ b/binding/CMakeLists.txt
@@ -1,5 +1,6 @@
###########################################################################
# Copyright 2015, 2016, 2017 IoT.bzh
+# Copyright (C) 2018 Konsulko Group
#
# author: Fulup Ar Foll <fulup@iot.bzh>
# contrib: Romain Forlot <romain.forlot@iot.bzh>
@@ -23,11 +24,8 @@ PROJECT_TARGET_ADD(radio-binding)
# Define project Targets
set(radio_SOURCES
radio-binding.c
- radio_output.c
radio_impl_kingfisher.c
- radio_impl_rtlsdr.c
- rtl_fm.c
- convenience/convenience.c)
+ radio_impl_rtlsdr.c)
add_library(${TARGET_NAME} MODULE ${radio_SOURCES})
@@ -45,3 +43,25 @@ PROJECT_TARGET_ADD(radio-binding)
# installation directory
INSTALL(TARGETS ${TARGET_NAME}
LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
+
+# Add helper program target
+PROJECT_TARGET_ADD(rtl_fm_helper)
+
+ # Define project targets
+ set(helper_SOURCES
+ ${TARGET_NAME}.c
+ rtl_fm.c
+ convenience/convenience.c
+ radio_output.c)
+ PKG_CHECK_MODULES(SOUND REQUIRED libpulse-simple)
+
+ add_executable(${TARGET_NAME} ${helper_SOURCES})
+
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "EXECUTABLE"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ PKG_CHECK_MODULES(RTLSDR REQUIRED librtlsdr)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ ${RTLSDR_LIBRARIES} ${SOUND_LIBRARIES} ${link_libraries} m)
diff --git a/binding/radio_impl_rtlsdr.c b/binding/radio_impl_rtlsdr.c
index 22d627e..7fd4d69 100644
--- a/binding/radio_impl_rtlsdr.c
+++ b/binding/radio_impl_rtlsdr.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 Konsulko Group
+ * Copyright (C) 2017,2018 Konsulko Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,18 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <errno.h>
#include <glib.h>
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
#include "radio_impl.h"
-#include "radio_output.h"
-#include "rtl_fm.h"
+
+#define HELPER_NAME "rtl_fm_helper"
+#define HELPER_MAX PATH_MAX + 64
// Structure to describe FM band plans, all values in Hz.
typedef struct {
@@ -42,27 +49,91 @@ static fm_band_plan_t known_fm_band_plans[5] = {
};
static unsigned int bandplan;
+static pid_t helper_pid;
+static int helper_in;
+static int helper_out;
+static pthread_mutex_t helper_mutex = PTHREAD_MUTEX_INITIALIZER;
static bool present;
static bool active;
+static bool scanning;
static uint32_t current_frequency;
+static radio_freq_callback_t freq_callback;
+static void *freq_callback_data;
static uint32_t rtlsdr_get_min_frequency(radio_band_t band);
+static void rtlsdr_set_frequency(uint32_t frequency);
static void rtlsdr_scan_stop(void);
-static void rtl_output_callback(int16_t *result, int result_len, void *ctx)
+/*
+ * Bi-directional popen implementation
+ * Based on one of the versions given in:
+ *
+ * https://stackoverflow.com/questions/12778672/killing-process-that-has-been-created-with-popen2
+ */
+static pid_t popen2(char *command, int *in_fd, int *out_fd)
{
- if(active)
- radio_output_write((char*) result, result_len * 2);
+ int pin[2], pout[2];
+ pid_t pid;
+
+ if(out_fd != NULL) {
+ if(pipe(pin) != 0)
+ return -1;
+ }
+ if(in_fd != NULL) {
+ if (pipe(pout) != 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ return -1;
+ }
+ }
+
+ pid = fork();
+ if(pid < 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ close(pout[1]);
+ }
+ return pid;
+ }
+ if(pid == 0) {
+ if(out_fd != NULL) {
+ close(pin[1]);
+ dup2(pin[0], 0);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ dup2(pout[1], 1);
+ }
+ execlp(command, command, NULL);
+ perror("Error:");
+ exit(1);
+ }
+ if(in_fd != NULL) {
+ close(pout[1]);
+ *in_fd = pout[0];
+ }
+ if(out_fd != NULL) {
+ close(pin[0]);
+ *out_fd = pin[1];
+ }
+ return pid;
}
static int rtlsdr_init(void)
{
- GKeyFile* conf_file;
- int conf_file_present = 0;
+ GKeyFile *conf_file;
char *value_str;
+ char *rootdir;
+ char *helper_path;
if(present)
- return -1;
+ return 0;
// Load settings from configuration file if it exists
conf_file = g_key_file_new();
@@ -73,7 +144,6 @@ static int rtlsdr_init(void)
NULL,
G_KEY_FILE_KEEP_COMMENTS,
NULL) == TRUE) {
- conf_file_present = 1;
// Set band plan if it is specified
value_str = g_key_file_get_string(conf_file,
@@ -92,42 +162,43 @@ static int rtlsdr_init(void)
}
}
}
- fprintf(stderr, "Using FM Bandplan: %s\n", known_fm_band_plans[bandplan].name);
+ // Start off with minimum bandplan frequency
current_frequency = rtlsdr_get_min_frequency(BAND_FM);
- if(rtl_fm_init(current_frequency, 200000, 48000, rtl_output_callback, NULL) < 0) {
- return -1;
- }
- if(conf_file_present) {
- GError *error = NULL;
- int n;
-
- // Allow over-riding scanning parameters just in case a demo
- // setup needs to do so to work reliably.
- n = g_key_file_get_integer(conf_file,
- "radio",
- "scan_squelch_level",
- &error);
- if(!error) {
- fprintf(stderr, "Scanning squelch level set to %d\n", n);
- rtl_fm_scan_set_squelch_level(n);
- }
+ rootdir = getenv("AFM_APP_INSTALL_DIR");
+ if(!rootdir)
+ return -1;
- error = NULL;
- n = g_key_file_get_integer(conf_file,
- "radio",
- "scan_squelch_limit",
- &error);
- if(!error) {
- fprintf(stderr, "Scanning squelch limit set to %d\n", n);
- rtl_fm_scan_set_squelch_limit(n);
- }
+ // Run helper to detect adapter
+ helper_path = malloc(HELPER_MAX);
+ if(!helper_path)
+ return -ENOMEM;
+ if(snprintf(helper_path, HELPER_MAX, "%s/bin/%s --detect", rootdir, HELPER_NAME) == HELPER_MAX) {
+ AFB_ERROR("Could not create command for \"%s --detect\"", HELPER_NAME);
+ return -EINVAL;
+ }
+ if(system(helper_path) != 0) {
+ free(helper_path);
+ return -1;
+ }
- g_key_file_free(conf_file);
+ // Run helper
+ if(snprintf(helper_path, PATH_MAX, "%s/bin/%s", rootdir, HELPER_NAME) == PATH_MAX) {
+ AFB_ERROR("Could not create path to %s", HELPER_NAME);
+ return -EINVAL;
}
+ helper_pid = popen2(helper_path, &helper_out, &helper_in);
+ if(helper_pid < 0) {
+ AFB_ERROR("Could not run %s!", HELPER_NAME);
+ return -1;
+ }
+ AFB_DEBUG("%s started", HELPER_NAME);
+ free(helper_path);
present = true;
+ rtlsdr_set_frequency(current_frequency);
+
return 0;
}
@@ -138,6 +209,12 @@ static uint32_t rtlsdr_get_frequency(void)
static void rtlsdr_set_frequency(uint32_t frequency)
{
+ char cmd[64];
+ char output[128];
+ bool found = false;
+ int rc;
+ uint32_t n;
+
if(!present)
return;
@@ -145,19 +222,45 @@ static void rtlsdr_set_frequency(uint32_t frequency)
frequency > known_fm_band_plans[bandplan].max)
return;
- rtlsdr_scan_stop();
+ if(scanning)
+ rtlsdr_scan_stop();
+
current_frequency = frequency;
- rtl_fm_set_freq(frequency);
+ sprintf(cmd, "F=%u\n", frequency);
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ if(rc < 0) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ while(!found) {
+ rc = read(helper_out, output, 128);
+ if(rc <= 0)
+ break;
+ if(rc == 128)
+ rc = 127;
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ found = true;
+ }
+ }
+ }
+ pthread_mutex_unlock(&helper_mutex);
}
static void rtlsdr_set_frequency_callback(radio_freq_callback_t callback,
void *data)
{
- rtl_fm_set_freq_callback(callback, data);
+ freq_callback = callback;
+ freq_callback_data = data;
}
static radio_band_t rtlsdr_get_band(void)
{
+ // We only support FM
return BAND_FM;
}
@@ -206,10 +309,15 @@ static void rtlsdr_start(void)
return;
if(!active) {
- if(radio_output_start() != 0)
+ int rc;
+ char cmd[64];
+
+ sprintf(cmd, "START\n");
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc < 0)
return;
-
- rtl_fm_start();
active = true;
}
}
@@ -220,10 +328,14 @@ static void rtlsdr_stop(void)
return;
if(active) {
- active = false;
- radio_output_stop();
- rtl_fm_stop();
+ int rc;
+ char cmd[64];
+ active = false;
+ sprintf(cmd, "STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
}
}
@@ -231,21 +343,71 @@ static void rtlsdr_scan_start(radio_scan_direction_t direction,
radio_scan_callback_t callback,
void *data)
{
- rtl_fm_scan_start(direction == SCAN_FORWARD ? 0 : 1,
- callback,
- data,
- rtlsdr_get_frequency_step(BAND_FM),
- rtlsdr_get_min_frequency(BAND_FM),
- rtlsdr_get_max_frequency(BAND_FM));
+ int rc;
+ char cmd[64];
+ char output[128];
+ bool found = false;
+ uint32_t n;
+
+ if(!active || scanning)
+ return;
+
+ scanning = true;
+ sprintf(cmd, "S=%s\n", direction == SCAN_FORWARD ? "UP" : "DOWN");
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc < 0)
+ return;
+ while(!found) {
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ break;
+ }
+ rc = read(helper_out, output, 128);
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc <= 0)
+ break;
+ if(rc == 128)
+ rc = 127;
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ current_frequency = n;
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ }
+ }
+ if(output[0] == 'S') {
+ if(sscanf(output, "S=%u\n", &n) == 1) {
+ if(callback)
+ callback(n, data);
+ found = true;
+ scanning = false;
+ }
+ }
+ }
}
static void rtlsdr_scan_stop(void)
{
- rtl_fm_scan_stop();
+ char cmd[64];
+
+ sprintf(cmd, "S=STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ scanning = false;
+ write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
}
static radio_stereo_mode_t rtlsdr_get_stereo_mode(void)
{
+ // We only support stereo
return STEREO;
}
diff --git a/binding/radio_output.c b/binding/radio_output_pulse.c
index a49687b..a49687b 100644
--- a/binding/radio_output.c
+++ b/binding/radio_output_pulse.c
diff --git a/binding/rtl_fm_helper.c b/binding/rtl_fm_helper.c
new file mode 100644
index 0000000..9405ae2
--- /dev/null
+++ b/binding/rtl_fm_helper.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 Konsulko Group
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <glib.h>
+
+#include "rtl_fm.h"
+#include "radio_output.h"
+
+#ifdef DEBUG
+#define LOG_FILE "/tmp/helper.log"
+FILE *_log;
+#define LOG(...) \
+ do { \
+ if(!_log) _log = fopen(LOG_FILE, "w"); \
+ fprintf(_log, __VA_ARGS__); \
+ fflush(_log); \
+ } while(0)
+#else
+#define LOG(...) do { } while(0)
+#endif
+
+// Structure to describe FM band plans, all values in Hz.
+typedef struct {
+ const char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} fm_band_plan_t;
+
+static fm_band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+
+static unsigned int bandplan;
+static char line[64];
+
+static void rtl_output_callback(int16_t *result, int result_len, void *ctx)
+{
+ radio_output_write((char*) result, result_len * 2);
+}
+
+static void rtl_freq_callback(uint32_t freq, void *ctx)
+{
+ // Note, need to flush output to ensure parent sees it
+ printf("F=%u\n", freq);
+ fflush(stdout);
+ LOG("F=%u\n", freq);
+}
+
+static void rtl_scan_callback(uint32_t freq, void *ctx)
+{
+ // Note, need to flush output to ensure parent sees it
+ printf("S=%u\n", freq);
+ fflush(stdout);
+ LOG("S=%u\n", freq);
+}
+
+static void read_config(void)
+{
+ GKeyFile* conf_file;
+ int conf_file_present = 0;
+ char *value_str;
+
+ // Load settings from configuration file if it exists
+ conf_file = g_key_file_new();
+ if(conf_file &&
+ g_key_file_load_from_dirs(conf_file,
+ "AGL.conf",
+ (const gchar**) g_get_system_config_dirs(),
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ NULL) == TRUE) {
+ conf_file_present = 1;
+
+ // Set band plan if it is specified
+ value_str = g_key_file_get_string(conf_file,
+ "radio",
+ "fmbandplan",
+ NULL);
+ if(value_str) {
+ unsigned int i;
+ for(i = 0;
+ i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t);
+ i++) {
+ if(!strcasecmp(value_str, known_fm_band_plans[i].name)) {
+ bandplan = i;
+ break;
+ }
+ }
+ }
+ }
+ fprintf(stderr, "Using FM Bandplan: %s\n", known_fm_band_plans[bandplan].name);
+
+ if(conf_file_present) {
+ GError *error = NULL;
+ int n;
+
+ // Allow over-riding scanning parameters just in case a demo
+ // setup needs to do so to work reliably.
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_squelch_level",
+ &error);
+ if(!error) {
+ fprintf(stderr, "Scanning squelch level set to %d\n", n);
+ rtl_fm_scan_set_squelch_level(n);
+ }
+
+ error = NULL;
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_squelch_limit",
+ &error);
+ if(!error) {
+ fprintf(stderr, "Scanning squelch limit set to %d\n", n);
+ rtl_fm_scan_set_squelch_limit(n);
+ }
+
+ g_key_file_free(conf_file);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ bool detect = false;
+ bool done = false;
+ bool started = false;
+ uint32_t frequency;
+
+ LOG("started\n");
+
+ read_config();
+ frequency = known_fm_band_plans[bandplan].min;
+
+ if(argc == 2 && strcmp(argv[1], "--detect") == 0) {
+ detect = true;
+ }
+
+ rc = rtl_fm_init(frequency, 200000, 48000, rtl_output_callback, NULL);
+ if(rc < 0) {
+ fprintf(stderr, "No RTL USB adapter?\n");
+ exit(1);
+ }
+ if(detect) {
+ rtl_fm_cleanup();
+ exit(0);
+ }
+
+ rtl_fm_set_freq_callback(rtl_freq_callback, NULL);
+
+ while(!done) {
+ LOG("Reading command\n");
+ fgets(line, sizeof(line), stdin);
+ if(line[0] == '\0' || line[0] == '\n')
+ continue;
+ if(strcmp(line, "START\n") == 0) {
+ LOG("START received\n");
+ if(!started) {
+ LOG("Starting\n");
+ radio_output_start();
+ rtl_fm_start();
+ started = true;
+ }
+ } else if(strcmp(line, "STOP\n") == 0) {
+ LOG("STOP received\n");
+ if(started) {
+ LOG("Stopping\n");
+ radio_output_stop();
+ rtl_fm_stop();
+ started = false;
+ }
+ } else if(strncmp(line, "F=", 2) == 0) {
+ uint32_t n;
+ if(sscanf(line, "F=%u\n", &n) == 1) {
+ LOG("F=%d received\n", n);
+ rtl_fm_scan_stop();
+ rtl_fm_set_freq(n);
+ }
+ } else if(strcmp(line, "S=UP\n") == 0) {
+ LOG("S=UP received\n");
+ if(!started)
+ continue;
+ LOG("Calling rtl_fm_scan_start\n");
+ rtl_fm_scan_start(0,
+ rtl_scan_callback,
+ NULL,
+ known_fm_band_plans[bandplan].step,
+ known_fm_band_plans[bandplan].min,
+ known_fm_band_plans[bandplan].max);
+ } else if(strcmp(line, "S=DOWN\n") == 0) {
+ LOG("S=DOWN received\n");
+ if(!started)
+ continue;
+ LOG("Calling rtl_fm_scan_start\n");
+ rtl_fm_scan_start(1,
+ rtl_scan_callback,
+ NULL,
+ known_fm_band_plans[bandplan].step,
+ known_fm_band_plans[bandplan].min,
+ known_fm_band_plans[bandplan].max);
+ } else if(strcmp(line, "S=STOP\n") == 0) {
+ LOG("S=STOP received\n");
+ if(started) {
+ LOG("Calling rtl_fm_scan_stop\n");
+ rtl_fm_scan_stop();
+ }
+ } else if(line[0] == 'q' || line[0] == 'Q')
+ break;
+ }
+ if(started) {
+ radio_output_stop();
+ rtl_fm_stop();
+ }
+ rtl_fm_cleanup();
+ LOG("done");
+ return 0;
+}
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
index 95cee56..4fa7666 100644
--- a/conf.d/cmake/config.cmake
+++ b/conf.d/cmake/config.cmake
@@ -1,5 +1,6 @@
###########################################################################
# Copyright 2015, 2016, 2017 IoT.bzh
+# Copyright (C) 2018 Konsulko Group
#
# author: Fulup Ar Foll <fulup@iot.bzh>
#
@@ -69,8 +70,6 @@ set (PKG_REQUIRED_LIST
json-c
libsystemd>=222
afb-daemon
- librtlsdr
- libpulse-simple
glib-2.0
gobject-2.0
)
diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in
index 605374a..1ce4ff9 100644
--- a/conf.d/wgt/config.xml.in
+++ b/conf.d/wgt/config.xml.in
@@ -12,6 +12,10 @@
<param name="urn:AGL:permission::public:no-htdocs" value="required" />
</feature>
+ <feature name="urn:AGL:widget:file-properties">
+ <param name="bin/rtl_fm_helper" value="executable" />
+ </feature>
+
<feature name="urn:AGL:widget:provided-api">
<param name="radio" value="ws" />
</feature>