diff options
author | Scott Murray <scott.murray@konsulko.com> | 2023-01-03 00:51:50 -0500 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2023-01-19 00:37:11 +0000 |
commit | 7f26a2d06410fd3a2768612b9c9daf869778e480 (patch) | |
tree | 4dfffa78e14c8eadb42a731ae15cc87137e17723 /src/radio_impl_rtlsdr.c | |
parent | 6191be9c1e4b628f60ccaeabcef2aaa9de00800b (diff) |
Repurpose into gRPC servicequillback_17.0.0quillback/17.0.017.0.0
Repurpose repository into a spiritual successor of the previous
binding. The backend code is retained behind a new gRPC API
defined in protos/radio.proto. The simpler synchronous gRPC API
had been used for expediency, this may warrant revisiting to
rework into an async or callback API based server instead. As
well, authentication has been left until some consensus on an
approach can be worked out.
Bug-AGL: SPEC-4665
Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Change-Id: I28b122ce6e0ecfc7504aa08b90394cb1b9e22976
(cherry picked from commit dd23c157bdba1b25bbb50cdb99a60aa597735f43)
Diffstat (limited to 'src/radio_impl_rtlsdr.c')
-rw-r--r-- | src/radio_impl_rtlsdr.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/src/radio_impl_rtlsdr.c b/src/radio_impl_rtlsdr.c new file mode 100644 index 0000000..d978605 --- /dev/null +++ b/src/radio_impl_rtlsdr.c @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2017,2018,2020 Konsulko Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <pthread.h> +#include <sys/stat.h> +#include <errno.h> +#include <glib.h> + +#include "radio_impl.h" + +#define HELPER_NAME "rtl_fm_helper" +#define HELPER_MAX PATH_MAX + 64 + +#define HELPER_CMD_MAXLEN 64 +#define HELPER_RSP_MAXLEN 128 + +// Structure to describe FM band plans, all values in Hz. +typedef struct { + 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 *helper_output; +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 initialized; +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); + +/* + * 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) +{ + 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_probe(void) +{ + const char *bindir = "/usr/sbin"; + char *helper_path; + + // Run helper to detect adapter + helper_path = malloc(HELPER_MAX); + if(!helper_path) + return -ENOMEM; + if(snprintf(helper_path, HELPER_MAX, "%s/%s --detect", bindir, HELPER_NAME) == HELPER_MAX) { + fprintf(stderr, "Could not create command for \"%s --detect\"", HELPER_NAME); + return -EINVAL; + } + if(system(helper_path) != 0) { + free(helper_path); + return -1; + } + + present = true; + return 0; +} + +static int rtlsdr_start_helper(void) +{ + const char *bindir = "/usr/sbin"; + char *helper_path; + static bool helper_started = false; + + if(!present || initialized) + return -1; + + if(helper_started) + return 0; + + if(helper_output) { + // Indicate desired output to helper + printf("Setting RADIO_OUTPUT=%s", helper_output); + setenv("RADIO_OUTPUT", helper_output, 1); + } + + // Run helper + helper_path = malloc(HELPER_MAX); + if(!helper_path) + return -ENOMEM; + if(snprintf(helper_path, PATH_MAX, "%s/%s", bindir, HELPER_NAME) == PATH_MAX) { + fprintf(stderr, "Could not create path to %s", HELPER_NAME); + return -EINVAL; + } + helper_pid = popen2(helper_path, &helper_out, &helper_in); + if(helper_pid < 0) { + fprintf(stderr, "Could not run %s!", HELPER_NAME); + return -1; + } + printf("%s started", HELPER_NAME); + helper_started = true; + free(helper_path); + + return 0; +} + +static int rtlsdr_init(void) +{ + GKeyFile *conf_file; + char *value_str; + int rc; + + if(!present) + return -1; + + if(initialized) + return 0; + + // 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) { + + // 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; + } + } + } + } + + // Start off with minimum bandplan frequency + current_frequency = rtlsdr_get_min_frequency(RADIO_BAND_FM); + rc = rtlsdr_start_helper(); + if(rc != 0) { + return rc; + } + + initialized = true; + rtlsdr_set_frequency(current_frequency); + + return 0; +} + +static void rtlsdr_set_output(const char *output) +{ + // Save output for later use + free(helper_output); + helper_output = output ? strdup(output) : NULL; +} + +static uint32_t rtlsdr_get_frequency(void) +{ + return current_frequency; +} + +static void rtlsdr_set_frequency(uint32_t frequency) +{ + char cmd[HELPER_CMD_MAXLEN]; + char output[HELPER_RSP_MAXLEN]; + bool found = false; + ssize_t rc; + uint32_t n; + + if(!initialized) + return; + + if(frequency < known_fm_band_plans[bandplan].min || + frequency > known_fm_band_plans[bandplan].max) + return; + + if(scanning) + rtlsdr_scan_stop(); + + current_frequency = frequency; + snprintf(cmd, sizeof(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, sizeof(output)-1); + if(rc <= 0) + break; + 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) +{ + freq_callback = callback; + freq_callback_data = data; +} + +static radio_band_t rtlsdr_get_band(void) +{ + // We only support FM + return RADIO_BAND_FM; +} + +static void rtlsdr_set_band(radio_band_t band) +{ + // We only support FM, so do nothing +} + +static int rtlsdr_band_supported(radio_band_t band) +{ + if(band == RADIO_BAND_FM) + return 1; + return 0; +} + +static uint32_t rtlsdr_get_min_frequency(radio_band_t band) +{ + return known_fm_band_plans[bandplan].min; +} + +static uint32_t rtlsdr_get_max_frequency(radio_band_t band) +{ + return known_fm_band_plans[bandplan].max; +} + +static uint32_t rtlsdr_get_frequency_step(radio_band_t band) +{ + uint32_t ret = 0; + + switch (band) { + case RADIO_BAND_AM: + ret = 1000; // 1 kHz + break; + case RADIO_BAND_FM: + ret = known_fm_band_plans[bandplan].step; + break; + default: + break; + } + return ret; +} + +static void rtlsdr_start(void) +{ + ssize_t rc; + char cmd[HELPER_CMD_MAXLEN]; + + if(!initialized) + return; + + if(active) + return; + + snprintf(cmd, sizeof(cmd), "START\n"); + pthread_mutex_lock(&helper_mutex); + rc = write(helper_in, cmd, strlen(cmd)); + pthread_mutex_unlock(&helper_mutex); + if (rc < 0) { + fprintf(stderr, "Failed to ask \"%s\" to start", HELPER_NAME); + return; + } + active = true; +} + +static void rtlsdr_stop(void) +{ + ssize_t rc; + if(!initialized) + return; + + if (!active) + return; + + char cmd[HELPER_CMD_MAXLEN]; + + snprintf(cmd, sizeof(cmd), "STOP\n"); + pthread_mutex_lock(&helper_mutex); + rc = write(helper_in, cmd, strlen(cmd)); + pthread_mutex_unlock(&helper_mutex); + if (rc < 0) { + fprintf(stderr, "Failed to ask \"%s\" to stop", HELPER_NAME); + return; + } + active = false; +} + +static void rtlsdr_scan_start(radio_scan_direction_t direction, + radio_scan_callback_t callback, + void *data) +{ + ssize_t rc; + char cmd[HELPER_CMD_MAXLEN]; + char output[HELPER_RSP_MAXLEN]; + bool found = false; + uint32_t n; + + if(!active || scanning) + return; + + scanning = true; + snprintf(cmd, + sizeof(cmd), + "S=%s\n", direction == RADIO_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, sizeof(output)-1); + pthread_mutex_unlock(&helper_mutex); + if(rc <= 0) + break; + + 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) +{ + char cmd[HELPER_CMD_MAXLEN]; + ssize_t rc; + + snprintf(cmd, sizeof(cmd), "S=STOP\n"); + pthread_mutex_lock(&helper_mutex); + scanning = false; + rc = write(helper_in, cmd, strlen(cmd)); + pthread_mutex_unlock(&helper_mutex); + if (rc < 0) + fprintf(stderr, "Failed to ask \"%s\" to stop scan", HELPER_NAME); +} + +static radio_stereo_mode_t rtlsdr_get_stereo_mode(void) +{ + // We only support stereo + return RADIO_MODE_STEREO; +} + +static void rtlsdr_set_stereo_mode(radio_stereo_mode_t mode) +{ + // We only support stereo, so do nothing +} + +radio_impl_ops_t rtlsdr_impl_ops = { + .name = "RTL-SDR USB adapter", + .probe = rtlsdr_probe, + .init = rtlsdr_init, + .set_output = rtlsdr_set_output, + .get_frequency = rtlsdr_get_frequency, + .set_frequency = rtlsdr_set_frequency, + .set_frequency_callback = rtlsdr_set_frequency_callback, + .get_band = rtlsdr_get_band, + .set_band = rtlsdr_set_band, + .band_supported = rtlsdr_band_supported, + .get_min_frequency = rtlsdr_get_min_frequency, + .get_max_frequency = rtlsdr_get_max_frequency, + .get_frequency_step = rtlsdr_get_frequency_step, + .start = rtlsdr_start, + .stop = rtlsdr_stop, + .scan_start = rtlsdr_scan_start, + .scan_stop = rtlsdr_scan_stop, + .get_stereo_mode = rtlsdr_get_stereo_mode, + .set_stereo_mode = rtlsdr_set_stereo_mode +}; |