diff options
author | Thierry Bultel <thierry.bultel@iot.bzh> | 2018-12-05 14:05:10 +0100 |
---|---|---|
committer | Thierry Bultel <thierry.bultel@iot.bzh> | 2018-12-12 22:01:30 +0100 |
commit | b2588f97269799bdcbc6253286ffaa28d5d7aeff (patch) | |
tree | f81f6fe6523d9c389f1ef4e7bc44fb127a323a41 | |
parent | 4b299e632148205bb27c9c4a4d6dd779d466ff37 (diff) |
Adds support for bluetooth audio through bluez-alsasandbox/tbultel/bluealsa
Implements a new bluealsa plugin to the HAL manager,
reacting to the changes of the available transports.
This plugin is linked with the new bluealsa.so shared
library.
New transports (SCO & A2DP) result in softmixer invocations
of the "attach" verb, that creates the new capture
(eg, A2DP capture from bluealsa ioplug PCM, SCO microphone
capture), playbacks (SCO playback to a softmixer zone,
and SCO output to bluealsa iogplug PCM).
When a transport disappears, the hal manager calls the
transaction deletion verb that will tell the softmixer
to remove the created streams and associated objects.
Signed-off-by: Thierry Bultel <thierry.bultel@iot.bzh>
24 files changed, 1017 insertions, 1253 deletions
diff --git a/4a-hal-cfg-community/hal-4a-2ch-generic-usb.json b/4a-hal-cfg-community/hal-4a-2ch-generic-usb.json index efc59e3..be3bdb6 100644 --- a/4a-hal-cfg-community/hal-4a-2ch-generic-usb.json +++ b/4a-hal-cfg-community/hal-4a-2ch-generic-usb.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "2CH-GENERIC-USB", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-csl-cm106-8ch-usb.json b/4a-hal-cfg-community/hal-4a-csl-cm106-8ch-usb.json index 09bfcfa..f5d6b33 100644 --- a/4a-hal-cfg-community/hal-4a-csl-cm106-8ch-usb.json +++ b/4a-hal-cfg-community/hal-4a-csl-cm106-8ch-usb.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "CSL-CM106-8CH-USB", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-ensoniq.json b/4a-hal-cfg-community/hal-4a-ensoniq.json index 7d1dc80..dc30584 100644 --- a/4a-hal-cfg-community/hal-4a-ensoniq.json +++ b/4a-hal-cfg-community/hal-4a-ensoniq.json @@ -10,14 +10,21 @@ "date": "2018-06-13" }, "resources": [ - { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + { + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "ENSONIQ", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-intel-upsquared-hdmi.json b/4a-hal-cfg-community/hal-4a-intel-upsquared-hdmi.json index 8597489..6cc1cf7 100644 --- a/4a-hal-cfg-community/hal-4a-intel-upsquared-hdmi.json +++ b/4a-hal-cfg-community/hal-4a-intel-upsquared-hdmi.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "INTEL-UPSQUARED-HDMI", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-intel.json b/4a-hal-cfg-community/hal-4a-intel.json index 1e27896..0752915 100644 --- a/4a-hal-cfg-community/hal-4a-intel.json +++ b/4a-hal-cfg-community/hal-4a-intel.json @@ -10,14 +10,21 @@ "date": "2018-06-13" }, "resources": [ - { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + { + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "INTEL", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-jabra.json b/4a-hal-cfg-community/hal-4a-jabra.json index d9879d4..37bdf48 100644 --- a/4a-hal-cfg-community/hal-4a-jabra.json +++ b/4a-hal-cfg-community/hal-4a-jabra.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "JABRA-SOLEMATE", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-community/hal-4a-raspberry-pi-3.json b/4a-hal-cfg-community/hal-4a-raspberry-pi-3.json index 86be677..99eb2d0 100644 --- a/4a-hal-cfg-community/hal-4a-raspberry-pi-3.json +++ b/4a-hal-cfg-community/hal-4a-raspberry-pi-3.json @@ -11,14 +11,22 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "RASPBERRY-PI-3", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } + ], "controls": [ { diff --git a/4a-hal-cfg-reference/hal-4a-intel-minnow.json b/4a-hal-cfg-reference/hal-4a-intel-minnow.json index 9a5effb..d96672b 100644 --- a/4a-hal-cfg-reference/hal-4a-intel-minnow.json +++ b/4a-hal-cfg-reference/hal-4a-intel-minnow.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "INTEL-MINNOW", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-reference/hal-4a-intel-qemu.json b/4a-hal-cfg-reference/hal-4a-intel-qemu.json index eed75de..8b34231 100644 --- a/4a-hal-cfg-reference/hal-4a-intel-qemu.json +++ b/4a-hal-cfg-reference/hal-4a-intel-qemu.json @@ -11,13 +11,20 @@ }, "resources": [ { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "INTEL-QEMU", + "zone": "full-stereo", + "delayms": 800 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 500 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-reference/hal-4a-rcar-m3.json b/4a-hal-cfg-reference/hal-4a-rcar-m3.json index 5644262..5df9bd6 100644 --- a/4a-hal-cfg-reference/hal-4a-rcar-m3.json +++ b/4a-hal-cfg-reference/hal-4a-rcar-m3.json @@ -10,14 +10,21 @@ "date": "2018-06-13" }, "resources": [ - { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + { + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "RCAR-M3", + "zone": "full-stereo", + "delayms": 200 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 1000 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/4a-hal-cfg-reference/hal-4a-rcar-m3kf.json b/4a-hal-cfg-reference/hal-4a-rcar-m3kf.json index d374fcf..a897d47 100644 --- a/4a-hal-cfg-reference/hal-4a-rcar-m3kf.json +++ b/4a-hal-cfg-reference/hal-4a-rcar-m3kf.json @@ -10,14 +10,21 @@ "date": "2018-06-15" }, "resources": [ - { - "uid": "hal-bt", - "info": "Bluetooth hal plugin to get A2DP bluetooth device as an input by tweaking softmixer configuration", + { + "uid": "hal-bluealsa", + "info": "Bluealsa hal plugin", "params": { - "channels": 2, - "zone": "front-seats" + "sco": { + "mic": "RCAR-M3", + "zone": "full-stereo", + "delayms": 200 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 1000 + } }, - "libs": ["hal-bt.ctlso"] + "libs": ["hal-bluealsa.ctlso"] } ], "controls": [ diff --git a/plugins/lib/bluetooth/CMakeLists.txt b/plugins/lib/bluealsa/CMakeLists.txt index ae9f5ab..699ccc4 100644 --- a/plugins/lib/bluetooth/CMakeLists.txt +++ b/plugins/lib/bluealsa/CMakeLists.txt @@ -1,7 +1,7 @@ ########################################################################### -# Copyright 2015, 2016, 2017, 2018 IoT.bzh +# Copyright 2018 IoT.bzh # -# author: Jonathan Aillet <jonathan.aillet@iot.bzh> +# author: Thierry Bultel <thierry.bultel@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,14 +16,15 @@ # limitations under the License. ########################################################################### -PROJECT_TARGET_ADD(hal-bt) + +PROJECT_TARGET_ADD(hal-bluealsa) + + pkg_check_modules(BLUEALSA REQUIRED bluealsa>=1.3.1) # Define targets ADD_LIBRARY(${TARGET_NAME} MODULE - hal-bt.c - hal-bt-cb.c - hal-bt-data.c - hal-bt-mixer-link.c + hal-bluealsa.c + hal-bluealsa-transports.c ) # Alsa Plugin properties @@ -38,9 +39,12 @@ PROJECT_TARGET_ADD(hal-bt) TARGET_LINK_LIBRARIES(${TARGET_NAME} afb-helpers ctl-utilities + ${BLUEALSA_LIBRARIES} ${link_libraries} ) + TARGET_COMPILE_OPTIONS(${TARGET_NAME} PUBLIC ${BLUEALSA_CFLAGS}) + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/app-controller/ctl-lib" PRIVATE "${CMAKE_SOURCE_DIR}/4a-hal/4a-hal-utilities" diff --git a/plugins/lib/bluealsa/hal-bluealsa-transports.c b/plugins/lib/bluealsa/hal-bluealsa-transports.c new file mode 100644 index 0000000..2c58023 --- /dev/null +++ b/plugins/lib/bluealsa/hal-bluealsa-transports.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author : Thierry Bultel <thierry.bultel@iot.bzh> + * + * 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 <malloc.h> + +#include "hal-bluealsa-transports.h" + + +static bool halBlueAlsaTransportMatch( + const struct ba_msg_transport * transport1, + const struct ba_msg_transport * transport2); + +int halBlueAlsaTransportsInit(bluealsa_transport_t * list) { + CDS_INIT_LIST_HEAD(&list->list); + return 0; +} + +bluealsa_transport_t * halBlueAlsaTransportsAdd( + const bluealsa_watch * watch, + bluealsa_transport_t * list, + const struct ba_msg_transport * transport) { + + bluealsa_transport_t * newTransport = (bluealsa_transport_t*) calloc(1, sizeof(bluealsa_transport_t)); + if (newTransport == NULL) + goto fail; + + memcpy(&newTransport->transport, transport, sizeof(struct ba_msg_transport)); + + newTransport->watch = watch; + + CDS_INIT_LIST_HEAD(&newTransport->list); + cds_list_add(&newTransport->list, &list->list); + + return newTransport; +fail: + return NULL; + +} + +bool halBlueAlsaTransportFind( + const bluealsa_watch * watch, + bluealsa_transport_t * list, + const struct ba_msg_transport * transport) { + + bluealsa_transport_t *tmp; + bool found = false; + cds_list_for_each_entry(tmp, &list->list, list) { + struct ba_msg_transport * _transport = &tmp->transport; + + if (watch != tmp->watch) + continue; + + if (!halBlueAlsaTransportMatch(_transport, transport)) + continue; + + found = true; + break; + } + return found; +} + + +int halBlueAlsaTransportUpdate( + const bluealsa_watch * watch, + bluealsa_transport_t * list, + const struct ba_msg_transport *transports, + size_t nb, + transport_destructor destructor) { + + bluealsa_transport_t *transport, *sav; + + cds_list_for_each_entry_safe(transport, sav, &list->list, list) { + struct ba_msg_transport * ba_transport1 = &transport->transport; + + if (watch != transport->watch) + continue; + + bool found = false; + + for (int ix = 0; ix<nb; ix++) { + const struct ba_msg_transport * ba_transport2 = &transports[ix]; + + if (halBlueAlsaTransportMatch(ba_transport1, ba_transport2)) { + found = true; + break; + } + } + + if (found) + continue; + + char transportS[HAL_BLUEALSA_TRANSPORT_LEN_MAX]; + + AFB_ApiInfo(watch->plugin->api, + "Unregister transport %s", + halBlueAlsaTransportAsString(transportS, HAL_BLUEALSA_TRANSPORT_LEN_MAX, &transport->transport)); + + cds_list_del(&transport->list); + if (destructor) { + destructor(transport); + } + + free(transport->transactionUidS); + free(transport); + + } + + return 0; +} + + +static bool halBlueAlsaTransportMatch(const struct ba_msg_transport * transport1, const struct ba_msg_transport * transport2) { + bool ret = false; + + if (transport1->type != transport2->type) + goto done; + + if (transport1->stream != transport2->stream) + goto done; + + if (bacmp(&transport1->addr, &transport2->addr) != 0) { + goto done; + } + ret = true; +done: + return ret; +} + + +char * halBlueAlsaTransportAsString(char * buff, size_t len, const struct ba_msg_transport * transport) { + char addr[18]; + + ba2str(&transport->addr, addr); + snprintf(buff, len, "%s,%s,%s", addr, + transport->type == BA_PCM_TYPE_A2DP?"a2dp":"sco", + transport->stream == BA_PCM_STREAM_PLAYBACK?"playback":"capture"); + + return buff; +} diff --git a/plugins/lib/bluealsa/hal-bluealsa-transports.h b/plugins/lib/bluealsa/hal-bluealsa-transports.h new file mode 100644 index 0000000..0c5d6a4 --- /dev/null +++ b/plugins/lib/bluealsa/hal-bluealsa-transports.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author : Thierry Bultel <thierry.bultel@iot.bzh> + * + * 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. + */ + +#ifndef __INC_HAL_BLUEALSA_TRANSPORT_H +#define __INC_HAL_BLUEALSA_TRANSPORT_H + +#include <bluealsa/bluealsa.h> +#include <stdbool.h> +#include <urcu/list.h> + +#include "afb-definitions.h" +#include "hal-bluealsa-watch.h" + +typedef struct { + const bluealsa_watch * watch; /* owner */ + struct cds_list_head list; + struct ba_msg_transport transport; + char * transactionUidS; +} bluealsa_transport_t; + +extern int halBlueAlsaTransportsInit(bluealsa_transport_t * list); + +extern bluealsa_transport_t * halBlueAlsaTransportsAdd(const bluealsa_watch * watch, bluealsa_transport_t * list, const struct ba_msg_transport * transport); +extern bool halBlueAlsaTransportFind(const bluealsa_watch * watch, bluealsa_transport_t * list, const struct ba_msg_transport * transport); + +typedef int (*transport_destructor)(bluealsa_transport_t *); + +extern int halBlueAlsaTransportUpdate( + const bluealsa_watch * watch, + bluealsa_transport_t * list, + const struct ba_msg_transport *transports, + size_t nb, + transport_destructor); + +#define HAL_BLUEALSA_TRANSPORT_LEN_MAX 64 + +extern char * halBlueAlsaTransportAsString(char * s, size_t len, const struct ba_msg_transport * ); + +#endif /* __INC_HAL_BLUEALSA_TRANSPORT_H */ diff --git a/plugins/lib/bluetooth/hal-bt.h b/plugins/lib/bluealsa/hal-bluealsa-watch.h index af5f6c3..114f515 100644 --- a/plugins/lib/bluetooth/hal-bt.h +++ b/plugins/lib/bluealsa/hal-bluealsa-watch.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> + * Author : Thierry Bultel <thierry.bultel@iot.bzh> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,15 @@ * limitations under the License. */ -#ifndef _HAL_BT_INCLUDE_ -#define _HAL_BT_INCLUDE_ +#ifndef __INC_HAL_BLUEALSA_WATCH_H +#define __INC_HAL_BLUEALSA_WATCH_H -#include <stdio.h> -#include <string.h> -#include <stdbool.h> +#include <ctl-config.h> -#define HAL_BT_PLUGIN_NAME "hal-bt" +typedef struct { + int fd; + const char * interface; + CtlPluginT * plugin; +} bluealsa_watch; -#define BT_MANAGER_API "Bluetooth-Manager" -#define BT_MANAGER_GET_POWER_INFO "power" -#define BT_MANAGER_SUBSCRIBE_VERB "subscribe" -#define BT_MANAGER_GET_DEVICES_VERB "discovery_result" - -#define BT_MANAGER_DEVICE_UPDATE_EVENT "device_updated" - -#endif /* _HAL_BT_INCLUDE_ */
\ No newline at end of file +#endif /* __INC_HAL_BLUEALSA_WATCH_H */ diff --git a/plugins/lib/bluealsa/hal-bluealsa.c b/plugins/lib/bluealsa/hal-bluealsa.c new file mode 100644 index 0000000..c1fd338 --- /dev/null +++ b/plugins/lib/bluealsa/hal-bluealsa.c @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author : Thierry Bultel <thierry.bultel@iot.bzh> + * + * 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 <stdio.h> +#include <unistd.h> + +#include <wrap-json.h> + +#include <errno.h> +#include <ctl-config.h> +#include <systemd/sd-event.h> +#include <bluealsa/bluealsa.h> + +#include "hal-bluealsa.h" + +#define HAL_BLUEALSA_PLUGIN_NAME "hal-bluealsa" + +/* forward static declarations */ +static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData); +static int halBlueAlsaFetchTransports(bluealsa_watch * plugin); +static int halBlueAlsaRegisterAll(CtlPluginT* plugin); +static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface); + +CTLP_CAPI_REGISTER(HAL_BLUEALSA_PLUGIN_NAME) + + +// Call at initialization time ('requires' are forbidden at this stage) +CTLP_ONLOAD(plugin, callbacks) +{ + AFB_ApiNotice(plugin->api, "%s Plugin Registered correctly: uid='%s' 'info='%s'", HAL_BLUEALSA_PLUGIN_NAME, plugin->uid, plugin->info); + return 0; +} + + +CTLP_INIT(plugin, callbacks) +{ + json_object *actionsToAdd = NULL; + CtlConfigT *ctrlConfig; + + wrap_json_pack(&actionsToAdd, "{s:s s:s s:s}", + "uid", "init-bluealsa-plugin", + "info", "Init Bluez-Alsa hal plugin", + "action", "plugin://hal-bluealsa#init"); + + if (!(ctrlConfig = (CtlConfigT *) AFB_ApiGetUserData(plugin->api))) { + AFB_ApiError(plugin->api, "Can't get current hal controller config"); + goto fail; + } + + int idx = 0; + while (ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "onload")) + idx++; + + if (!ctrlConfig->sections[idx].key) { + AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload, 'onload' section not found", json_object_get_string(actionsToAdd)); + goto fail; + } + + if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) { + AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].uid); + goto fail; + } + + AFB_ApiNotice(plugin->api, "Plugin initialization of %s plugin correctly done", HAL_BLUEALSA_PLUGIN_NAME); + + return 0; +fail: + json_object_put(actionsToAdd); + return -1; +} + + +// Call at controller onload time +CTLP_CAPI(init, source, argsJ, queryJ) +{ + AFB_ApiNotice(source->api, "Controller onload event"); + + CtlPluginT * plugin = source->plugin; + + hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*) calloc(1, sizeof(hal_bluealsa_plugin_data_t)); + int error; + + /* + * "params": { + "sco": { + "mic": "2CH-GENERIC-USB", + "zone": "full-stereo", + "delayms": 300 + }, + "a2dp": { + "zone": "full-stereo", + "delayms": 1000 + } + } + */ + + json_object * scoParamsJ = NULL; + json_object * a2dpParamsJ = NULL; + + error = wrap_json_unpack(plugin->paramsJ, "{s?o,s?o !}", + "sco", &scoParamsJ, + "a2dp", &a2dpParamsJ); + + if (error) { + AFB_ApiError(plugin->api, "%s: wrong parameters", __func__); + goto fail; + } + + if (scoParamsJ) { + AFB_ApiInfo(plugin->api, "%s: sco parameters: %s", __func__, json_object_get_string(scoParamsJ)); + error = wrap_json_unpack(scoParamsJ, "{s:s,s:s,s?i !}", + "mic", &pluginData->sco.mic, + "zone", &pluginData->sco.speaker, + "delayms", &pluginData->sco.delayms); + if (error) { + AFB_ApiError(plugin->api, "%s: wrong sco parameters: err %s", __func__, wrap_json_get_error_string(error)); + goto fail; + } + } + + if (a2dpParamsJ) { + AFB_ApiInfo(plugin->api, "%s: a2dp parameters: %s", __func__, json_object_get_string(a2dpParamsJ)); + error = wrap_json_unpack(a2dpParamsJ, "{s:s,s?i !}", + "zone", &pluginData->a2dp.zone, + "delayms", &pluginData->a2dp.delayms); + if (error) { + AFB_ApiError(plugin->api, "%s: wrong a2dp parameters: err=%s", __func__, wrap_json_get_error_string(error)); + goto fail; + } + } + + halBlueAlsaTransportsInit(&pluginData->transport_list); + + setPluginContext(plugin, pluginData); + + if (halBlueAlsaRegisterAll(plugin) != 0) + goto fail; + + return 0; +fail: + return -1; +} + + +static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { + + bluealsa_watch * watch = (bluealsa_watch *)userData; + CtlPluginT * plugin = watch->plugin; + + struct ba_msg_event event; + ssize_t ret; + + AFB_ApiNotice(plugin->api, "%s 0x%x!", __func__, revents); + + if ((revents & EPOLLIN) == 0) + goto done; + + if (revents & EPOLLHUP) { + AFB_ApiInfo(plugin->api, "Lost connection with bluealsa on interface %s", watch->interface); + sd_event_source_unref(src); + close(fd); + halBlueAlsaRegister(plugin, watch->interface); + goto done; + } + + while ((ret = recv(watch->fd, &event, sizeof(event), MSG_DONTWAIT)) == -1 && errno == EINTR) + continue; + + if (ret != sizeof(event)) { + AFB_ApiError(plugin->api, "Couldn't read event: %s", strerror(ret == -1 ? errno : EBADMSG)); + goto done; + } + + AFB_ApiDebug(plugin->api, "event mask 0x%x", event.mask); + + halBlueAlsaFetchTransports(watch); + +done: + return 0; +} + + +/* + * + * The following function builds that kind of json data and send it to smixer + * + * A2DP Case: + * + * { + "uid":"a2dp_F6:32:15:2A:80:70", + "captures": + { + "uid":"a2dp_listen_capture", + "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=a2dp", + "source" : { + "channels": [ + { + "uid": "a2dp-right", + "port": 0 + }, + { + "uid": "a2dp-left", + "port": 1 + } + ] + } + }, + "streams": + { + "uid" : "a2dp_listen_stream", + "source":"a2dp_listen_capture", + "zone": "full-stereo" + } + } + * + *----------------------------------------------------------------------------------- + * + * SCO Case: + * + * { + "uid":"sco_F6:32:15:2A:80:70",", + "captures": + { + "uid":"sco_listen_capture", + "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco", + "source" : { + "channels": [ + { + "uid": "sco-right", + "port": 0 + }, + { + "uid": "sco-left", + "port": 1 + } + ] + } + }, + "playbacks" + { + "uid"="sco_talk_playback", + "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco", + "sink": { + "channels": [ + { + "uid": "sco-right", + "port": 0 + }, + { + "uid": "sco-left", + "port": 1 + } + ] + } + }, + "streams": [ + { + "uid" : "sco_listen_stream", + "source":"sco_listen_capture", + "zone": "full-stereo", + "delayms": 300 + }, + { + "uid" : "sco_talk_stream", + "source": "2CH-GENERIC-USB", + "playback": "sco_talk_playback", + "delayms": 1000 + } + ] + } + * + * */ + +static json_object* halBlueAlsaTransportChannels(const char * transportTypeS) { + json_object* channelRightJ; + json_object* channelLeftJ; + json_object* channelsJ = json_object_new_array(); + char * channelRightS; + char * channelLeftS; + + channelRightJ = json_object_new_object(); + if (asprintf(&channelRightS, "%s-right", transportTypeS) == -1) + goto fail; + + json_object_object_add(channelRightJ, "uid", json_object_new_string(channelRightS)); + json_object_object_add(channelRightJ, "port", json_object_new_int(0)); + + channelLeftJ = json_object_new_object(); + if (asprintf(&channelLeftS, "%s-left", transportTypeS) == -1) + goto fail; + + json_object_object_add(channelLeftJ, "uid", json_object_new_string(channelLeftS)); + json_object_object_add(channelLeftJ, "port", json_object_new_int(1)); + + json_object_array_add(channelsJ, channelRightJ); + json_object_array_add(channelsJ, channelLeftJ); +fail: + return channelsJ; +} + +static json_object* halBlueAlsaPcmPlugParams(const char * interface, const char * addr, const char * transportTypeS) { + char * pcmplug_paramsS = NULL; + if (asprintf(&pcmplug_paramsS, "bluealsa:HCI=%s,DEV=%s,PROFILE=%s", interface, addr, transportTypeS) == -1) + goto fail; + return json_object_new_string(pcmplug_paramsS); +fail: + return NULL; +} + + +static json_object* halBlueAlsaListenCapture( + const char * listenCaptureS, + json_object * pcmplugParamsJ, + json_object * sourceJ) { + + json_object * captureJ = json_object_new_object(); + + json_object_object_add(captureJ, "uid", json_object_new_string(listenCaptureS)); + json_object_object_add(captureJ, "pcmplug_params", pcmplugParamsJ); + json_object_object_add(captureJ, "source", sourceJ); + + return captureJ; +} + +static json_object * halBlueAlsaTalkPlayback( + const char * talkPlaybackS, + json_object* pcmplugParamsJ, + json_object* sinkJ ) { + + json_object * playbackJ = json_object_new_object(); + + json_object_object_add(playbackJ, "uid", json_object_new_string(talkPlaybackS)); + json_object_object_add(playbackJ, "pcmplug_params", pcmplugParamsJ); + json_object_object_add(playbackJ, "sink", sinkJ); + + return playbackJ; +} + + +static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) { + int ret = -1; + const char* transportTypeS; + + struct ba_msg_transport * ba_transport = &transport->transport; + const bluealsa_watch * watch = transport->watch; + + CtlPluginT* plugin = watch->plugin; + hal_bluealsa_plugin_data_t * pluginData=getPluginContext(plugin); + + json_object* requestJ = NULL; + json_object* returnJ = NULL; + + json_object* sourceJ = NULL; + json_object* playbackJ = NULL; + + json_object* pcmplugParamsJ, *pcmplugParamsJ2 = NULL; + + json_object* streamsJ = NULL; + json_object* streamJ = NULL; + + char * playbackZoneS; + uint32_t delayms; + + if (ba_transport->type == BA_PCM_TYPE_SCO) { + transportTypeS = "sco"; + delayms = pluginData->sco.delayms; + playbackZoneS = pluginData->sco.speaker; + } + else if (ba_transport->type == BA_PCM_TYPE_A2DP) { + transportTypeS = "a2dp"; + delayms = pluginData->a2dp.delayms; + playbackZoneS = pluginData->a2dp.zone; + } else { + AFB_ApiError(plugin->api, "%s: unsupported transport type", __func__ ); + goto fail; + } + + char * captureS = NULL; + if (asprintf(&captureS, "%s_listen_capture", transportTypeS) == -1) + goto fail; + + // for SCO only + char * playbackS = NULL; + if (asprintf(&playbackS, "%s_talk_playback", transportTypeS) == -1) + goto fail; + + char * transactionUidS = NULL; + char * streamS = NULL; + + char addr[18]; + ba2str(&ba_transport->addr, addr); + + requestJ = json_object_new_object(); + if (!requestJ) + goto fail; + + if (asprintf(&transactionUidS, "%s_%s", transportTypeS, addr) == -1) + goto fail; + + json_object_object_add(requestJ, "uid", json_object_new_string(transactionUidS)); + + pcmplugParamsJ = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS); + + sourceJ = json_object_new_object(); + json_object_object_add(sourceJ, "channels", halBlueAlsaTransportChannels(transportTypeS)); + json_object_object_add(requestJ, "captures", halBlueAlsaListenCapture(captureS, pcmplugParamsJ, sourceJ)); + + if (ba_transport->type == BA_PCM_TYPE_SCO) { + playbackJ = json_object_new_object(); + // that is a shame that deep copy in not available in the current json-c version + pcmplugParamsJ2 = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS); + + json_object_object_add(playbackJ, "channels", halBlueAlsaTransportChannels(transportTypeS)); + json_object_object_add(requestJ, "playbacks", halBlueAlsaTalkPlayback(playbackS, pcmplugParamsJ2, playbackJ)); + } + + /* Build the array of streams */ + streamsJ = json_object_new_array(); + + streamJ = json_object_new_object(); + if (asprintf(&streamS, "%s_listen_stream", transportTypeS) == -1) + goto fail; + + json_object_object_add(streamJ, "uid", json_object_new_string(streamS)); + json_object_object_add(streamJ, "source", json_object_new_string(captureS)); + json_object_object_add(streamJ, "zone", json_object_new_string(playbackZoneS)); + if (delayms != 0) { + json_object_object_add(streamJ, "delayms", json_object_new_int(delayms)); + } + + json_object_array_add(streamsJ, streamJ); + + /* In case of SCO, to have full-duplex, we instantiate a stream for talk */ + if (ba_transport->type == BA_PCM_TYPE_SCO ) { + + streamJ = json_object_new_object(); + if (asprintf(&streamS, "%s_talk_stream", transportTypeS) == -1) + goto fail; + + json_object_object_add(streamJ, "uid", json_object_new_string(streamS)); + json_object_object_add(streamJ, "source", json_object_new_string(pluginData->sco.mic)); + json_object_object_add(streamJ, "playback", json_object_new_string(playbackS)); + if (delayms != 0) + json_object_object_add(streamJ, "delayms", json_object_new_int(delayms)); + json_object_array_add(streamsJ, streamJ); + } + + json_object_object_add(requestJ, "streams", streamsJ); + + /* In softmixer, this will create a transaction verb (whose name is transactionUidS), + * will be used later to destroy all the created objects upon transport removal */ + + if (AFB_ServiceSync(plugin->api, SMIXER_API_NAME, "attach", requestJ, &returnJ)) { + AFB_ApiError(plugin->api, "Error calling attach verb of mixer" ); + goto done; + } + + transport->transactionUidS = transactionUidS; + + if (returnJ) + json_object_put(returnJ); + + ret = 0; + goto done; + +fail: + return -1; +done: + AFB_ApiDebug(plugin->api, "DONE."); + return ret; +} + + +static int halBluezAlsaRemoveTransportStream(bluealsa_transport_t * transport) { + + CtlPluginT * plugin = transport->watch->plugin; + json_object* requestJ = NULL; + json_object* returnJ = NULL; + + AFB_ApiInfo(plugin->api, "Call transaction detach verb %s", transport->transactionUidS); + if (transport->transactionUidS == NULL) + goto fail; + + requestJ = json_object_new_object(); + if (!requestJ) + goto fail; + + json_object_object_add(requestJ, "action", json_object_new_string("remove")); + + if (AFB_ServiceSync(plugin->api, SMIXER_API_NAME, transport->transactionUidS, requestJ, &returnJ)) { + AFB_ApiError(plugin->api, "Error calling attach verb of mixer" ); + goto fail; + } + + if (returnJ) + json_object_put(returnJ); + + return 0; + +fail: + return -1; +} + +static int halBlueAlsaFetchTransports(bluealsa_watch * watch) { + ssize_t nbTransports; + struct ba_msg_transport *transports; + CtlPluginT * plugin = watch->plugin; + + hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*)getPluginContext(plugin); + bluealsa_transport_t * transport_list = &pluginData->transport_list; + bluealsa_transport_t * transport = NULL; + + AFB_ApiDebug(plugin->api, "Fetching available transports of interface %s", watch->interface); + + if ((nbTransports = bluealsa_get_transports(watch->fd, &transports)) == -1) { + AFB_ApiError(plugin->api, "Couldn't get transports: %s", strerror(errno)); + goto done; + } + + AFB_ApiDebug(plugin->api, "Got %zu transport(s)", nbTransports); + + for (int ix=0; ix<nbTransports; ix++) { + char addr[18]; + struct ba_msg_transport * ba_transport = &transports[ix]; + ba2str(&ba_transport->addr, addr); + const char * typeS; + if (ba_transport->type == BA_PCM_TYPE_SCO) + typeS = "sco"; + else if (ba_transport->type == BA_PCM_TYPE_A2DP) + typeS = "a2dp"; + else + typeS = "unknown"; + + AFB_ApiDebug(plugin->api, "Transport %d: type %s, dev %s", ix, typeS, addr); + + if (halBlueAlsaTransportFind(watch, transport_list, ba_transport)) { + AFB_ApiDebug(plugin->api, "This transport is already streamed"); + continue; + } + + AFB_ApiInfo(plugin->api, "Registering transport type %s, dev %s", typeS, addr); + transport = halBlueAlsaTransportsAdd(watch, transport_list, ba_transport); + if (transport == NULL) { + AFB_ApiError(plugin->api, "Failed to register this transport"); + goto done; + } + + // Do the softmixer stuff + if (halBlueAlsaAttachTransportStreams(transport) != 0) { + AFB_ApiError(plugin->api, "Failed create transport streams"); + goto done; + } + + AFB_ApiDebug(plugin->api, "%s: transaction id %s", __func__, transport->transactionUidS); + + } + + halBlueAlsaTransportUpdate(watch, transport_list, transports, nbTransports , halBluezAlsaRemoveTransportStream); + +done: + free(transports); + return 0; +} + +static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface) { + int ret; + sd_event *sdLoop; + sd_event_source* evtsrc; + + sdLoop = AFB_GetEventLoop(plugin->api); + + enum ba_event transport_mask = BA_EVENT_TRANSPORT_ADDED | +// BA_EVENT_TRANSPORT_CHANGED | + BA_EVENT_TRANSPORT_REMOVED; + + bluealsa_watch * watch = (bluealsa_watch *)malloc(sizeof(bluealsa_watch)); + if (watch == NULL) + goto fail; + + watch->interface = interface; + watch->plugin = plugin; + + if ((watch->fd = bluealsa_open(interface)) == -1) { + AFB_ApiError(plugin->api, "BlueALSA connection failed: %s", strerror(errno)); + goto fail; + } + + halBlueAlsaFetchTransports(watch); + + if (bluealsa_subscribe(watch->fd, transport_mask) == -1) { + AFB_ApiError(plugin->api, "BlueALSA subscription failed: %s", strerror(errno)); + goto fail; + } + + // Register sound event to the main loop of the binder + if ((ret = sd_event_add_io(sdLoop, &evtsrc, watch->fd, EPOLLIN, halBlueAlsaTransportEventCB, watch)) < 0) { + AFB_ApiError(plugin->api, + "%s: Failed to register event fd to io loop", + __func__); + goto fail; + } + return 0; + +fail: + if (watch->fd) + close(watch->fd); + if (watch) + free(watch); + return -1; +} + + +static int halBlueAlsaRegisterAll(CtlPluginT* plugin) { + /* TODO add a watch to all the available interfaces */ + halBlueAlsaRegister(plugin, "hci0"); + return 0; +} diff --git a/plugins/lib/bluealsa/hal-bluealsa.h b/plugins/lib/bluealsa/hal-bluealsa.h new file mode 100644 index 0000000..86fe45a --- /dev/null +++ b/plugins/lib/bluealsa/hal-bluealsa.h @@ -0,0 +1,21 @@ +#ifndef __INC_HAL_BLUEALSA_H +#define __INC_HAL_BLUEALSA_H + +#include "hal-bluealsa-transports.h" + +typedef struct { + bluealsa_transport_t transport_list; + struct { + char * mic; // capture for talking + char * speaker; // playback zone + uint32_t delayms; + } sco; + struct { + char * zone; + uint32_t delayms; + } a2dp; +} hal_bluealsa_plugin_data_t; + +#define SMIXER_API_NAME "smixer" + +#endif /* __INC_HAL_BLUEALSA_H */ diff --git a/plugins/lib/bluetooth/hal-bt-cb.c b/plugins/lib/bluetooth/hal-bt-cb.c deleted file mode 100644 index 0638d50..0000000 --- a/plugins/lib/bluetooth/hal-bt-cb.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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 <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <wrap-json.h> - -#include <ctl-config.h> - -#include "hal-bt-data.h" -#include "hal-bt-mixer-link.h" - -/******************************************************************************* - * HAL Bluetooth plugin verbs functions * - ******************************************************************************/ - -void HalBtGetStreamingStatus(AFB_ReqT request) -{ - struct HalBtPluginData *localHalBtPluginData; - - if(! (localHalBtPluginData = (struct HalBtPluginData *) AFB_ReqVCBData(request))) { - AFB_ReqFail(request, "bt_plugin_data", "Can't get bluetooth plugin data"); - return; - } - - AFB_ReqSuccess(request, json_object_new_string(localHalBtPluginData->btStreamEnabled ? "enabled" : "disabled"), "Bluetooth streaming status"); -} - -void HalBtSetStreamingStatus(AFB_ReqT request) -{ - unsigned int requestedBtStreamingStatus; - - struct HalBtPluginData *localHalBtPluginData; - - json_object *requestJson; - - AFB_ApiT apiHandle; - - if(! (localHalBtPluginData = (struct HalBtPluginData *) AFB_ReqVCBData(request))) { - AFB_ReqFail(request, "bt_plugin_data", "Can't get bluetooth plugin data"); - return; - } - - if(! (requestJson = AFB_ReqJson(request))) { - AFB_ReqFail(request, "request_json", "Can't get request json"); - return; - } - - if(! (apiHandle = (AFB_ApiT ) AFB_ReqGetApi(request))) { - AFB_ReqFail(request, "api_handle", "Can't get current hal controller api handle"); - return; - } - - if(wrap_json_unpack(requestJson, "{s:b}", "status", &requestedBtStreamingStatus)) { - AFB_ReqFail(request, "requested_status", "Can't get requested bluetooth streaming status"); - return; - } - - localHalBtPluginData->btStreamEnabled = requestedBtStreamingStatus; - - if(HalBtMixerLinkSetBtStreamingSettings(apiHandle, - localHalBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName, - localHalBtPluginData->btStreamEnabled, - localHalBtPluginData->selectedBtDevice ? localHalBtPluginData->selectedBtDevice->hci : NULL, - localHalBtPluginData->selectedBtDevice ? localHalBtPluginData->selectedBtDevice->address : NULL)) { - AFB_ApiError(apiHandle, - "Couldn't set bluetooth streaming settings during call to verb '%s' of api '%s'", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - localHalBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName); - AFB_ReqFail(request, "requested_device_to_select", "Error happened during set of streamed bt device"); - return; - } - - AFB_ReqSuccess(request, NULL, "Bluetooth streaming status successfully set"); -} - -void HalBtGetA2DPBluetoothDevices(AFB_ReqT request) -{ - struct HalBtPluginData *localHalBtPluginData; - struct HalBtDeviceData *currentBtDeviceData; - - json_object *requestAnswer, *currentBtDeviceObjectJ; - - if(! (localHalBtPluginData = (struct HalBtPluginData *) AFB_ReqVCBData(request))) { - AFB_ReqFail(request, "bt_plugin_data", "Can't get bluetooth plugin data"); - return; - } - - if(! (currentBtDeviceData = localHalBtPluginData->first)) { - AFB_ReqSuccess(request, NULL, "No A2DP bluetooth device connected"); - return; - } - - requestAnswer = json_object_new_array(); - if(! requestAnswer) { - AFB_ReqFail(request, "json_answer", "Can't generate json answer"); - return; - } - - while(currentBtDeviceData) { - wrap_json_pack(¤tBtDeviceObjectJ, - "{s:s s:s s:s}", - "Interface", currentBtDeviceData->hci, - "Name", currentBtDeviceData->name, - "Address", currentBtDeviceData->address); - json_object_array_add(requestAnswer, currentBtDeviceObjectJ); - - currentBtDeviceData = currentBtDeviceData->next; - } - - AFB_ReqSuccess(request, requestAnswer, "Connected A2DP bluetooth devices list"); -} - -void HalBtGetSelectedA2DPBluetoothDevice(AFB_ReqT request) -{ - struct HalBtPluginData *localHalBtPluginData; - - json_object *selectedBtDeviceObject; - - if(! (localHalBtPluginData = (struct HalBtPluginData *) AFB_ReqVCBData(request))) { - AFB_ReqFail(request, "bt_plugin_data", "Can't get bluetooth plugin data"); - return; - } - - if(! localHalBtPluginData->selectedBtDevice) { - AFB_ReqSuccess(request, NULL, "No bluetooth device selected"); - return; - } - - wrap_json_pack(&selectedBtDeviceObject, - "{s:s s:s s:s}", - "Interface", localHalBtPluginData->selectedBtDevice->hci, - "Name", localHalBtPluginData->selectedBtDevice->name, - "Address", localHalBtPluginData->selectedBtDevice->address); - - AFB_ReqSuccess(request, selectedBtDeviceObject, "Selected Bluetooth device"); -} - -void HalBtSetSelectedA2DPBluetoothDevice(AFB_ReqT request) -{ - char *requestedBtDeviceToSelect; - - struct HalBtPluginData *localHalBtPluginData; - struct HalBtDeviceData *selectedBtDeviceData; - - json_object *requestJson; - - AFB_ApiT apiHandle; - - if(! (localHalBtPluginData = (struct HalBtPluginData *) AFB_ReqVCBData(request))) { - AFB_ReqFail(request, "bt_plugin_data", "Can't get bluetooth plugin data"); - return; - } - - if(! (requestJson = AFB_ReqJson(request))) { - AFB_ReqFail(request, "request_json", "Can't get request json"); - return; - } - - if(! (apiHandle = (AFB_ApiT ) AFB_ReqGetApi(request))) { - AFB_ReqFail(request, "api_handle", "Can't get current hal controller api handle"); - return; - } - - if(wrap_json_unpack(requestJson, "{s:s}", "Address", &requestedBtDeviceToSelect)) { - AFB_ReqFail(request, "requested_device_to_select", "Can't get requested bluetooth device to select"); - return; - } - - if(! (selectedBtDeviceData = HalBtDataSearchBtDeviceByAddress(&localHalBtPluginData->first, requestedBtDeviceToSelect))) { - AFB_ReqFail(request, "requested_device_to_select", "Requested A2DP bluetooth device to select is not currently connected"); - return; - } - - localHalBtPluginData->selectedBtDevice = selectedBtDeviceData; - - if(HalBtMixerLinkSetBtStreamingSettings(apiHandle, - localHalBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName, - localHalBtPluginData->btStreamEnabled, - localHalBtPluginData->selectedBtDevice->hci, - localHalBtPluginData->selectedBtDevice->address)) { - AFB_ApiError(apiHandle, - "Couldn't set bluetooth streaming settings during call to verb '%s' of api '%s'", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - localHalBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName); - AFB_ReqFail(request, "requested_device_to_select", "Error happened during set of streamed bt device"); - return; - } - - AFB_ReqSuccess(request, NULL, "Selected bluetooth device successfully set"); -}
\ No newline at end of file diff --git a/plugins/lib/bluetooth/hal-bt-cb.h b/plugins/lib/bluetooth/hal-bt-cb.h deleted file mode 100644 index f0f1052..0000000 --- a/plugins/lib/bluetooth/hal-bt-cb.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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. - */ - -#ifndef _HAL_BT_CB_INCLUDE_ -#define _HAL_BT_CB_INCLUDE_ - -#include <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <ctl-config.h> - -#define HAL_BT_GET_STREAMING_STATUS_VERB "get_bt_streaming_status" -#define HAL_BT_SET_STREAMING_STATUS_VERB "set_bt_streaming_status" -#define HAL_BT_GET_CONNECTED_A2DP_DEVICES_VERB "get_connected_bt_a2dp_devices" -#define HAL_BT_GET_SELECTED_A2DP_DEVICE_VERB "get_selected_bt_a2dp_device" -#define HAL_BT_SET_SELECTED_A2DP_DEVICE_VERB "set_selected_bt_a2dp_device" - -// HAL Bluetooth plugin verbs functions -void HalBtGetStreamingStatus(AFB_ReqT request); -void HalBtSetStreamingStatus(AFB_ReqT request); -void HalBtGetA2DPBluetoothDevices(AFB_ReqT request); -void HalBtGetSelectedA2DPBluetoothDevice(AFB_ReqT request); -void HalBtSetSelectedA2DPBluetoothDevice(AFB_ReqT request); - -#endif /* _HAL_BT_CB_INCLUDE_ */
\ No newline at end of file diff --git a/plugins/lib/bluetooth/hal-bt-data.c b/plugins/lib/bluetooth/hal-bt-data.c deleted file mode 100644 index f3cb0e4..0000000 --- a/plugins/lib/bluetooth/hal-bt-data.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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 <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <wrap-json.h> - -#include "hal-bt-data.h" - -/******************************************************************************* - * Bt device data handling functions * - ******************************************************************************/ - -int HalBtDataRemoveSelectedBtDeviceFromList(struct HalBtDeviceData **firstBtDeviceData, struct HalBtDeviceData *btDeviceDataToRemove) -{ - struct HalBtDeviceData *currentBtDevice, *matchingBtDevice; - - if(! firstBtDeviceData || ! btDeviceDataToRemove) - return -1; - - currentBtDevice = *firstBtDeviceData; - - if(currentBtDevice == btDeviceDataToRemove) { - *firstBtDeviceData = currentBtDevice->next; - matchingBtDevice = currentBtDevice; - } - else { - while(currentBtDevice && currentBtDevice->next != btDeviceDataToRemove) - currentBtDevice = currentBtDevice->next; - - if(currentBtDevice) { - matchingBtDevice = currentBtDevice->next; - currentBtDevice->next = currentBtDevice->next->next; - } - else { - return -2; - } - } - - free(matchingBtDevice->hci); - free(matchingBtDevice->uid); - free(matchingBtDevice->name); - free(matchingBtDevice->address); - - free(matchingBtDevice); - - return 0; -} - -struct HalBtDeviceData *HalBtDataAddBtDeviceToBtDeviceList(struct HalBtDeviceData **firstBtDeviceData, json_object *currentSingleBtDeviceDataJ) -{ - char *currentBtDeviceName, *currentBtDeviceAddress, *currentBtDevicePath, *currentBtDeviceHci; - - struct HalBtDeviceData *currentBtDeviceData; - - if(! firstBtDeviceData || ! currentSingleBtDeviceDataJ) - return NULL; - - currentBtDeviceData = *firstBtDeviceData; - - if(! currentBtDeviceData) { - currentBtDeviceData = (struct HalBtDeviceData *) calloc(1, sizeof(struct HalBtDeviceData)); - if(! currentBtDeviceData) - return NULL; - - *firstBtDeviceData = currentBtDeviceData; - } - else { - while(currentBtDeviceData->next) - currentBtDeviceData = currentBtDeviceData->next; - - currentBtDeviceData->next = calloc(1, sizeof(struct HalBtDeviceData)); - if(! currentBtDeviceData) - return NULL; - - currentBtDeviceData = currentBtDeviceData->next; - } - - if(wrap_json_unpack(currentSingleBtDeviceDataJ, - "{s:s s:s s:s}", - "Name", ¤tBtDeviceName, - "Address", ¤tBtDeviceAddress, - "Path", ¤tBtDevicePath)) { - HalBtDataRemoveSelectedBtDeviceFromList(firstBtDeviceData, currentBtDeviceData); - return NULL; - } - - if(asprintf(¤tBtDeviceData->uid, "BT#%s", currentBtDeviceAddress) == -1) { - HalBtDataRemoveSelectedBtDeviceFromList(firstBtDeviceData, currentBtDeviceData); - return NULL; - } - - currentBtDeviceHci = strtok(currentBtDevicePath, "/"); - while(currentBtDeviceHci && strncmp(currentBtDeviceHci, "hci", 3)) - currentBtDeviceHci = strtok(NULL, "/"); - - if((! currentBtDeviceHci) || (! (currentBtDeviceData->hci = strdup(currentBtDeviceHci)))) { - HalBtDataRemoveSelectedBtDeviceFromList(firstBtDeviceData, currentBtDeviceData); - return NULL; - } - - if(! (currentBtDeviceData->name = strdup(currentBtDeviceName))) { - HalBtDataRemoveSelectedBtDeviceFromList(firstBtDeviceData, currentBtDeviceData); - return NULL; - } - - if(! (currentBtDeviceData->address = strdup(currentBtDeviceAddress))) { - HalBtDataRemoveSelectedBtDeviceFromList(firstBtDeviceData, currentBtDeviceData); - return NULL; - } - - return currentBtDeviceData; -} - -int HalBtDataGetNumberOfBtDeviceInList(struct HalBtDeviceData **firstBtDeviceData) -{ - unsigned int btDeviceNb = 0; - - struct HalBtDeviceData *currentBtDeviceData; - - if(! firstBtDeviceData) - return -1; - - currentBtDeviceData = *firstBtDeviceData; - - while(currentBtDeviceData) { - currentBtDeviceData = currentBtDeviceData->next; - btDeviceNb++; - } - - return btDeviceNb; -} - -struct HalBtDeviceData *HalBtDataSearchBtDeviceByAddress(struct HalBtDeviceData **firstBtDeviceData, char *btAddress) -{ - struct HalBtDeviceData *currentBtDevice; - - if(! firstBtDeviceData || ! btAddress) - return NULL; - - currentBtDevice = *firstBtDeviceData; - - while(currentBtDevice) { - if(! strcmp(btAddress, currentBtDevice->address)) - return currentBtDevice; - - currentBtDevice = currentBtDevice->next; - } - - return NULL; -} - -int HalBtDataHandleReceivedSingleBtDeviceData(struct HalBtPluginData *halBtPluginData, json_object *currentSingleBtDeviceDataJ) -{ - int btProfilesCount; - - unsigned int idx = 0, currentBtDeviceIsConnected, currentBtDeviceIsA2DP; - - char *currentBtDeviceAddress, *currentBtDeviceIsConnectedString, *currentBtDeviceIsAVPConnectedString; - - json_object *currentBtAllProfilesJ = NULL, *currentBtCurrentProfileJ; - - struct HalBtDeviceData *currentBtDevice; - if(! halBtPluginData || ! currentSingleBtDeviceDataJ) - return -1; - - if(wrap_json_unpack(currentSingleBtDeviceDataJ, - "{s:s s:s s:s s?:o}", - "Address", ¤tBtDeviceAddress, - "Connected", ¤tBtDeviceIsConnectedString, - "AVPConnected", ¤tBtDeviceIsAVPConnectedString, - "UUIDs", ¤tBtAllProfilesJ)) { - return -2; - } - - if(! currentBtAllProfilesJ) { - AFB_ApiInfo(halBtPluginData->currentHalApiHandle, "Bluetooth device (address = %s) has no specified profiles, ignore it", currentBtDeviceAddress); - return 0; - } - - if(json_object_is_type(currentBtAllProfilesJ, json_type_array)) { - btProfilesCount = json_object_array_length(currentBtAllProfilesJ); - - while(idx < btProfilesCount) { - currentBtCurrentProfileJ = json_object_array_get_idx(currentBtAllProfilesJ, idx); - - if(json_object_is_type(currentBtCurrentProfileJ, json_type_string) && - ! strncasecmp(json_object_get_string(currentBtCurrentProfileJ), A2DP_AUDIOSOURCE_UUID, sizeof(A2DP_AUDIOSOURCE_UUID))) { - currentBtDeviceIsA2DP = 1; - break; - } - - idx++; - } - } - else if(json_object_is_type(currentBtAllProfilesJ, json_type_string) && - ! strncasecmp(json_object_get_string(currentBtAllProfilesJ), A2DP_AUDIOSOURCE_UUID, sizeof(A2DP_AUDIOSOURCE_UUID))) { - currentBtDeviceIsA2DP = 1; - } - - if(! currentBtDeviceIsA2DP) - return 0; - - currentBtDeviceIsConnected = ((! strncmp(currentBtDeviceIsConnectedString, "True", strlen(currentBtDeviceIsConnectedString))) && - (! strncmp(currentBtDeviceIsAVPConnectedString, "True", strlen(currentBtDeviceIsAVPConnectedString)))); - - currentBtDevice = HalBtDataSearchBtDeviceByAddress(&halBtPluginData->first, currentBtDeviceAddress); - - if(currentBtDevice && ! currentBtDeviceIsConnected) { - if(HalBtDataRemoveSelectedBtDeviceFromList(&halBtPluginData->first, currentBtDevice)) - return -3; - - AFB_ApiInfo(halBtPluginData->currentHalApiHandle, "Bluetooth device (address = %s) successfully removed from list", currentBtDeviceAddress); - - if(halBtPluginData->selectedBtDevice == currentBtDevice) { - halBtPluginData->selectedBtDevice = halBtPluginData->first; - AFB_ApiDebug(halBtPluginData->currentHalApiHandle, - "Bluetooth selected device changed to '%s'", - halBtPluginData->selectedBtDevice ? halBtPluginData->selectedBtDevice->address : "none"); - } - } - else if(! currentBtDevice && currentBtDeviceIsConnected) { - if(! HalBtDataAddBtDeviceToBtDeviceList(&halBtPluginData->first, currentSingleBtDeviceDataJ)) - return -4; - - AFB_ApiInfo(halBtPluginData->currentHalApiHandle, "Bluetooth device (address = %s) successfully added to list", currentBtDeviceAddress); - - if(! halBtPluginData->selectedBtDevice) { - halBtPluginData->selectedBtDevice = halBtPluginData->first; - AFB_ApiDebug(halBtPluginData->currentHalApiHandle, - "Bluetooth selected device changed to '%s'", - halBtPluginData->selectedBtDevice ? halBtPluginData->selectedBtDevice->address : "none"); - } - } - - return 0; -} - -int HalBtDataHandleReceivedMutlipleBtDeviceData(struct HalBtPluginData *halBtPluginData, json_object *currentMultipleBtDeviceDataJ) -{ - int err = 0; - size_t idx, btDeviceNumber; - - if(! halBtPluginData || ! currentMultipleBtDeviceDataJ) - return -1; - - if(! json_object_is_type(currentMultipleBtDeviceDataJ, json_type_array)) - return -2; - - btDeviceNumber = json_object_array_length(currentMultipleBtDeviceDataJ); - - for(idx = 0; idx < btDeviceNumber; idx++) { - if((err = HalBtDataHandleReceivedSingleBtDeviceData(halBtPluginData, json_object_array_get_idx(currentMultipleBtDeviceDataJ, (unsigned int) idx)))) { - return ((int) idx * err * 10); - } - } - - return 0; -} diff --git a/plugins/lib/bluetooth/hal-bt-data.h b/plugins/lib/bluetooth/hal-bt-data.h deleted file mode 100644 index 36e0430..0000000 --- a/plugins/lib/bluetooth/hal-bt-data.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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. - */ - -#ifndef _HAL_BT_DATA_INCLUDE_ -#define _HAL_BT_DATA_INCLUDE_ - -#include <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <json-c/json.h> - -#include <4a-hal-utilities-data.h> - -#define A2DP_AUDIOSOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb" -#define HSP_GATEAWAY_UUID "00001112-0000-1000-8000-00805f9b34fb" -#define HFP_GATEAWAY_UUID "0000111f-0000-1000-8000-00805f9b34fb" - -// Structure to store bluetooth device data -struct HalBtDeviceData { - char *uid; - - char *hci; - char *name; - char *address; - - struct HalBtDeviceData *next; -}; - -// Structure to store hal bluetooth plugin data -struct HalBtPluginData { - unsigned int halBtPluginEnabled; - - AFB_ApiT currentHalApiHandle; - - struct SpecificHalData *currentHalData; - - unsigned int btStreamEnabled; - - struct HalBtDeviceData *selectedBtDevice; - struct HalBtDeviceData *first; -}; - -// Exported verbs for 'struct HalBtDeviceData' list (available in 'struct HalBtPluginData') handling -int HalBtDataGetNumberOfBtDeviceInList(struct HalBtDeviceData **firstBtDeviceData); -struct HalBtDeviceData *HalBtDataSearchBtDeviceByAddress(struct HalBtDeviceData **firstBtDeviceData, char *btAddress); -int HalBtDataHandleReceivedSingleBtDeviceData(struct HalBtPluginData *halBtPluginData, json_object *currentSingleBtDeviceDataJ); -int HalBtDataHandleReceivedMutlipleBtDeviceData(struct HalBtPluginData *halBtPluginData, json_object *currentMultipleBtDeviceDataJ); - -#endif /* _HAL_BT_DATA_INCLUDE_ */
\ No newline at end of file diff --git a/plugins/lib/bluetooth/hal-bt-mixer-link.c b/plugins/lib/bluetooth/hal-bt-mixer-link.c deleted file mode 100644 index abbd1b7..0000000 --- a/plugins/lib/bluetooth/hal-bt-mixer-link.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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 <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <wrap-json.h> - -#include <ctl-config.h> - -#include "hal-bt-mixer-link.h" - -/******************************************************************************* - * HAL controllers handle mixer bt calls functions * - ******************************************************************************/ - -int HalBtMixerLinkSetBtStreamingSettings(AFB_ApiT apiHandle, char *mixerApiName, unsigned int btStreamStatus, char *hci, char *btAddress) -{ - char *returnedError = NULL; - - json_object *toSendJ, *returnedJ; - - if(! apiHandle || ! mixerApiName) - return -1; - - if(! btStreamStatus) { - AFB_ApiDebug(apiHandle, "Will try to disable bluetooth streamed device"); - toSendJ = NULL; - } - else if(btStreamStatus == 1 && hci && btAddress) { - AFB_ApiDebug(apiHandle, "Will try to change bluetooth streamed device to hci='%s' address='%s'", hci, btAddress); - wrap_json_pack(&toSendJ, "{s:s s:s s:s}", "interface", hci, "device", btAddress, "profile", "a2dp"); - } - else { - return -2; - } - - if(AFB_ServiceSync(apiHandle, mixerApiName, MIXER_SET_STREAMED_BT_DEVICE_VERB, toSendJ, &returnedJ)) { - AFB_ApiError(apiHandle, - "Error during call to verb %s of %s api", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - mixerApiName); - return -3; - } - else if(! wrap_json_unpack(returnedJ, "{s:{s:s}}", "request", "info", &returnedError)) { - AFB_ApiError(apiHandle, - "Error '%s' happened during set bluetooth streaming settings during call to verb '%s' of api '%s'", - returnedError ? returnedError : "no_error_string", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - mixerApiName); - json_object_put(returnedJ); - return -4; - } - - json_object_put(returnedJ); - - if(btStreamStatus) - AFB_ApiInfo(apiHandle, "Bluetooth streamed device changed to hci='%s' address='%s'", hci, btAddress); - else - AFB_ApiInfo(apiHandle, "Bluetooth streamed device disbaled"); - - return 0; -}
\ No newline at end of file diff --git a/plugins/lib/bluetooth/hal-bt-mixer-link.h b/plugins/lib/bluetooth/hal-bt-mixer-link.h deleted file mode 100644 index 0550339..0000000 --- a/plugins/lib/bluetooth/hal-bt-mixer-link.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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. - */ - -#ifndef _HAL_BT_MIXER_LINK_INCLUDE_ -#define _HAL_BT_MIXER_LINK_INCLUDE_ - -#include <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <ctl-config.h> - -#define MIXER_SET_STREAMED_BT_DEVICE_VERB "bluezalsa_dev" - -#define MIXER_BT_CAPTURE_JSON_SECTION "\ -{\ - \"uid\": \"bluetooth\",\ - \"pcmplug_params\": \"bluealsa_proxy\",\ - \"source\": {\ - \"channels\": [\ - {\ - \"uid\": \"bluetooth-right\",\ - \"port\": 0\ - },\ - {\ - \"uid\": \"bluetooth-left\",\ - \"port\": 1\ - }\ - ]\ - }\ -}\ -" -#define MIXER_BT_STREAM_JSON_SECTION "\ -{\ - \"uid\": \"bluetooth_stream\",\ - \"verb\": \"bluetooth_stream\",\ - \"source\" : \"bluetooth\",\ - \"volume\": 80,\ - \"mute\": false,\ - \"params\": {\ - \"rate\" : 44100,\ - \"format\": \"S16_LE\",\ - \"channels\": 2\ - }\ -}\ -" - -// HAL controllers handle mixer bt calls functions -int HalBtMixerLinkSetBtStreamingSettings(AFB_ApiT apiHandle, char *mixerApiName, unsigned int btStreamStatus, char *hci, char *btAddress); - -#endif /* _HAL_BT_MIXER_LINK_INCLUDE_ */ diff --git a/plugins/lib/bluetooth/hal-bt.c b/plugins/lib/bluetooth/hal-bt.c deleted file mode 100644 index f270593..0000000 --- a/plugins/lib/bluetooth/hal-bt.c +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Jonathan Aillet <jonathan.aillet@iot.bzh> - * - * 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 <stdio.h> -#include <string.h> -#include <stdbool.h> - -#include <wrap-json.h> - -#include <ctl-config.h> - -#include "hal-bt.h" -#include "hal-bt-cb.h" -#include "hal-bt-data.h" -#include "hal-bt-mixer-link.h" - -CTLP_CAPI_REGISTER(HAL_BT_PLUGIN_NAME) - -// Call at initialisation time -CTLP_ONLOAD(plugin, callbacks) -{ - struct HalBtPluginData *currentBtPluginData; - - AFB_ApiInfo(plugin->api, "%s Plugin Registering: uid='%s' 'info='%s'", HAL_BT_PLUGIN_NAME, plugin->uid, plugin->info); - - currentBtPluginData = calloc(1, sizeof(struct HalBtPluginData)); - if(! currentBtPluginData) { - AFB_ApiError(plugin->api, "An error happenned during allocation of %s plugin data structure ", HAL_BT_PLUGIN_NAME); - return -1; - } - - setPluginContext(plugin, (void *) currentBtPluginData); - - currentBtPluginData->currentHalApiHandle = plugin->api; - - AFB_ApiNotice(plugin->api, "%s Plugin Registered correctly: uid='%s' 'info='%s'", HAL_BT_PLUGIN_NAME, plugin->uid, plugin->info); - - return 0; -} - -CTLP_INIT(plugin, callbacks) -{ - int btChannelsNumber; - - unsigned int idx; - - char *btStreamZone, *returnedInfo; - - CtlConfigT *ctrlConfig; - - struct HalBtPluginData *currentBtPluginData; - - json_object *actionsToAdd, *returnedJ, *halMixerJ, *halOrigCaptureJ, *halNewCaptureJ, *halOrigStreamJ, *halNewStreamJ, *btCaptureJ, *btCaptureParamsJ, *btStreamJ; - - AFB_ApiInfo(plugin->api, "Plugin initialization of %s plugin", HAL_BT_PLUGIN_NAME); - - if(AFB_RequireApi(plugin->api, BT_MANAGER_API, 1)) { - AFB_ApiWarning(plugin->api, "Didn't succeed to require %s api, bluetooth is disable because not reachable", BT_MANAGER_API); - return 0; - } - - if(! (currentBtPluginData = (struct HalBtPluginData *) getPluginContext(plugin))) { - AFB_ApiError(plugin->api, "Can't get current %s plugin data", HAL_BT_PLUGIN_NAME); - return -1; - } - - if(! (ctrlConfig = (CtlConfigT *) AFB_ApiGetUserData(plugin->api))) { - AFB_ApiError(plugin->api, "Can't get current hal controller config"); - return -2; - } - - if(! (currentBtPluginData->currentHalData = getExternalData(ctrlConfig))) { - AFB_ApiError(plugin->api, "Can't get current hal controller data"); - return -3; - } - - if(currentBtPluginData->currentHalData->status != HAL_STATUS_AVAILABLE) { - AFB_ApiWarning(plugin->api, "Controller initialization of %s plugin cannot be done because hal is not ready to be used", HAL_BT_PLUGIN_NAME); - return 0; - } - - if((! currentBtPluginData->currentHalData->ctlHalSpecificData) || - (! (halMixerJ = currentBtPluginData->currentHalData->ctlHalSpecificData->halMixerJ))) { - AFB_ApiError(plugin->api, "Can't get current hal mixer json section"); - return -4; - } - - if(AFB_ServiceSync(plugin->api, BT_MANAGER_API, BT_MANAGER_GET_POWER_INFO, NULL, &returnedJ)) { - if((! wrap_json_unpack(returnedJ, "{s:{s:s}}", "request", "info", &returnedInfo)) && - (! strncmp(returnedInfo, "Unable to get power status", strlen(returnedInfo)))) { - AFB_ApiWarning(plugin->api, - "No bluetooth receiver detected when calling verb '%s' of '%s' api, bluetooth is disable because not reachable", - BT_MANAGER_GET_POWER_INFO, - BT_MANAGER_API); - return 0; - } - else { - AFB_ApiError(plugin->api, - "Error during call to verb '%s' of '%s' api (%s)", - BT_MANAGER_GET_POWER_INFO, - BT_MANAGER_API, - json_object_get_string(returnedJ)); - return -5; - } - } - - if((! json_object_is_type(plugin->paramsJ, json_type_object)) || - (wrap_json_unpack(plugin->paramsJ, "{s:i, s:s}", "channels", &btChannelsNumber, "zone", &btStreamZone))) { - AFB_ApiError(plugin->api, "Can't get %s plugin parameters from json ('%s')", HAL_BT_PLUGIN_NAME, json_object_get_string(plugin->paramsJ)); - return -6; - } - - wrap_json_pack(&actionsToAdd, "{s:s s:s}", - "uid", "Bluetooth-Manager/device_updated", - "action", "plugin://hal-bt#events"); - - idx = 0; - while(ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "events")) - idx++; - - if(! ctrlConfig->sections[idx].key) { - AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new event, 'events' section not found", json_object_get_string(actionsToAdd)); - json_object_put(actionsToAdd); - return -7; - } - - if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) { - AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new event to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].key); - json_object_put(actionsToAdd); - return -8; - } - - wrap_json_pack(&actionsToAdd, "{s:s s:s s:s}", - "uid", "init-bt-plugin", - "info", "Init Bluetooth hal plugin", - "action", "plugin://hal-bt#init"); - - idx = 0; - while(ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "onload")) - idx++; - - if(! ctrlConfig->sections[idx].key) { - AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload, 'onload' section not found", json_object_get_string(actionsToAdd)); - json_object_put(actionsToAdd); - return -9; - } - - if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) { - AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].uid); - json_object_put(actionsToAdd); - return -10; - } - - btCaptureJ = json_tokener_parse(MIXER_BT_CAPTURE_JSON_SECTION); - wrap_json_pack(&btCaptureParamsJ, "{s:i}", "channels", btChannelsNumber); - json_object_object_add(btCaptureJ, "params", btCaptureParamsJ); - - if(! json_object_object_get_ex(halMixerJ, "captures", &halOrigCaptureJ)) { - halNewCaptureJ = json_object_new_array(); - json_object_array_add(halNewCaptureJ, btCaptureJ); - json_object_object_add(halMixerJ, "captures", halNewCaptureJ); - } - else if(json_object_is_type(halOrigCaptureJ, json_type_array)) { - halNewCaptureJ = halOrigCaptureJ; - json_object_array_add(halNewCaptureJ, btCaptureJ); - } - else if(json_object_is_type(halOrigCaptureJ, json_type_object)) { - json_object_get(halOrigCaptureJ); - json_object_object_del(halMixerJ, "captures"); - halNewCaptureJ = json_object_new_array(); - json_object_array_add(halNewCaptureJ, halOrigCaptureJ); - json_object_array_add(halNewCaptureJ, btCaptureJ); - json_object_object_add(halMixerJ, "captures", halNewCaptureJ); - } - else { - AFB_ApiError(plugin->api, "Unrecognized 'halmixer' captures format"); - return -11; - } - - btStreamJ = json_tokener_parse(MIXER_BT_STREAM_JSON_SECTION); - json_object_object_add(btStreamJ, "zone", json_object_new_string(btStreamZone)); - - if(! json_object_object_get_ex(halMixerJ, "streams", &halOrigStreamJ)) { - halNewStreamJ = json_object_new_array(); - json_object_array_add(halNewStreamJ, btStreamJ); - json_object_object_add(halMixerJ, "streams", halNewStreamJ); - } - else if(json_object_is_type(halOrigStreamJ, json_type_array)) { - halNewStreamJ = halOrigStreamJ; - json_object_array_add(halNewStreamJ, btStreamJ); - } - else if(json_object_is_type(halOrigStreamJ, json_type_object)) { - json_object_get(halOrigStreamJ); - json_object_object_del(halMixerJ, "streams"); - halNewStreamJ = json_object_new_array(); - json_object_array_add(halNewStreamJ, halOrigStreamJ); - json_object_array_add(halNewStreamJ, btStreamJ); - json_object_object_add(halMixerJ, "streams", halNewStreamJ); - } - else { - AFB_ApiError(plugin->api, "Unrecognized 'halmixer' streams format"); - return -12; - } - - currentBtPluginData->halBtPluginEnabled = 1; - - AFB_ApiNotice(plugin->api, "Plugin initialization of %s plugin correctly done", HAL_BT_PLUGIN_NAME); - - return 0; -} - -// Call at contoller onload time -CTLP_CAPI(init, source, argsJ, queryJ) -{ - int err; - - char *returnedInfo; - - struct HalBtPluginData *currentBtPluginData; - - json_object *toSendJ, *returnedJ, *returnedBtList = NULL; - - if(! (currentBtPluginData = (struct HalBtPluginData *) getPluginContext(source->plugin))) { - AFB_ApiError(source->api, "Can't get current %s plugin data", HAL_BT_PLUGIN_NAME); - return -1; - } - - if(! currentBtPluginData->halBtPluginEnabled) { - AFB_ApiWarning(source->api, "Controller onload initialization of %s plugin cannot be done because bluetooth is not reachable", HAL_BT_PLUGIN_NAME); - return 0; - } - - AFB_ApiInfo(source->api, "Controller onload initialization of %s plugin", HAL_BT_PLUGIN_NAME); - - // Loading hal BT plugin specific verbs - if(AFB_ApiAddVerb(source->api, - HAL_BT_GET_STREAMING_STATUS_VERB, - "Get Bluetooth streaming status", - HalBtGetStreamingStatus, - (void *) currentBtPluginData, - NULL, - 0, - 0)) { - AFB_ApiError(source->api, "Error while creating verb for bluetooth plugin : '%s'", HAL_BT_GET_STREAMING_STATUS_VERB); - return -2; - } - - if(AFB_ApiAddVerb(source->api, - HAL_BT_SET_STREAMING_STATUS_VERB, - "Set Bluetooth streaming status", - HalBtSetStreamingStatus, - (void *) currentBtPluginData, - NULL, - 0, - 0)) { - AFB_ApiError(source->api, "Error while creating verb for bluetooth plugin : '%s'", HAL_BT_SET_STREAMING_STATUS_VERB); - return -3; - } - - if(AFB_ApiAddVerb(source->api, - HAL_BT_GET_CONNECTED_A2DP_DEVICES_VERB, - "Get connected Bluetooth A2DP devices list", - HalBtGetA2DPBluetoothDevices, - (void *) currentBtPluginData, - NULL, - 0, - 0)) { - AFB_ApiError(source->api, "Error while creating verb for bluetooth plugin : '%s'", HAL_BT_GET_CONNECTED_A2DP_DEVICES_VERB); - return -4; - } - - if(AFB_ApiAddVerb(source->api, - HAL_BT_GET_SELECTED_A2DP_DEVICE_VERB, - "Get selected Bluetooth A2DP device", - HalBtGetSelectedA2DPBluetoothDevice, - (void *) currentBtPluginData, - NULL, - 0, - 0)) { - AFB_ApiError(source->api, "Error while creating verb for bluetooth plugin : '%s'", HAL_BT_GET_SELECTED_A2DP_DEVICE_VERB); - return -5; - } - - if(AFB_ApiAddVerb(source->api, - HAL_BT_SET_SELECTED_A2DP_DEVICE_VERB, - "Set selected Bluetooth A2DP device", - HalBtSetSelectedA2DPBluetoothDevice, - (void *) currentBtPluginData, - NULL, - 0, - 0)) { - AFB_ApiError(source->api, "Error while creating verb for bluetooth plugin : '%s'", HAL_BT_SET_SELECTED_A2DP_DEVICE_VERB); - return -6; - } - - // Register to Bluetooth manager - wrap_json_pack(&toSendJ, "{s:s}", "value", BT_MANAGER_DEVICE_UPDATE_EVENT); - if(AFB_ServiceSync(source->api, BT_MANAGER_API, BT_MANAGER_SUBSCRIBE_VERB, toSendJ, &returnedJ)) { - AFB_ApiError(source->api, - "Error during call to verb '%s' of '%s' api (%s)", - BT_MANAGER_SUBSCRIBE_VERB, - BT_MANAGER_API, - json_object_get_string(returnedJ)); - - return -7; - } - else if(! wrap_json_unpack(returnedJ, "{s:{s:s}}", "request", "info", &returnedInfo)) { - AFB_ApiError(source->api, - "Couldn't subscribe to event '%s' during call to verb '%s' of api '%s' (error '%s')", - BT_MANAGER_DEVICE_UPDATE_EVENT, - BT_MANAGER_SUBSCRIBE_VERB, - BT_MANAGER_API, - returnedInfo); - json_object_put(returnedJ); - return -7; - } - json_object_put(returnedJ); - - if(AFB_ServiceSync(source->api, BT_MANAGER_API, BT_MANAGER_GET_DEVICES_VERB, NULL, &returnedJ)) { - if((! wrap_json_unpack(returnedJ, "{s:{s:s}}", "request", "info", &returnedInfo)) && - (! strncmp(returnedInfo, "No find devices", strlen(returnedInfo)))) { - AFB_ApiInfo(source->api, - "No bluetooth devices returned by call to verb '%s' of '%s' api", - BT_MANAGER_GET_DEVICES_VERB, - BT_MANAGER_API); - } - else { - AFB_ApiError(source->api, - "Error during call to verb '%s' of '%s' api (%s)", - BT_MANAGER_GET_DEVICES_VERB, - BT_MANAGER_API, - json_object_get_string(returnedJ)); - - return -8; - } - } - else if(wrap_json_unpack(returnedJ, "{s:{s:o}}", "response", "list", &returnedBtList)) { - AFB_ApiError(source->api, - "Couldn't get bluetooth device list during call to verb '%s' of api '%s'", - BT_MANAGER_GET_DEVICES_VERB, - BT_MANAGER_API); - json_object_put(returnedJ); - return -8; - } - - if((returnedBtList) && (err = HalBtDataHandleReceivedMutlipleBtDeviceData(currentBtPluginData, returnedBtList))) - return (10 * err); - - json_object_put(returnedJ); - - if(currentBtPluginData->selectedBtDevice) { - currentBtPluginData->btStreamEnabled = 1; - - AFB_ApiInfo(source->api, "Useable bluetooth device detected at initialization, will try to use it"); - - if(HalBtMixerLinkSetBtStreamingSettings(source->api, - currentBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName, - currentBtPluginData->btStreamEnabled, - currentBtPluginData->selectedBtDevice->hci, - currentBtPluginData->selectedBtDevice->address)) { - AFB_ApiError(source->api, - "Couldn't set bluetooth streaming settings during call to verb '%s' of api '%s'", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - currentBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName); - return -9; - } - } - - AFB_ApiNotice(source->api, "Controller onload initialization of %s plugin correctly done", HAL_BT_PLUGIN_NAME); - - return 0; -} - -// This receive Hal bluetooth plugin events -CTLP_CAPI(events, source, argsJ, queryJ) -{ - struct HalBtPluginData *currentBtPluginData; - - struct HalBtDeviceData *previouslySelectedBtDevice; - - if(! (currentBtPluginData = (struct HalBtPluginData *) getPluginContext(source->plugin))) { - AFB_ApiError(source->api, "Can't get current %s plugin data", HAL_BT_PLUGIN_NAME); - return -1; - } - - if(! currentBtPluginData->halBtPluginEnabled) { - AFB_ApiWarning(source->api, "Bluetooth event received but cannot be handled because bluetooth is not reachable"); - return 0; - } - - previouslySelectedBtDevice = currentBtPluginData->selectedBtDevice; - - if(HalBtDataHandleReceivedSingleBtDeviceData(currentBtPluginData, queryJ)) { - AFB_ApiError(source->api, "Error while decoding bluetooth event received json (%s)", json_object_get_string(queryJ)); - return -2; - } - - if(currentBtPluginData->selectedBtDevice == previouslySelectedBtDevice) { - AFB_ApiDebug(source->api, "Bluetooth event received but device didn't change"); - return 0; - } - - if(currentBtPluginData->selectedBtDevice) - currentBtPluginData->btStreamEnabled = 1; - else - currentBtPluginData->btStreamEnabled = 0; - - if(HalBtMixerLinkSetBtStreamingSettings(source->api, - currentBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName, - currentBtPluginData->btStreamEnabled, - currentBtPluginData->selectedBtDevice ? currentBtPluginData->selectedBtDevice->hci : NULL, - currentBtPluginData->selectedBtDevice ? currentBtPluginData->selectedBtDevice->address : NULL)) { - AFB_ApiError(source->api, - "Couldn't set bluetooth streaming settings during call to verb '%s' of api '%s'", - MIXER_SET_STREAMED_BT_DEVICE_VERB, - currentBtPluginData->currentHalData->ctlHalSpecificData->mixerApiName); - return -3; - } - - return 0; -}
\ No newline at end of file |