diff options
-rw-r--r-- | binding/CMakeLists.txt | 28 | ||||
-rw-r--r-- | binding/radio_impl_rtlsdr.c | 270 | ||||
-rw-r--r-- | binding/radio_output_pulse.c (renamed from binding/radio_output.c) | 0 | ||||
-rw-r--r-- | binding/rtl_fm_helper.c | 242 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 3 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 4 |
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> |