summaryrefslogtreecommitdiffstats
path: root/radio-binding.c
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2017-05-22 18:05:21 -0400
committerScott Murray <scott.murray@konsulko.com>2017-05-23 18:53:26 -0400
commit4a134c89fcd4afabb10aa32120495b8259bd0c41 (patch)
treeb8896295efd56165d1122e45acf733225447c857 /radio-binding.c
Rework to add and use a binding for radio control
A radio binding has been added in the new binding directory, and the application has been reworked to use it. The binding uses a modified version of the rtl_fm code used in the qtmultimedia radio plugin that was previously used, and some new code has been added to output to PulseAudio using the asynchronous API to ensure compatibility with stream corking. The rtl_fm code has been enhanced to add seeking support, and the application has been tweaked to use it. Bug-AGL: SPEC-581 Change-Id: I011e98374accc2cad2b36c93ac800948ee51f2aa Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Diffstat (limited to 'radio-binding.c')
-rw-r--r--radio-binding.c493
1 files changed, 493 insertions, 0 deletions
diff --git a/radio-binding.c b/radio-binding.c
new file mode 100644
index 0000000..12ed966
--- /dev/null
+++ b/radio-binding.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <json-c/json.h>
+#include <afb/afb-binding.h>
+#include <afb/afb-service-itf.h>
+
+#include "radio_impl.h"
+
+static const struct afb_binding_interface *interface;
+
+static struct afb_event freq_event;
+static struct afb_event scan_event;
+
+static void freq_callback(uint32_t frequency, void *data)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_int((int) frequency);
+
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(freq_event, json_object_get(jresp));
+}
+
+static void scan_callback(uint32_t frequency, void *data)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_int((int) frequency);
+
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(scan_event, json_object_get(jresp));
+}
+
+/*
+ * Binding verb handlers
+ */
+
+/*
+ * @brief Get (and optionally set) frequency
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void frequency(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ uint32_t frequency;
+
+ if(value) {
+ char *p;
+ frequency = strtoul(value, &p, 10);
+ if(frequency && *p == '\0') {
+ radio_impl_set_frequency(frequency);
+ } else {
+ afb_req_fail(request, "failed", "Invalid scan direction");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ frequency = radio_impl_get_frequency();
+ json_object_object_add(ret_json, "frequency", json_object_new_int((int32_t) frequency));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Get (and optionally set) frequency band
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void band(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ int valid = 0;
+ radio_band_t band;
+ char band_name[4];
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(valid) {
+ radio_impl_set_band(band);
+ } else {
+ afb_req_fail(request, "failed", "Invalid band");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ band = radio_impl_get_band();
+ sprintf(band_name, "%s", band == BAND_AM ? "AM" : "FM");
+ json_object_object_add(ret_json, "band", json_object_new_string(band_name));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Check if band is supported
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void band_supported(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_fail(request, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ json_object_object_add(ret_json,
+ "supported",
+ json_object_new_int(radio_impl_band_supported(band)));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Get frequency range for a band
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void frequency_range(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+ uint32_t min_frequency;
+ uint32_t max_frequency;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_fail(request, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ min_frequency = radio_impl_get_min_frequency(band);
+ max_frequency = radio_impl_get_max_frequency(band);
+ json_object_object_add(ret_json, "min", json_object_new_int((int32_t) min_frequency));
+ json_object_object_add(ret_json, "max", json_object_new_int((int32_t) max_frequency));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Get frequency step size (Hz) for a band
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void frequency_step(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+ uint32_t step;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_fail(request, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ step = radio_impl_get_frequency_step(band);
+ json_object_object_add(ret_json, "step", json_object_new_int((int32_t) step));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Start radio playback
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void start(struct afb_req request)
+{
+ radio_impl_start();
+ afb_req_success(request, NULL, NULL);
+}
+
+/*
+ * @brief Stop radio playback
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void stop(struct afb_req request)
+{
+ radio_impl_stop();
+ afb_req_success(request, NULL, NULL);
+}
+
+/*
+ * @brief Scan for a station in the specified direction
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void scan_start(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "direction");
+ int valid = 0;
+ radio_scan_direction_t direction;
+
+ if(value) {
+ if(!strcasecmp(value, "forward")) {
+ direction = SCAN_FORWARD;
+ valid = 1;
+ } else if(!strcasecmp(value, "backward")) {
+ direction = SCAN_BACKWARD;
+ valid = 1;
+ } else {
+ char *p;
+ direction = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(direction) {
+ case SCAN_FORWARD:
+ case SCAN_BACKWARD:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_fail(request, "failed", "Invalid direction");
+ return;
+ }
+ radio_impl_scan_start(direction, scan_callback, NULL);
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Stop station scan
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void scan_stop(struct afb_req request)
+{
+ radio_impl_scan_stop();
+ afb_req_success(request, NULL, NULL);
+}
+
+/*
+ * @brief Get (and optionally set) stereo mode
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void stereo_mode(struct afb_req request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ int valid = 0;
+ radio_stereo_mode_t mode;
+ char mode_name[4];
+
+ if(value) {
+ if(!strcasecmp(value, "mono")) {
+ mode = MONO;
+ valid = 1;
+ } else if(!strcasecmp(value, "stereo")) {
+ mode = STEREO;
+ valid = 1;
+ } else {
+ char *p;
+ mode = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(mode) {
+ case MONO:
+ case STEREO:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(valid) {
+ radio_impl_set_stereo_mode(mode);
+ } else {
+ afb_req_fail(request, "failed", "Invalid mode");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ mode = radio_impl_get_stereo_mode();
+ sprintf(mode_name, "%s", mode == MONO ? "mono" : "stereo");
+ json_object_object_add(ret_json, "mode", json_object_new_string(mode_name));
+ afb_req_success(request, ret_json, NULL);
+}
+
+/*
+ * @brief Subscribe for an event
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void subscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+ if(value) {
+ if(!strcasecmp(value, "frequency")) {
+ afb_req_subscribe(request, freq_event);
+ } else if(!strcasecmp(value, "station_found")) {
+ afb_req_subscribe(request, scan_event);
+ } else {
+ afb_req_fail(request, "failed", "Invalid event");
+ return;
+ }
+ }
+ afb_req_success(request, NULL, NULL);
+}
+
+/*
+ * @brief Unsubscribe for an event
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void unsubscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+ if(value) {
+ if(!strcasecmp(value, "frequency")) {
+ afb_req_unsubscribe(request, freq_event);
+ } else if(!strcasecmp(value, "station_found")) {
+ afb_req_unsubscribe(request, scan_event);
+ } else {
+ afb_req_fail(request, "failed", "Invalid event");
+ return;
+ }
+ }
+ afb_req_success(request, NULL, NULL);
+}
+
+static const struct afb_verb_desc_v1 verbs[]= {
+ { "frequency", AFB_SESSION_CHECK, frequency, "Get/Set frequency" },
+ { "band", AFB_SESSION_CHECK, band, "Get/Set band" },
+ { "band_supported", AFB_SESSION_CHECK, band_supported, "Check band support" },
+ { "frequency_range", AFB_SESSION_CHECK, frequency_range, "Get frequency range" },
+ { "frequency_step", AFB_SESSION_CHECK, frequency_step, "Get frequency step" },
+ { "start", AFB_SESSION_CHECK, start, "Start radio playback" },
+ { "stop", AFB_SESSION_CHECK, stop, "Stop radio playback" },
+ { "scan_start", AFB_SESSION_CHECK, scan_start, "Start station scan" },
+ { "scan_stop", AFB_SESSION_CHECK, scan_stop, "Stop station scan" },
+ { "stereo_mode", AFB_SESSION_CHECK, stereo_mode, "Get/Set stereo_mode" },
+ { "subscribe", AFB_SESSION_CHECK, subscribe, "Subscribe for an event" },
+ { "unsubscribe", AFB_SESSION_CHECK, unsubscribe, "Unsubscribe for an event" },
+ { NULL }
+};
+
+static const struct afb_binding binding_desc = {
+ .type = AFB_BINDING_VERSION_1,
+ .v1 = {
+ .info = "radio service",
+ .prefix = "radio",
+ .verbs = verbs
+ }
+};
+
+const struct afb_binding *afbBindingV1Register (const struct afb_binding_interface *itf)
+{
+ interface = itf;
+
+ return &binding_desc;
+}
+
+int afbBindingV1ServiceInit(struct afb_service service)
+{
+ int rc;
+
+ freq_event = afb_daemon_make_event(interface->daemon, "frequency");
+ scan_event = afb_daemon_make_event(interface->daemon, "station_found");
+
+ rc = radio_impl_init();
+ if(rc == 0) {
+ radio_impl_set_frequency_callback(freq_callback, NULL);
+ }
+
+ return rc;
+}