diff options
-rw-r--r-- | .gitlab/issue_templates/mytemplate.md | 3 | ||||
-rw-r--r-- | .gitlab/merge_request_templates/mytemplate.md | 3 | ||||
-rw-r--r-- | README.md | 12 | ||||
-rwxr-xr-x | autobuild/agl/autobuild | 131 | ||||
-rwxr-xr-x | autobuild/linux/autobuild | 131 | ||||
-rw-r--r-- | binding/bluetooth-agent.c | 23 | ||||
-rw-r--r-- | binding/bluetooth-api.c | 232 | ||||
-rw-r--r-- | binding/bluetooth-api.h | 2 | ||||
-rw-r--r-- | binding/bluetooth-bluez.c | 16 | ||||
-rw-r--r-- | binding/bluetooth-common.h | 7 | ||||
-rw-r--r-- | binding/bluetooth-conf.c | 64 | ||||
-rw-r--r-- | binding/bluetooth-util.c | 5 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 1 | ||||
-rw-r--r-- | test/afb-test/tests/bluetooth.lua | 24 |
14 files changed, 512 insertions, 142 deletions
diff --git a/.gitlab/issue_templates/mytemplate.md b/.gitlab/issue_templates/mytemplate.md new file mode 100644 index 0000000..25d91d8 --- /dev/null +++ b/.gitlab/issue_templates/mytemplate.md @@ -0,0 +1,3 @@ +**Please use https://gerrit.automotivelinux.org for code contributions.** +See also: https://docs.automotivelinux.org/ chapter "How to contribute". + diff --git a/.gitlab/merge_request_templates/mytemplate.md b/.gitlab/merge_request_templates/mytemplate.md new file mode 100644 index 0000000..25d91d8 --- /dev/null +++ b/.gitlab/merge_request_templates/mytemplate.md @@ -0,0 +1,3 @@ +**Please use https://gerrit.automotivelinux.org for code contributions.** +See also: https://docs.automotivelinux.org/ chapter "How to contribute". + @@ -20,7 +20,7 @@ Bluetooth service uses the respective BlueZ package to connect to bluetooth devi | cancel_pairing | cancel an outgoing pair request | | | confirm_pairing | confirm incoming/outgoing bluetooth pairing pincode | *Request:* {"pincode": 31415} | | remove_device | remove already paired device | *Request:* {"device": "dev_88_0F_10_96_D3_20"} | - +| set_pincode | set pincode as string into database for outgoing pairing request | *Request:* {"pincode": "4321"} | ### managed_objects verb @@ -149,11 +149,11 @@ This verb allows an client to get initial paired devices, and discovered unpaire avrcp_controls verb allow controlling the playback of the defined device -| Name | Description | -|-----------------|----------------------------------------------------------------------------------------------| -| adapter | Name of the adapter (i.e. hci0) | -| device | Must be the name of the device (i.e. dev_88_0F_10_96_D3_20) | -| action | Playback control action to take (e.g Play, Pause, Stop, Next, Previous, FastForward, Rewind) | +| Name | Description | +|-----------------|--------------------------------------------------------------------------------------------------------------| +| adapter | Name of the adapter (optional, i.e. hci0) | +| device | Must be the name of the device (optional, i.e. dev_88_0F_10_96_D3_20) | +| action | Playback control action to take (e.g Play, Pause, Stop, Next, Previous, FastForward, Rewind) | ### connect/disconnect verbs diff --git a/autobuild/agl/autobuild b/autobuild/agl/autobuild index db00c1a..16181b8 100755 --- a/autobuild/agl/autobuild +++ b/autobuild/agl/autobuild @@ -1,5 +1,6 @@ #!/usr/bin/make -f # Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group # Author "Romain Forlot" <romain.forlot@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,65 +16,113 @@ # limitations under the License. THISFILE := $(lastword $(MAKEFILE_LIST)) -BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build) -DEST := ${BUILD_DIR} +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) -.PHONY: all clean distclean configure build package help update +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) -all: help +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all help: @echo "List of targets available:" @echo "" @echo "- all" + @echo "- help" @echo "- clean" @echo "- distclean" @echo "- configure" @echo "- build: compilation, link and prepare files for package into a widget" @echo "- package: output a widget file '*.wgt'" - @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" @echo "" @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" @echo "Don't use your build dir as DEST as wgt file is generated at this location" -update: configure - @cmake --build ${BUILD_DIR} --target autobuild - -clean: - @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean - -distclean: - @rm -rf ${BUILD_DIR} - -configure: - @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} - @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) - -build: configure - @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all - -package: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ fi -package-test: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ fi +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + install: build - @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/autobuild/linux/autobuild b/autobuild/linux/autobuild index db00c1a..16181b8 100755 --- a/autobuild/linux/autobuild +++ b/autobuild/linux/autobuild @@ -1,5 +1,6 @@ #!/usr/bin/make -f # Copyright (C) 2015 - 2018 "IoT.bzh" +# Copyright (C) 2020 Konsulko Group # Author "Romain Forlot" <romain.forlot@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,65 +16,113 @@ # limitations under the License. THISFILE := $(lastword $(MAKEFILE_LIST)) -BUILD_DIR := $(abspath $(dir $(THISFILE))/../../build) -DEST := ${BUILD_DIR} +ROOT_DIR := $(abspath $(dir $(THISFILE))/../..) -.PHONY: all clean distclean configure build package help update +# Build directories +# Note that the debug/test/coverage directories are defined in relation +# to the release directory (BUILD_DIR), this needs to be kept in mind +# if over-riding it and building those widget types, the specific widget +# type variable (e.g. BUILD_DIR_DEBUG) may also need to be specified +# to yield the desired output hierarchy. +BUILD_DIR = $(ROOT_DIR)/build +BUILD_DIR_DEBUG = $(abspath $(BUILD_DIR)/../build-debug) +BUILD_DIR_TEST = $(abspath $(BUILD_DIR)/../build-test) +BUILD_DIR_COVERAGE = $(abspath $(BUILD_DIR)/../build-coverage) -all: help +# Output directory variable for use in pattern rules. +# This is intended for internal use only, hence the explicit override +# definition. +override OUTPUT_DIR = $(BUILD_DIR) + +# Final install directory for widgets +DEST = $(OUTPUT_DIR) + +# Default build type for release/test builds +BUILD_TYPE = RELEASE + +.PHONY: all help update install distclean +.PHONY: clean clean-release clean-debug clean-test clean-coverage clean-all +.PHONY: configure configure-release configure-debug configure-test configure-coverage +.PHONY: build build-release build-debug build-test build-coverage build-all +.PHONY: package package-release package-debug package-test package-coverage package-all help: @echo "List of targets available:" @echo "" @echo "- all" + @echo "- help" @echo "- clean" @echo "- distclean" @echo "- configure" @echo "- build: compilation, link and prepare files for package into a widget" @echo "- package: output a widget file '*.wgt'" - @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "- install: install in your $(CMAKE_INSTALL_DIR) directory" @echo "" @echo "Usage: ./autobuild/agl/autobuild package DEST=${HOME}/opt" @echo "Don't use your build dir as DEST as wgt file is generated at this location" -update: configure - @cmake --build ${BUILD_DIR} --target autobuild - -clean: - @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} ${CLEAN_ARGS} clean) || echo Nothing to clean - -distclean: - @rm -rf ${BUILD_DIR} - -configure: - @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} - @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) - -build: configure - @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all - -package: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +all: package-all + +# Target specific variable over-rides so static pattern rules can be +# used for the various type-specific targets. + +configure-test build-test package-test clean-test: OUTPUT_DIR = $(BUILD_DIR_TEST) + +configure-coverage build-coverage package-coverage clean-coverage: OUTPUT_DIR = $(BUILD_DIR_COVERAGE) +configure-coverage build-coverage package-coverage: BUILD_TYPE = COVERAGE + +configure-debug build-debug package-debug clean-debug: OUTPUT_DIR = $(BUILD_DIR_DEBUG) +configure-debug build-debug package-debug: BUILD_TYPE = DEBUG + +clean-release clean-test clean-debug clean-coverage: + @if [ -d $(OUTPUT_DIR) ]; then \ + $(MAKE) -C $(OUTPUT_DIR) $(CLEAN_ARGS) clean; \ + else \ + echo Nothing to clean; \ fi -package-test: build - @mkdir -p ${BUILD_DIR}/$@/bin - @mkdir -p ${BUILD_DIR}/$@/etc - @mkdir -p ${BUILD_DIR}/$@/lib - @mkdir -p ${BUILD_DIR}/$@/htdocs - @mkdir -p ${BUILD_DIR}/$@/var - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target widget - @cmake --build ${BUILD_DIR} ${PACKAGE_ARGS} --target test_widget - @if [ "${DEST}" != "${BUILD_DIR}" ]; then \ - mkdir -p ${DEST} && cp ${BUILD_DIR}/*.wgt ${DEST}; \ +clean: clean-release + +clean-all: clean-release clean-test clean-debug clean-coverage + +distclean: clean-all + +configure-release configure-test configure-debug configure-coverage: + @mkdir -p $(OUTPUT_DIR) + @if [ ! -f $(OUTPUT_DIR)/Makefile ]; then \ + (cd $(OUTPUT_DIR) && cmake -S $(ROOT_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) $(CONFIGURE_ARGS)); \ fi +configure: configure-release + +build-release build-debug build-coverage: build-%: configure-% + @cmake --build $(OUTPUT_DIR) $(BUILD_ARGS) --target all + +# Kept for consistency, empty to avoid building everything for test widget +build-test: configure-test + +build: build-release + +build-all: build-release build-debug build-test build-coverage + +package-release package-debug package-coverage: package-%: build-% + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package-test: build-test + @cmake --build $(OUTPUT_DIR) $(PACKAGE_ARGS) --target test_widget + @if [ "$(abspath $(DEST))" != "$(abspath $(OUTPUT_DIR))" ]; then \ + mkdir -p $(DEST) && cp $(OUTPUT_DIR)/*.wgt $(DEST); \ + fi + +package: package-release + +package-all: package-release package-test package-coverage package-debug + +update: configure + @cmake --build $(BUILD_DIR) --target autobuild + install: build - @cmake --build ${BUILD_DIR} ${INSTALL_ARGS} --target install + @cmake --build $(BUILD_DIR) $(INSTALL_ARGS) --target install diff --git a/binding/bluetooth-agent.c b/binding/bluetooth-agent.c index a942baa..3783b3f 100644 --- a/binding/bluetooth-agent.c +++ b/binding/bluetooth-agent.c @@ -41,6 +41,10 @@ static const gchar introspection_xml[] = "<node>" " <interface name='org.bluez.Agent1'>" +" <method name='RequestPinCode'>" +" <arg name='device' direction='in' type='o'/>" +" <arg name='pincode' direction='out' type='s'/>" +" </method>" " <method name='RequestConfirmation'>" " <arg name='device' direction='in' type='o'/>" " <arg name='passkey' direction='in' type='u'/>" @@ -69,7 +73,7 @@ static void handle_method_call( GError *error = NULL; json_object *jev = NULL; const gchar *path = NULL; - + char *device = NULL; /* AFB_INFO("sender=%s", sender_name); AFB_INFO("object_path=%s", object_path); AFB_INFO("interface=%s", interface_name); @@ -114,6 +118,23 @@ static void handle_method_call( afb_event_push(ns->agent_event, jev); return; + + } else if (!g_strcmp0(method_name, "RequestPinCode")) { + + g_variant_get(parameters, "(o)", &device); + + call_work_lock(ns); + cw = call_work_lookup_unlocked(ns, BLUEZ_AT_DEVICE, device, "pair_device"); + + if (!cw) + g_dbus_method_invocation_return_dbus_error(invocation, "org.bluez.Error.Rejected", "No connection pending"); + else + g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", cw->agent_data.fixed_pincode)); + + call_work_unlock(ns); + + return; + } else if (!g_strcmp0(method_name, "AuthorizeService")) { /* g_variant_get(parameters, "(os)", &path, &service); diff --git a/binding/bluetooth-api.c b/binding/bluetooth-api.c index faa9e02..5022ba9 100644 --- a/binding/bluetooth-api.c +++ b/binding/bluetooth-api.c @@ -231,11 +231,13 @@ void call_work_destroy_unlocked(struct call_work *cw) /* agent struct data */ g_free(cw->agent_data.device_path); - + cw->agent_data.device_path = NULL; g_free(cw->access_type); g_free(cw->type_arg); g_free(cw->method); g_free(cw->bluez_method); + g_free(cw->agent_data.fixed_pincode); + cw->agent_data.fixed_pincode = NULL; } void call_work_destroy(struct call_work *cw) @@ -300,7 +302,7 @@ static void bluez_devices_signal_callback( jresp = json_object_new_object(); - json_process_path(jresp, path); + json_process_path(jresp, path); jobj = json_object_new_object(); @@ -519,6 +521,47 @@ static void bluez_devices_signal_callback( json_object_put(jresp); } +static int bluetooth_select_init_adapter(struct init_data *id) +{ + struct bluetooth_state *ns =id->ns; + GError *error = NULL; + json_object *jresp, *jobj = NULL; + int ret, i; + size_t count; + + jresp = object_properties(ns, &error); + if (error) { + AFB_INFO("object_properties for adapters error: %s", + error->message); + g_clear_error(&error); + ret = -EIO; + } else { + json_object_object_get_ex(jresp, "adapters", &jobj); + count = json_object_array_length(jobj); + ret = (int)count; + + for (i = 1; (ret-i) >= 0; i++) { + json_object *idx = json_object_array_get_idx(jobj, count-i); + json_object *name = NULL; + const char *adapter; + json_object_object_get_ex(idx, "name", &name); + adapter = json_object_get_string(name); + if (!g_strcmp0(adapter, id->default_adapter)) { + id->ns->adapter = id->default_adapter; + break; + } + if (!(count-i)) { + /* fallback to 1st available adapter */ + id->ns->adapter = g_strdup(adapter); + AFB_WARNING("default adapter %s not found, fell back to: %s", + id->default_adapter, adapter); + } + } + json_object_put(jresp); + } + return ret; +} + static struct bluetooth_state *bluetooth_init(GMainLoop *loop) { struct bluetooth_state *ns; @@ -608,6 +651,10 @@ static gpointer bluetooth_func(gpointer ptr) struct bluetooth_state *ns; GMainLoop *loop; int rc = 0; + unsigned int delay; + unsigned int attempt; + + g_atomic_rc_box_acquire(id); loop = g_main_loop_new(NULL, FALSE); if (!loop) { @@ -615,7 +662,7 @@ static gpointer bluetooth_func(gpointer ptr) goto err_no_loop; } - /* real bluetooth init */ + // Do BlueZ D-Bus related init ns = bluetooth_init(loop); if (!ns) { AFB_ERROR("bluetooth_init() failed"); @@ -629,17 +676,41 @@ static gpointer bluetooth_func(gpointer ptr) goto err_no_agent; } - /* note that we wait for agent registration to signal done */ - afb_api_set_userdata(id->api, ns); - g_main_loop_run(loop); + + // Let main process know initialization is done + signal_init_done(id, 0); + + // Wait for an adapter to appear + rc = 0; + delay = 1; + attempt = 1; + while(rc <= 0) { + rc = bluetooth_select_init_adapter(id); + if (rc != 0) + break; + + // Back off querying rate after the first 60 seconds + if (attempt++ == 60) + delay = 10; + + sleep(delay); + } + + if (rc > 0) { + g_main_loop_run(loop); + } else { + AFB_ERROR("bluetooth_select_init_adapter() failed"); + } g_main_loop_unref(ns->loop); - bluetooth_unregister_agent(ns); + if (ns->agent_path) + bluetooth_unregister_agent(ns); bluetooth_cleanup(ns); afb_api_set_userdata(id->api, NULL); + g_atomic_rc_box_release(id); return NULL; @@ -651,18 +722,24 @@ err_no_ns: err_no_loop: signal_init_done(id, -1); + g_atomic_rc_box_release(id); return NULL; } static int init(afb_api_t api) { - struct init_data init_data, *id = &init_data; + struct init_data *id = NULL; json_object *args = NULL; gint64 end_time; int ret; + gboolean init_done; + + id = g_atomic_rc_box_new0(struct init_data); + if (!id) + return -ENOMEM; + g_atomic_rc_box_acquire(id); - memset(id, 0, sizeof(*id)); id->init_done = FALSE; id->rc = 0; id->api = api; @@ -681,13 +758,21 @@ static int init(afb_api_t api) return ret; } + id->default_adapter = get_default_adapter(id->api); + if (!id->default_adapter) { + id->default_adapter = g_strdup(BLUEZ_DEFAULT_ADAPTER); + ret = set_default_adapter(api, BLUEZ_DEFAULT_ADAPTER); + if (ret) + AFB_WARNING("Request to save default adapter to persistent storage failed "); + } + args = json_object_new_object(); json_object_object_add(args , "technology", json_object_new_string("bluetooth")); afb_api_call_sync(api, "network-manager", "enable_technology", args, NULL, NULL, NULL); global_thread = g_thread_new("agl-service-bluetooth", - bluetooth_func, - id); + bluetooth_func, + id); AFB_INFO("bluetooth-binding waiting for init done"); @@ -698,26 +783,31 @@ static int init(afb_api_t api) if (!g_cond_wait_until(&id->cond, &id->mutex, end_time)) break; } + ret = id->rc; + init_done = id->init_done; g_mutex_unlock(&id->mutex); + g_atomic_rc_box_release(id); - if (!id->init_done) { + if (!init_done) { AFB_ERROR("bluetooth-binding init timeout"); return -1; } - if (id->rc) - AFB_ERROR("bluetooth-binding init thread returned %d", - id->rc); + if (ret) + AFB_ERROR("bluetooth-binding init thread returned %d", ret); else AFB_INFO("bluetooth-binding operational"); - id->ns->default_adapter = get_default_adapter(id->api); - - return id->rc; + return ret; } static void mediaplayer1_send_event(struct bluetooth_state *ns) { + if (!ns->mediaplayer_path) { + AFB_ERROR("NULL mediaplayer_path"); + return; + } + gchar *player = g_strdup(ns->mediaplayer_path); json_object *jresp = mediaplayer_properties(ns, NULL, player); @@ -738,11 +828,16 @@ static void bluetooth_subscribe_unsubscribe(afb_req_t request, gboolean unsub) { struct bluetooth_state *ns = bluetooth_get_userdata(request); - json_object *jresp = json_object_new_object(); + json_object *jresp; const char *value; afb_event_t event; int rc; + if (!ns) { + afb_req_fail(request, "failed", "Cannot process request"); + return; + } + /* if value exists means to set offline mode */ value = afb_req_value(request, "value"); if (!value) { @@ -773,6 +868,7 @@ static void bluetooth_subscribe_unsubscribe(afb_req_t request, return; } + jresp = json_object_new_object(); afb_req_success_f(request, jresp, "Bluetooth %s to event \"%s\"", !unsub ? "subscribed" : "unsubscribed", value); @@ -794,9 +890,20 @@ static void bluetooth_list(afb_req_t request) GError *error = NULL; json_object *jresp; + if (!ns) { + afb_req_fail(request, "failed", "Cannot process request"); + return; + } + jresp = object_properties(ns, &error); + if (error) { + AFB_INFO("object_properties error: %s", + error->message); + g_clear_error(&error); + afb_req_fail(request, "failed", "BlueZ managed objects error"); + } else + afb_req_success(request, jresp, "BlueZ managed objects"); - afb_req_success(request, jresp, "Bluetooth - managed objects"); } static void bluetooth_state(afb_req_t request) @@ -806,7 +913,12 @@ static void bluetooth_state(afb_req_t request) json_object *jresp; const char *adapter = afb_req_value(request, "adapter"); - adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->default_adapter); + if (!ns || (!adapter && !ns->adapter)) { + afb_req_fail(request, "failed", "No adapter"); + return; + } + + adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter); jresp = adapter_properties(ns, &error, adapter); if (!jresp) { @@ -825,7 +937,12 @@ static void bluetooth_adapter(afb_req_t request) const char *adapter = afb_req_value(request, "adapter"); const char *scan, *discoverable, *powered, *filter, *transport; - adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->default_adapter); + if (!ns || (!adapter && !ns->adapter)) { + afb_req_fail(request, "failed", "No adapter"); + return; + } + + adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter); scan = afb_req_value(request, "discovery"); if (scan) { @@ -891,7 +1008,7 @@ static void bluetooth_adapter(afb_req_t request) uuid = json_array_to_strv(jobj); g_variant_builder_add(&builder, "{sv}", "UUIDs", - g_variant_new_strv((const gchar * const *) uuid, -1)); + g_variant_new_strv((const gchar * const *) uuid, -1)); g_strfreev(uuid); } @@ -923,22 +1040,30 @@ static void bluetooth_adapter(afb_req_t request) static void bluetooth_default_adapter(afb_req_t request) { - struct bluetooth_state *ns = bluetooth_get_userdata(request); const char *adapter = afb_req_value(request, "adapter"); json_object *jresp = json_object_new_object(); + afb_api_t api = afb_req_get_api(request); + const char *adapter_default = get_default_adapter(api); + int rc; - call_work_lock(ns); if (adapter) { - set_default_adapter(afb_req_get_api(request), adapter); - - if (ns->default_adapter) - g_free(ns->default_adapter); - ns->default_adapter = g_strdup(adapter); + if (adapter_default && g_strcmp0(adapter_default, adapter)) { + rc = set_default_adapter(api, adapter); + if (rc) { + AFB_DEBUG("Request to save default adapter to persistent storage failed "); + afb_req_fail(request, "failed", "Update default adapter failed"); + json_object_put(jresp); + return; + } + } + json_object_object_add(jresp, "adapter", json_object_new_string(adapter)); + } else if (adapter_default) { + json_object_object_add(jresp, "adapter", json_object_new_string(adapter_default)); + } else { + afb_req_fail(request, "failed", "No default adapter"); + json_object_put(jresp); + return; } - - json_object_object_add(jresp, "adapter", json_object_new_string(ns->default_adapter)); - call_work_unlock(ns); - afb_req_success(request, jresp, "Bluetooth - default adapter"); } @@ -1111,6 +1236,8 @@ static void bluetooth_pair_device(afb_req_t request) cw->request = request; afb_req_addref(request); + cw->agent_data.fixed_pincode = get_pincode(afb_req_get_api(request)); + cw->cpw = bluez_call_async(ns, BLUEZ_AT_DEVICE, device, "Pair", NULL, &error, pair_service_callback, cw); @@ -1171,10 +1298,10 @@ static void bluetooth_confirm_pairing(afb_req_t request) const char *value = afb_req_value(request, "pincode"); if (value) - pin = (int)strtol(value, NULL, 10); + pin = (int)strtol(value, NULL, 10); if (!value || !pin) { - afb_req_fail_f(request, "failed", "No pincode parameter"); + afb_req_fail(request, "failed", "No pincode parameter"); return; } @@ -1259,9 +1386,10 @@ static void bluetooth_avrcp_controls(afb_req_t request) { struct bluetooth_state *ns = bluetooth_get_userdata(request); const char *action = afb_req_value(request, "action"); - gchar *device, *player; + gchar *device, *player = NULL; GVariant *reply; GError *error = NULL; + json_object *jval = NULL; if (!action) { afb_req_fail(request, "failed", "No action given"); @@ -1271,7 +1399,11 @@ static void bluetooth_avrcp_controls(afb_req_t request) device = return_bluez_path(request); if (device) { /* TODO: handle multiple players per device */ - player = g_strconcat(device, "/", BLUEZ_DEFAULT_PLAYER, NULL); + jval = bluez_get_property(ns, BLUEZ_AT_MEDIACONTROL, device, FALSE, "Player", NULL); + if (jval) + player = (gchar *)json_object_get_string(jval); + if (!player) + player = g_strconcat(device, "/", BLUEZ_DEFAULT_PLAYER, NULL); g_free(device); } else { player = g_strdup(ns->mediaplayer_path); @@ -1303,6 +1435,24 @@ out_success: afb_req_success(request, NULL, "Bluetooth - AVRCP controls"); } +static void bluetooth_pincode(afb_req_t request) +{ + char *error = NULL; + const char *pincode; + int rc; + + pincode = afb_req_value(request, "pincode"); + rc = set_pincode(afb_req_get_api(request), pincode, &error); + + if (rc) { + afb_req_fail(request, "failed", error); + return; + } + + afb_req_success(request, NULL, "Bluetooth - pincode"); + return; +} + static const afb_verb_t bluetooth_verbs[] = { { .verb = "subscribe", @@ -1364,7 +1514,11 @@ static const afb_verb_t bluetooth_verbs[] = { .session = AFB_SESSION_NONE, .callback = bluetooth_avrcp_controls, .info = "AVRCP controls" - }, + }, { + .verb = "set_pincode", + .session = AFB_SESSION_NONE, + .callback = bluetooth_pincode, + .info = "Set pincode for outgoing pairing"}, { } /* marker for end of the array */ }; diff --git a/binding/bluetooth-api.h b/binding/bluetooth-api.h index a0b807b..aecbad0 100644 --- a/binding/bluetooth-api.h +++ b/binding/bluetooth-api.h @@ -35,6 +35,7 @@ #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" #define BLUEZ_MEDIAPLAYER_INTERFACE BLUEZ_SERVICE ".MediaPlayer1" #define BLUEZ_MEDIATRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" +#define BLUEZ_MEDIACONTROL_INTERFACE BLUEZ_SERVICE ".MediaControl1" #define BLUEZ_OBJECT_PATH "/" #define BLUEZ_PATH "/org/bluez" @@ -70,6 +71,7 @@ #define BLUEZ_AT_AGENTMANAGER "agent-manager" #define BLUEZ_AT_MEDIAPLAYER "mediaplayer" #define BLUEZ_AT_MEDIATRANSPORT "mediatransport" +#define BLUEZ_AT_MEDIACONTROL "mediacontrol" #define BLUEZ_DEFAULT_ADAPTER "hci0" #define BLUEZ_DEFAULT_PLAYER "player0" diff --git a/binding/bluetooth-bluez.c b/binding/bluetooth-bluez.c index 18194d5..de4bc0f 100644 --- a/binding/bluetooth-bluez.c +++ b/binding/bluetooth-bluez.c @@ -99,6 +99,12 @@ static const struct property_info mediatransport_props[] = { { }, }; +static const struct property_info mediacontrol_props[] = { + { .name = "Connected", .fmt = "b", }, + { .name = "Player", .fmt = "s", }, + { }, +}; + const struct property_info *bluez_get_property_info( const char *access_type, GError **error) { @@ -112,6 +118,8 @@ const struct property_info *bluez_get_property_info( pi = mediaplayer_props; else if (!strcmp(access_type, BLUEZ_AT_MEDIATRANSPORT)) pi = mediatransport_props; + else if (!strcmp(access_type, BLUEZ_AT_MEDIACONTROL)) + pi = mediacontrol_props; else g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, "illegal %s argument", access_type); @@ -357,7 +365,8 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, if (!strcmp(access_type, BLUEZ_AT_DEVICE) || !strcmp(access_type, BLUEZ_AT_MEDIAPLAYER) || !strcmp(access_type, BLUEZ_AT_MEDIATRANSPORT) || - !strcmp(access_type, BLUEZ_AT_ADAPTER)) { + !strcmp(access_type, BLUEZ_AT_ADAPTER) || + !strcmp(access_type, BLUEZ_AT_MEDIACONTROL)) { pi = bluez_get_property_info(access_type, error); if (!pi) @@ -380,6 +389,8 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, interface2 = BLUEZ_MEDIATRANSPORT_INTERFACE; else if (!strcmp(access_type, BLUEZ_AT_ADAPTER)) interface2 = BLUEZ_ADAPTER_INTERFACE; + else if (!strcmp(access_type, BLUEZ_AT_MEDIACONTROL)) + interface2 = BLUEZ_MEDIACONTROL_INTERFACE; else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) interface2 = NULL; else @@ -404,7 +415,8 @@ json_object *bluez_get_properties(struct bluetooth_state *ns, if (!strcmp(access_type, BLUEZ_AT_DEVICE) || !strcmp(access_type, BLUEZ_AT_MEDIAPLAYER) || !strcmp(access_type, BLUEZ_AT_MEDIATRANSPORT) || - !strcmp(access_type, BLUEZ_AT_ADAPTER)) { + !strcmp(access_type, BLUEZ_AT_ADAPTER) || + !strcmp(access_type, BLUEZ_AT_MEDIACONTROL)) { jprop = json_object_new_object(); g_variant_get(reply, "(a{sv})", &array); while (g_variant_iter_loop(array, "{sv}", &key, &var)) { diff --git a/binding/bluetooth-common.h b/binding/bluetooth-common.h index 0e719eb..616e322 100644 --- a/binding/bluetooth-common.h +++ b/binding/bluetooth-common.h @@ -63,7 +63,7 @@ struct bluetooth_state { gchar *mediaplayer_path; /* adapter */ - gchar *default_adapter; + gchar *adapter; }; struct init_data { @@ -71,12 +71,14 @@ struct init_data { GMutex mutex; gboolean init_done; afb_api_t api; + gchar *default_adapter; struct bluetooth_state *ns; /* before setting afb_api_set_userdata() */ int rc; }; struct agent_data { int pin_code; + gchar *fixed_pincode; gchar *device_path; }; @@ -104,6 +106,9 @@ void bluetooth_unregister_agent(struct bluetooth_state *ns); gchar *get_default_adapter(afb_api_t api); int set_default_adapter(afb_api_t api, const char *adapter); +int set_pincode(afb_api_t api, const char *pincode, char **error); +gchar *get_pincode(afb_api_t api); + /* utility methods in bluetooth-util.c */ extern gboolean auto_lowercase_keys; diff --git a/binding/bluetooth-conf.c b/binding/bluetooth-conf.c index d9edf98..a5824bb 100644 --- a/binding/bluetooth-conf.c +++ b/binding/bluetooth-conf.c @@ -38,7 +38,7 @@ gchar *get_default_adapter(afb_api_t api) { json_object *response, *query, *val; - gchar *adapter; + gchar *adapter = NULL; int ret; query = json_object_new_object(); @@ -46,13 +46,14 @@ gchar *get_default_adapter(afb_api_t api) ret = afb_api_call_sync(api, "persistence", "read", query, &response, NULL, NULL); if (ret < 0) - return g_strdup(BLUEZ_DEFAULT_ADAPTER); + goto out; - json_object_object_get_ex(response, "value", &val); - adapter = g_strdup(json_object_get_string(val)); + if (json_object_object_get_ex(response, "value", &val)) + adapter = g_strdup(json_object_get_string(val)); json_object_put(response); - return adapter ? adapter : g_strdup(BLUEZ_DEFAULT_ADAPTER); +out: + return adapter; } int set_default_adapter(afb_api_t api, const char *adapter) @@ -65,7 +66,60 @@ int set_default_adapter(afb_api_t api, const char *adapter) json_object_object_add(query, "value", json_object_new_string(adapter)); ret = afb_api_call_sync(api, "persistence", "update", query, &response, NULL, NULL); + if (ret < 0) { + ret = afb_api_call_sync(api, "persistence", "insert", query, &response, NULL, NULL); + if (ret < 0) + goto out; + } + json_object_put(response); +out: return ret; } + +gchar *get_pincode(afb_api_t api) +{ + json_object *response, *query, *val; + gchar *pincode = NULL; + + query = json_object_new_object(); + json_object_object_add(query, "key", json_object_new_string("pincode")); + + afb_api_call_sync(api, "persistence", "read", query, &response, NULL, NULL); + + if (json_object_object_get_ex(response, "value", &val)) + pincode = g_strdup(json_object_get_string(val)); + if (!pincode) + pincode = g_strdup("1234"); + json_object_put(response); + + return pincode; +} + +int set_pincode(afb_api_t api, const char *pincode, char **error) +{ + json_object *response, *query; + int ret; + gchar *endptr = NULL; + query = json_object_new_object(); + if (strlen(pincode) > 8 || strlen(pincode) < 4) + { + *error = "length of pincode must be between 4 and 8"; + return -1; + } + + g_ascii_strtoll(pincode, &endptr, 10); + if (*endptr) + { + *error = "pincode must be as digits"; + return -1; + } + json_object_object_add(query, "key", json_object_new_string("pincode")); + json_object_object_add(query, "value", json_object_new_string(pincode)); + + ret = afb_api_call_sync(api, "persistence", "update", query, &response, NULL, NULL); + json_object_put(response); + + return ret; +}
\ No newline at end of file diff --git a/binding/bluetooth-util.c b/binding/bluetooth-util.c index 8a50731..a1e15f0 100644 --- a/binding/bluetooth-util.c +++ b/binding/bluetooth-util.c @@ -1047,8 +1047,11 @@ gchar *return_bluez_path(afb_req_t request) { const char *adapter = afb_req_value(request, "adapter"); const char *device, *tmp; + if (!ns || (!adapter && !ns->adapter)) + return NULL; + call_work_lock(ns); - adapter = adapter ? adapter : ns->default_adapter; + adapter = adapter ? adapter : ns->adapter; call_work_unlock(ns); device = afb_req_value(request, "device"); diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in index aa1f563..eba7297 100644 --- a/conf.d/wgt/config.xml.in +++ b/conf.d/wgt/config.xml.in @@ -9,6 +9,7 @@ <feature name="urn:AGL:widget:required-permission"> <param name="urn:AGL:permission::public:hidden" value="required" /> + <param name="urn:AGL:permission::public:bluetooth" value="required" /> <param name="urn:AGL:permission::public:no-htdocs" value="required" /> <param name="urn:AGL:permission::system:run-by-default" value="required" /> <param name="urn:AGL:permission::partner:scope-platform" value="required" /> diff --git a/test/afb-test/tests/bluetooth.lua b/test/afb-test/tests/bluetooth.lua index 7130018..5b6a4a6 100644 --- a/test/afb-test/tests/bluetooth.lua +++ b/test/afb-test/tests/bluetooth.lua @@ -17,10 +17,6 @@ --]] - --- Version Verb test -_AFT.testVerbStatusSuccess('testBtVersionSuccess','Bluetooth-Manager','version', {}) - -- Default Adapter test _AFT.testVerbStatusSuccess('testBtDefaultAdapterSuccess','Bluetooth-Manager','default_adapter', {}) @@ -48,5 +44,23 @@ _AFT.testVerbStatusSuccess('testBtAdpStateSuccess','Bluetooth-Manager','adapter_ -- Cancel pairing test - cancel an ongoing pairing -- _AFT.testVerbStatusSuccess('testBtCancelPairSuccess', 'Bluetooth-Manager', 'cancel_pairing', {}) --- Connect to a paired device +-- Confirm pairing test - confirm incoming/outgoing bluetooth pairing pincode +-- _AFT.testVerbStatusSuccess('testBtConfirmPairSuccess', 'Bluetooth-Manager', 'confirm_pairing', {pincode="31415"}) + +-- Remove device test - remove already paired device +-- _AFT.testVerbStatusSuccess('testBtRemoveDeviceSuccess', 'Bluetooth-Manager', 'remove_device', {device="dev_01_23_45_67_89_0A"}) + +-- Connect to a paired device test - connect to already paired device -- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'connect', {device="dev_01_23_45_67_89_0A"}) + +-- avrcp_controls test -- requied connected to a smart phone +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Play"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Pause"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Stop"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Next"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Previous"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="FastForward"}) +-- _AFT.testVerbStatusSuccess('testBtConnectSuccess', 'Bluetooth-Manager', 'avrcp_controls', {action="Rewind"}) + +-- Disconnect to a connected device test +-- _AFT.testVerbStatusSuccess('testBtDisConnectSuccess', 'Bluetooth-Manager', 'disconnect', {device="dev_01_23_45_67_89_0A"}) |