From c4a0dd5ddd3c4c1f8d350a4b96de2018b531d50a Mon Sep 17 00:00:00 2001 From: Pawel Andruszkiewicz Date: Mon, 4 May 2015 16:13:27 +0200 Subject: [PATCH] [BluetoothLE] Implementation of Bluetooth LE adapter. Class, startScan(), stopScan(). Change-Id: Ib1b701f6c6208cd767d459a1d322d2462d2674a7 Signed-off-by: Pawel Andruszkiewicz --- src/bluetooth/bluetooth.gyp | 2 + src/bluetooth/bluetooth_api.js | 109 ++++++++++++- src/bluetooth/bluetooth_instance.cc | 9 +- src/bluetooth/bluetooth_instance.h | 2 + src/bluetooth/bluetooth_le_adapter.cc | 220 ++++++++++++++++++++++++++ src/bluetooth/bluetooth_le_adapter.h | 62 ++++++++ src/bluetooth/bluetooth_util.cc | 90 ++++++++++- src/bluetooth/bluetooth_util.h | 3 + 8 files changed, 486 insertions(+), 11 deletions(-) create mode 100644 src/bluetooth/bluetooth_le_adapter.cc create mode 100644 src/bluetooth/bluetooth_le_adapter.h diff --git a/src/bluetooth/bluetooth.gyp b/src/bluetooth/bluetooth.gyp index 4ce4d8d6..9bf3975a 100644 --- a/src/bluetooth/bluetooth.gyp +++ b/src/bluetooth/bluetooth.gyp @@ -27,6 +27,8 @@ 'bluetooth_health_profile_handler.h', 'bluetooth_instance.cc', 'bluetooth_instance.h', + 'bluetooth_le_adapter.cc', + 'bluetooth_le_adapter.h', 'bluetooth_service_handler.cc', 'bluetooth_service_handler.h', 'bluetooth_socket.cc', diff --git a/src/bluetooth/bluetooth_api.js b/src/bluetooth/bluetooth_api.js index e8b84ff6..d0d78f70 100644 --- a/src/bluetooth/bluetooth_api.js +++ b/src/bluetooth/bluetooth_api.js @@ -907,24 +907,117 @@ BluetoothHealthChannel.prototype.unsetListener = function() { } }; +var _bleScanListener = (function() { + var kListenerName = 'BluetoothLEScanCallback'; + var successCallback; + var errorCallback; + var listenerRegistered = false; + + function callback(event) { + var d; + + switch (event.action) { + case 'onstarted': + break; + + case 'ondevicefound': + d = new BluetoothLEDevice(event.data); + break; + + case 'onfinished': + d = []; + event.data.forEach(function(data) { + d.push(new BluetoothLEDevice(data)); + }); + + // remove listener + removeListener(); + break; + + case 'onerror': + if (errorCallback) { + errorCallback(native.getErrorObject(event)); + } + return; + + default: + console.log('Unknown mode: ' + event.action); + return; + } + + if (successCallback && successCallback[event.action]) { + successCallback[event.action](d); + } + } + + function addListener(s, e) { + successCallback = s; + errorCallback = e; + + if (!listenerRegistered) { + native.addListener(kListenerName, callback); + listenerRegistered = true; + } + } + + function removeListener() { + if (listenerRegistered) { + native.removeListener(kListenerName, callback); + listenerRegistered = false; + } + + successCallback = undefined; + errorCallback = undefined; + } + + return { + addListener: addListener, + removeListener: removeListener + }; +})(); + //class BluetoothLEAdapter //////////////////////////////////////////////////// var BluetoothLEAdapter = function() { }; BluetoothLEAdapter.prototype.startScan = function() { - console.log('Entered BluetoothLEAdapter.startScan()'); + console.log('Entered BluetoothLEAdapter.startScan()'); - xwalk.utils.checkPrivilegeAccess(Privilege.BLUETOOTH); - //TODO validate - //TODO call c++ layer + xwalk.utils.checkPrivilegeAccess(Privilege.BLUETOOTH); + + var args = AV.validateMethod(arguments, [{ + name: 'successCallback', + type: AV.Types.LISTENER, + values: ['onstarted', 'ondevicefound', 'onfinished'] + }, { + name: 'errorCallback', + type: AV.Types.FUNCTION, + optional: true, + nullable: true + }]); + + var result = native.callSync('BluetoothLEAdapter_startScan', {}); + + if (native.isFailure(result)) { + throw native.getErrorObject(result); + } + + _bleScanListener.addListener(args.successCallback, args.errorCallback); }; BluetoothLEAdapter.prototype.stopScan = function() { - console.log('Entered BluetoothLEAdapter.stopScan()'); + console.log('Entered BluetoothLEAdapter.stopScan()'); - xwalk.utils.checkPrivilegeAccess(Privilege.BLUETOOTH); - //TODO validate - //TODO call c++ layer + xwalk.utils.checkPrivilegeAccess(Privilege.BLUETOOTH); + + // _bleScanListener.removeListener() is going to be called in 'onfinished' handler + + var result = native.callSync('BluetoothLEAdapter_stopScan', {}); + + if (native.isFailure(result)) { + _bleScanListener.removeListener(); + throw native.getErrorObject(result); + } }; BluetoothLEAdapter.prototype.startAdvertise = function() { diff --git a/src/bluetooth/bluetooth_instance.cc b/src/bluetooth/bluetooth_instance.cc index 76593478..a655d382 100644 --- a/src/bluetooth/bluetooth_instance.cc +++ b/src/bluetooth/bluetooth_instance.cc @@ -20,7 +20,8 @@ BluetoothInstance::BluetoothInstance() : bluetooth_health_profile_handler_(*this), bluetooth_health_application_(bluetooth_health_profile_handler_), bluetooth_service_handler_(bluetooth_adapter_), - bluetooth_socket_(bluetooth_adapter_) + bluetooth_socket_(bluetooth_adapter_), + bluetooth_le_adapter_(*this) { LoggerD("Entered"); using std::placeholders::_1; @@ -99,6 +100,12 @@ BluetoothInstance::BluetoothInstance() : REGISTER_SYNC("BluetoothSocket_close", std::bind(&BluetoothSocket::Close, &bluetooth_socket_, _1, _2)); + // BluetoothLEAdapter + REGISTER_SYNC("BluetoothLEAdapter_startScan", + std::bind(&BluetoothLEAdapter::StartScan, &bluetooth_le_adapter_, _1, _2)); + REGISTER_SYNC("BluetoothLEAdapter_stopScan", + std::bind(&BluetoothLEAdapter::StopScan, &bluetooth_le_adapter_, _1, _2)); + #undef REGISTER_ASYNC #undef REGISTER_SYNC } diff --git a/src/bluetooth/bluetooth_instance.h b/src/bluetooth/bluetooth_instance.h index afbac548..fddb3f63 100644 --- a/src/bluetooth/bluetooth_instance.h +++ b/src/bluetooth/bluetooth_instance.h @@ -12,6 +12,7 @@ #include "bluetooth/bluetooth_health_application.h" #include "bluetooth/bluetooth_health_channel.h" #include "bluetooth/bluetooth_health_profile_handler.h" +#include "bluetooth/bluetooth_le_adapter.h" #include "bluetooth/bluetooth_service_handler.h" #include "bluetooth/bluetooth_socket.h" #include "bluetooth/bluetooth_util.h" @@ -40,6 +41,7 @@ class BluetoothInstance: public common::ParsedInstance { BluetoothHealthProfileHandler bluetooth_health_profile_handler_; BluetoothServiceHandler bluetooth_service_handler_; BluetoothSocket bluetooth_socket_; + BluetoothLEAdapter bluetooth_le_adapter_; }; } // namespace bluetooth diff --git a/src/bluetooth/bluetooth_le_adapter.cc b/src/bluetooth/bluetooth_le_adapter.cc new file mode 100644 index 00000000..426e64c1 --- /dev/null +++ b/src/bluetooth/bluetooth_le_adapter.cc @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 "bluetooth/bluetooth_le_adapter.h" + +#include "common/logger.h" + +#include "bluetooth/bluetooth_instance.h" +#include "bluetooth/bluetooth_util.h" + +namespace extension { +namespace bluetooth { + +namespace { + +// TODO: remove this code when BluetoothLEDevice is available +class BluetoothLEDevice { + public: + static common::PlatformResult ToJson( + bt_adapter_le_device_scan_result_info_s* info, + picojson::object* le_device) { + return common::PlatformResult(common::ErrorCode::NO_ERROR); + } +}; +// TODO end + +// utility functions + +bool ToBool(bt_adapter_le_state_e state) { + return (BT_ADAPTER_LE_ENABLED == state) ? true : false; +} + +// constants + +const std::string kAction = "action"; +const std::string kData = "data"; +// scan-related +const std::string kOnScanStarted = "onstarted"; +const std::string kOnScanDeviceFound = "ondevicefound"; +const std::string kOnScanFinished = "onfinished"; +const std::string kOnScanError = "onerror"; +const std::string kScanEvent = "BluetoothLEScanCallback"; + +} // namespace + +using common::ErrorCode; +using common::PlatformResult; +using common::tools::ReportError; +using common::tools::ReportSuccess; + +BluetoothLEAdapter::BluetoothLEAdapter(BluetoothInstance& instance) + : instance_(instance), + enabled_(false), + scanning_(false) { + LoggerD("Entered"); + + bt_adapter_le_state_e le_state = BT_ADAPTER_LE_DISABLED; + + int ret = bt_adapter_le_get_state(&le_state); + + if (BT_ERROR_NONE == ret) { + enabled_ = ToBool(le_state); + + ret = bt_adapter_le_set_state_changed_cb(OnStateChanged, this); + + if (BT_ERROR_NONE == ret) { + if (!enabled_) { + LoggerD("BTLE is not enabled, turning on..."); + // enabled_ is going to be updated by OnStateChanged callback + ret = bt_adapter_le_enable(); + + if (BT_ERROR_NONE != ret) { + LoggerE("Failed to enable BTLE."); + } + } + } else { + LoggerE("Failed to register BTLE state changed listener."); + } + } else { + LoggerE("Failed to obtain current state of BTLE."); + } +} + +BluetoothLEAdapter::~BluetoothLEAdapter() { + bt_adapter_le_unset_state_changed_cb(); + if (scanning_) { + bt_adapter_le_stop_scan(); + } + bt_adapter_le_disable(); +} + +void BluetoothLEAdapter::StartScan(const picojson::value& data, picojson::object& out) { + LoggerD("Entered"); + + int ret = bt_adapter_le_start_scan(OnScanResult, this); + + if (BT_ERROR_NONE != ret) { + if (BT_ERROR_NOW_IN_PROGRESS == ret) { + LoggerE("Scan in progress"); + ReportError(PlatformResult(ErrorCode::INVALID_STATE_ERR, "Scan already in progress"), &out); + } else { + LoggerE("Failed to start scan: %d", ret); + + // other errors are reported asynchronously + picojson::value value = picojson::value(picojson::object()); + picojson::object* data_obj = &value.get(); + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanError))); + ReportError(PlatformResult(ErrorCode::UNKNOWN_ERR, "Failed to start scan"), data_obj); + instance_.FireEvent(kScanEvent, value); + } + } else { + scanning_ = true; + ReportSuccess(out); + } +} + +void BluetoothLEAdapter::StopScan(const picojson::value& data, picojson::object& out) { + LoggerD("Entered"); + + int ret = bt_adapter_le_stop_scan(); + + if (BT_ERROR_NONE != ret && BT_ERROR_NOT_IN_PROGRESS != ret) { + LoggerE("Failed to stop scan: %d", ret); + ReportError(PlatformResult(ErrorCode::UNKNOWN_ERR, "Failed to stop scan"), &out); + } else { + scanning_ = false; + ReportSuccess(out); + } +} + +void BluetoothLEAdapter::StartAdvertise(const picojson::value& data, picojson::object& out) { + LoggerD("Entered"); +} + +void BluetoothLEAdapter::StopAdvertise(const picojson::value& data, picojson::object& out) { + LoggerD("Entered"); +} + +void BluetoothLEAdapter::OnStateChanged(int result, + bt_adapter_le_state_e adapter_le_state, + void* user_data) { + LoggerD("Entered"); + + auto adapter = static_cast(user_data); + + if (!adapter) { + LoggerE("user_data is NULL"); + return; + } + + adapter->enabled_ = ToBool(adapter_le_state); +} + +void BluetoothLEAdapter::OnScanResult( + int result, bt_adapter_le_device_scan_result_info_s* info, + void* user_data) { + LoggerD("Entered, result: %d, info: %p, data: %p", result, info, user_data); + + auto adapter = static_cast(user_data); + + if (!adapter) { + LoggerE("user_data is NULL"); + return; + } + + picojson::value value = picojson::value(picojson::object()); + picojson::object* data_obj = &value.get(); + + if (BT_ERROR_NONE != result) { + LoggerE("Error during scanning: %d", result); + ReportError(util::GetBluetoothError(result, "Error during scanning"), data_obj); + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanError))); + } else { + // TODO: this is probably capi-network-bluetooth error: when scan is stopped info has 0x1 value + if (nullptr == info || reinterpret_cast(0x1) == info) { + // info is empty, so this is start/stop callback + if (!adapter->scanning_) { // scanning has to be stopped by the user, it is not stopped by the platform + LoggerD("Scan finished"); + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanFinished))); + data_obj->insert(std::make_pair(kData, picojson::value(adapter->discovered_devices_))); + } else { + LoggerD("Scan started"); + adapter->discovered_devices_.clear(); + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanStarted))); + } + } else { + // device found + LoggerD("Device found"); + picojson::value data{picojson::object{}}; + const auto& r = BluetoothLEDevice::ToJson(info, &data.get()); + if (r) { + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanDeviceFound))); + data_obj->insert(std::make_pair(kData, data)); + adapter->discovered_devices_.push_back(data); + } else { + LoggerE("Failed to parse Bluetooth LE device"); + ReportError(r, data_obj); + data_obj->insert(std::make_pair(kAction, picojson::value(kOnScanError))); + } + } + } + + adapter->instance_.FireEvent(kScanEvent, value); +} + +} // namespace bluetooth +} // namespace extension diff --git a/src/bluetooth/bluetooth_le_adapter.h b/src/bluetooth/bluetooth_le_adapter.h new file mode 100644 index 00000000..311ef096 --- /dev/null +++ b/src/bluetooth/bluetooth_le_adapter.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 BLUETOOTH_BLUETOOTH_LE_ADAPTER_H_ +#define BLUETOOTH_BLUETOOTH_LE_ADAPTER_H_ + +#include + +#include "common/picojson.h" + +namespace extension { +namespace bluetooth { + +class BluetoothInstance; + +class BluetoothLEAdapter { + public: + explicit BluetoothLEAdapter(BluetoothInstance& instance); + ~BluetoothLEAdapter(); + + void StartScan(const picojson::value& data, picojson::object& out); + void StopScan(const picojson::value& data, picojson::object& out); + + void StartAdvertise(const picojson::value& data, picojson::object& out); + void StopAdvertise(const picojson::value& data, picojson::object& out); + + private: + BluetoothLEAdapter() = delete; + BluetoothLEAdapter(const BluetoothLEAdapter&) = delete; + BluetoothLEAdapter(const BluetoothLEAdapter&&) = delete; + BluetoothLEAdapter& operator=(const BluetoothLEAdapter&) = delete; + BluetoothLEAdapter& operator=(const BluetoothLEAdapter&&) = delete; + + static void OnStateChanged(int result, bt_adapter_le_state_e adapter_le_state, + void* user_data); + static void OnScanResult(int result, + bt_adapter_le_device_scan_result_info_s* info, + void* user_data); + + BluetoothInstance& instance_; + bool enabled_; + bool scanning_; + picojson::array discovered_devices_; +}; + +} // namespace bluetooth +} // namespace extension + +#endif // BLUETOOTH_BLUETOOTH_LE_ADAPTER_H_ diff --git a/src/bluetooth/bluetooth_util.cc b/src/bluetooth/bluetooth_util.cc index 78a1618b..3b3fbdee 100644 --- a/src/bluetooth/bluetooth_util.cc +++ b/src/bluetooth/bluetooth_util.cc @@ -2,14 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "bluetooth_util.h" +#include "bluetooth/bluetooth_util.h" -using namespace common; +#include namespace extension { namespace bluetooth { namespace util { +using common::ErrorCode; +using common::PlatformResult; + namespace { const char* JSON_CALLBACK_ID = "callbackId"; } // namespace @@ -22,6 +25,89 @@ const picojson::object& GetArguments(const picojson::value& data) { return data.get(); } +PlatformResult GetBluetoothError(int error_code, + const std::string& hint) { + common::ErrorCode error = ErrorCode::UNKNOWN_ERR; + + switch (error_code) { + case BT_ERROR_RESOURCE_BUSY: + case BT_ERROR_NOW_IN_PROGRESS: + case BT_ERROR_NOT_ENABLED: + error = ErrorCode::SERVICE_NOT_AVAILABLE_ERR; + break; + + case BT_ERROR_REMOTE_DEVICE_NOT_FOUND: + error = ErrorCode::NOT_FOUND_ERR; + break; + + case BT_ERROR_INVALID_PARAMETER: + error = ErrorCode::INVALID_VALUES_ERR; + break; + + case BT_ERROR_QUOTA_EXCEEDED: + error = ErrorCode::QUOTA_EXCEEDED_ERR; + break; + + default: + error = ErrorCode::UNKNOWN_ERR; + break; + } + + return PlatformResult(error, + hint + " : " + GetBluetoothErrorMessage(error_code)); +} + +std::string GetBluetoothErrorMessage(int error_code) { + switch (error_code) { + case BT_ERROR_CANCELLED: + return "Operation cancelled"; + case BT_ERROR_INVALID_PARAMETER: + return "Invalid parameter"; + case BT_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case BT_ERROR_RESOURCE_BUSY: + return "Bluetooth device is busy"; + case BT_ERROR_TIMED_OUT: + return "Timeout error"; + case BT_ERROR_NOW_IN_PROGRESS: + return "Operation now in progress"; + case BT_ERROR_NOT_SUPPORTED: + return "Not Supported"; + case BT_ERROR_PERMISSION_DENIED: + return "Permission denied"; + case BT_ERROR_QUOTA_EXCEEDED: + return "Quota exceeded"; + case BT_ERROR_NOT_INITIALIZED: + return "Local adapter not initialized"; + case BT_ERROR_NOT_ENABLED: + return "Local adapter not enabled"; + case BT_ERROR_ALREADY_DONE: + return "Operation already done"; + case BT_ERROR_OPERATION_FAILED: + return "Operation failed"; + case BT_ERROR_NOT_IN_PROGRESS: + return "Operation not in progress"; + case BT_ERROR_REMOTE_DEVICE_NOT_BONDED: + return "Remote device not bonded"; + case BT_ERROR_AUTH_REJECTED: + return "Authentication rejected"; + case BT_ERROR_AUTH_FAILED: + return "Authentication failed"; + case BT_ERROR_REMOTE_DEVICE_NOT_FOUND: + return "Remote device not found"; + case BT_ERROR_SERVICE_SEARCH_FAILED: + return "Service search failed"; + case BT_ERROR_REMOTE_DEVICE_NOT_CONNECTED: + return "Remote device is not connected"; + case BT_ERROR_AGAIN: + return "Resource temporarily unavailable"; + case BT_ERROR_SERVICE_NOT_FOUND: + return "Service Not Found"; + default: + return "Unknown Error"; + } +} + } // util } // bluetooth } // extension diff --git a/src/bluetooth/bluetooth_util.h b/src/bluetooth/bluetooth_util.h index 198be842..fb06f625 100644 --- a/src/bluetooth/bluetooth_util.h +++ b/src/bluetooth/bluetooth_util.h @@ -19,6 +19,9 @@ double GetAsyncCallbackHandle(const picojson::value& data); const picojson::object& GetArguments(const picojson::value& data); +common::PlatformResult GetBluetoothError(int error_code, const std::string& hint); +std::string GetBluetoothErrorMessage(int error_code); + } // util } // bluetooth } // extension -- 2.34.1