| /* |
| * |
| * Copyright (c) 2020-2021 Project CHIP Authors |
| * |
| * 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. |
| */ |
| |
| /** |
| * @file |
| * Provides an implementation of the BLEManager singleton object |
| * for mbed platforms. |
| */ |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| |
| #include <platform/internal/CHIPDeviceLayerInternal.h> |
| |
| #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE |
| |
| #include <platform/mbed/BLEManagerImpl.h> |
| |
| #include <ble/Ble.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/CommissionableDataProvider.h> |
| #include <platform/internal/BLEManager.h> |
| |
| // Show BLE status with LEDs |
| #define _BLEMGRIMPL_USE_LEDS 0 |
| |
| // mbed-os headers |
| #include "ble/BLE.h" |
| #include "ble/Gap.h" |
| #include "platform/Callback.h" |
| #include "platform/NonCopyable.h" |
| #include "platform/Span.h" |
| |
| #if _BLEMGRIMPL_USE_LEDS |
| #include "drivers/DigitalOut.h" |
| #endif |
| |
| using namespace ::chip; |
| using namespace ::chip::Ble; |
| using namespace ::chip::System; |
| |
| namespace chip { |
| namespace DeviceLayer { |
| namespace Internal { |
| |
| namespace { |
| const UUID ShortUUID_CHIPoBLEService(0xFFF6); |
| // RX = BleLayer::CHIP_BLE_CHAR_1_ID |
| const UUID LongUUID_CHIPoBLEChar_RX("18EE2EF5-263D-4559-959F-4F9C429F9D11"); |
| // TX = BleLayer::CHIP_BLE_CHAR_2_ID |
| const UUID LongUUID_CHIPoBLEChar_TX("18EE2EF5-263D-4559-959F-4F9C429F9D12"); |
| } // namespace |
| |
| #if _BLEMGRIMPL_USE_LEDS |
| #include "drivers/DigitalOut.h" |
| // LED1 -- toggle on every call to ble::BLE::processEvents() |
| // LED2 -- on when ble::BLE::init() callback completes |
| // LED3 -- on when advertising |
| mbed::DigitalOut led1(LED1, 1); |
| mbed::DigitalOut led2(LED2, 1); |
| mbed::DigitalOut led3(LED3, 1); |
| #endif |
| |
| class ConnectionInfo |
| { |
| public: |
| struct ConnStatus |
| { |
| ble::connection_handle_t connHandle; |
| uint16_t attMtuSize; |
| }; |
| |
| ConnectionInfo() |
| { |
| for (size_t i = 0; i < kMaxConnections; i++) |
| { |
| mConnStates[i].connHandle = kInvalidHandle; |
| mConnStates[i].attMtuSize = 0; |
| } |
| } |
| |
| CHIP_ERROR setStatus(ble::connection_handle_t conn_handle, uint16_t mtu_size) |
| { |
| size_t new_i = kMaxConnections; |
| for (size_t i = 0; i < kMaxConnections; i++) |
| { |
| if (mConnStates[i].connHandle == conn_handle) |
| { |
| mConnStates[i].attMtuSize = mtu_size; |
| return CHIP_NO_ERROR; |
| } |
| else if (mConnStates[i].connHandle == kInvalidHandle && i < new_i) |
| { |
| new_i = i; |
| } |
| } |
| // Handle not found, has to be added. |
| if (new_i == kMaxConnections) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| mConnStates[new_i].connHandle = conn_handle; |
| mConnStates[new_i].attMtuSize = mtu_size; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR clearStatus(ble::connection_handle_t conn_handle) |
| { |
| for (size_t i = 0; i < kMaxConnections; i++) |
| { |
| if (mConnStates[i].connHandle == conn_handle) |
| { |
| mConnStates[i].connHandle = kInvalidHandle; |
| mConnStates[i].attMtuSize = 0; |
| return CHIP_NO_ERROR; |
| } |
| } |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| ConnStatus getStatus(ble::connection_handle_t conn_handle) const |
| { |
| for (size_t i = 0; i < kMaxConnections; i++) |
| { |
| if (mConnStates[i].connHandle == conn_handle) |
| { |
| return mConnStates[i]; |
| } |
| } |
| return { kInvalidHandle, 0 }; |
| } |
| |
| uint16_t getMTU(ble::connection_handle_t conn_handle) const { return getStatus(conn_handle).attMtuSize; } |
| |
| private: |
| const size_t kMaxConnections = BLE_LAYER_NUM_BLE_ENDPOINTS; |
| ConnStatus mConnStates[BLE_LAYER_NUM_BLE_ENDPOINTS]; |
| const ble::connection_handle_t kInvalidHandle = 0xf00d; |
| }; |
| |
| static ConnectionInfo sConnectionInfo; |
| |
| #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" |
| class GapEventHandler : private mbed::NonCopyable<GapEventHandler>, public ble::Gap::EventHandler |
| { |
| void onScanRequestReceived(const ble::ScanRequestEvent & event) |
| { |
| // Requires enable action from setScanRequestNotification(true). |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| /* Called when advertising starts. |
| */ |
| void onAdvertisingStart(const ble::AdvertisingStartEvent & event) |
| { |
| #if _BLEMGRIMPL_USE_LEDS |
| led3 = 0; |
| #endif |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| |
| BLEManagerImpl & ble_manager = BLEMgrImpl(); |
| ble_manager.mFlags.Set(ble_manager.kFlag_Advertising); |
| ble_manager.mFlags.Clear(ble_manager.kFlag_AdvertisingRefreshNeeded); |
| |
| // Post a CHIPoBLEAdvertisingChange(Started) event. |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEAdvertisingChange; |
| chip_event.CHIPoBLEAdvertisingChange.Result = kActivity_Started; |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| |
| PlatformMgr().ScheduleWork(ble_manager.DriveBLEState, 0); |
| } |
| |
| /* Called when advertising ends. |
| * |
| * Advertising ends when the process timeout or if it is stopped by the |
| * application or if the local device accepts a connection request. |
| */ |
| void onAdvertisingEnd(const ble::AdvertisingEndEvent & event) |
| { |
| #if _BLEMGRIMPL_USE_LEDS |
| led3 = 1; |
| #endif |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| |
| BLEManagerImpl & ble_manager = BLEMgrImpl(); |
| ble_manager.mFlags.Clear(ble_manager.kFlag_Advertising); |
| |
| // Post a CHIPoBLEAdvertisingChange(Stopped) event. |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEAdvertisingChange; |
| chip_event.CHIPoBLEAdvertisingChange.Result = kActivity_Stopped; |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| |
| if (event.isConnected()) |
| { |
| ble_manager.mFlags.Set(ble_manager.kFlag_AdvertisingRefreshNeeded); |
| ChipLogDetail(DeviceLayer, "Restarting advertising to allow more connections."); |
| } |
| PlatformMgr().ScheduleWork(ble_manager.DriveBLEState, 0); |
| } |
| |
| /* Called when connection attempt ends or an advertising device has been |
| * connected. |
| */ |
| void onConnectionComplete(const ble::ConnectionCompleteEvent & event) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| |
| ble_error_t mbed_err = event.getStatus(); |
| BLEManagerImpl & ble_manager = BLEMgrImpl(); |
| |
| if (mbed_err == BLE_ERROR_NONE) |
| { |
| const ble::address_t & peer_addr = event.getPeerAddress(); |
| ChipLogProgress(DeviceLayer, "BLE connection established with %02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX", peer_addr[5], |
| peer_addr[4], peer_addr[3], peer_addr[2], peer_addr[1], peer_addr[0]); |
| ble_manager.mGAPConns++; |
| CHIP_ERROR err = sConnectionInfo.setStatus(event.getConnectionHandle(), BLE_GATT_MTU_SIZE_DEFAULT); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DeviceLayer, "Unable to store connection status, error: %s ", ErrorStr(err)); |
| } |
| } |
| else |
| { |
| ChipLogError(DeviceLayer, "BLE connection failed, mbed-os error: %d", mbed_err); |
| } |
| ChipLogProgress(DeviceLayer, "Current number of connections: %u/%d", ble_manager.NumConnections(), |
| ble_manager.kMaxConnections); |
| |
| // The connection established event is propagated when the client has subscribed to |
| // the TX characteristic. |
| } |
| |
| void onUpdateConnectionParametersRequest(const ble::UpdateConnectionParametersRequestEvent & event) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| void onConnectionParametersUpdateComplete(const ble::ConnectionParametersUpdateCompleteEvent & event) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| void onReadPhy(ble_error_t status, ble::connection_handle_t connectionHandle, ble::phy_t txPhy, ble::phy_t rxPhy) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| void onPhyUpdateComplete(ble_error_t status, ble::connection_handle_t connectionHandle, ble::phy_t txPhy, ble::phy_t rxPhy) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| /* Called when a connection has been disconnected. |
| */ |
| void onDisconnectionComplete(const ble::DisconnectionCompleteEvent & event) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| |
| const ble::disconnection_reason_t & reason = event.getReason(); |
| BLEManagerImpl & ble_manager = BLEMgrImpl(); |
| |
| if (ble_manager.NumConnections()) |
| { |
| ble_manager.mGAPConns--; |
| } |
| CHIP_ERROR err = sConnectionInfo.clearStatus(event.getConnectionHandle()); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DeviceLayer, "Unable to clear connection status, error: %s ", ErrorStr(err)); |
| } |
| |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEConnectionError; |
| chip_event.CHIPoBLEConnectionError.ConId = event.getConnectionHandle(); |
| switch (reason.value()) |
| { |
| case ble::disconnection_reason_t::REMOTE_USER_TERMINATED_CONNECTION: |
| chip_event.CHIPoBLEConnectionError.Reason = BLE_ERROR_REMOTE_DEVICE_DISCONNECTED; |
| break; |
| case ble::disconnection_reason_t::LOCAL_HOST_TERMINATED_CONNECTION: |
| chip_event.CHIPoBLEConnectionError.Reason = BLE_ERROR_APP_CLOSED_CONNECTION; |
| break; |
| default: |
| chip_event.CHIPoBLEConnectionError.Reason = BLE_ERROR_CHIPOBLE_PROTOCOL_ABORT; |
| break; |
| } |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| |
| ChipLogProgress(DeviceLayer, "BLE connection terminated, mbed-os reason: %d", reason.value()); |
| ChipLogProgress(DeviceLayer, "Current number of connections: %u/%d", ble_manager.NumConnections(), |
| ble_manager.kMaxConnections); |
| |
| // Force a reconfiguration of advertising in case we switched to non-connectable mode when |
| // the BLE connection was established. |
| ble_manager.mFlags.Set(ble_manager.kFlag_AdvertisingRefreshNeeded); |
| PlatformMgr().ScheduleWork(ble_manager.DriveBLEState, 0); |
| } |
| |
| void onDataLengthChange(ble::connection_handle_t connectionHandle, uint16_t txSize, uint16_t rxSize) |
| { |
| ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); |
| } |
| |
| void onPrivacyEnabled() { ChipLogDetail(DeviceLayer, "GAP %s", __FUNCTION__); } |
| }; |
| |
| #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" |
| struct CHIPService : public ble::GattServer::EventHandler |
| { |
| CHIPService() {} |
| CHIPService(const CHIPService &) = delete; |
| CHIPService & operator=(const CHIPService &) = delete; |
| |
| CHIP_ERROR init(ble::BLE & ble_interface) |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s", __FUNCTION__); |
| |
| if (mCHIPoBLEChar_RX != nullptr || mCHIPoBLEChar_TX != nullptr) |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| mCHIPoBLEChar_RX = new GattCharacteristic(LongUUID_CHIPoBLEChar_RX, nullptr, 0, BLE_GATT_MTU_SIZE_DEFAULT, |
| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | |
| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE); |
| |
| mCHIPoBLEChar_TX = new GattCharacteristic(LongUUID_CHIPoBLEChar_TX, nullptr, 0, BLE_GATT_MTU_SIZE_DEFAULT, |
| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); |
| |
| // Setup callback |
| mCHIPoBLEChar_RX->setWriteAuthorizationCallback(this, &CHIPService::onWriteAuth); |
| |
| GattCharacteristic * chipoble_gatt_characteristics[] = { mCHIPoBLEChar_RX, mCHIPoBLEChar_TX }; |
| auto num_characteristics = sizeof chipoble_gatt_characteristics / sizeof chipoble_gatt_characteristics[0]; |
| GattService chipoble_gatt_service(ShortUUID_CHIPoBLEService, chipoble_gatt_characteristics, num_characteristics); |
| |
| auto mbed_err = ble_interface.gattServer().addService(chipoble_gatt_service); |
| if (mbed_err != BLE_ERROR_NONE) |
| { |
| ChipLogError(DeviceLayer, "Unable to add GATT service, mbed-os err: %d", mbed_err); |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| // Store the attribute handles in the class so they are reused in |
| // callbacks to discriminate events. |
| mRxHandle = mCHIPoBLEChar_RX->getValueHandle(); |
| mTxHandle = mCHIPoBLEChar_TX->getValueHandle(); |
| // There is a single descriptor in the characteristic, CCCD is at index 0 |
| mTxCCCDHandle = mCHIPoBLEChar_TX->getDescriptor(0)->getHandle(); |
| ChipLogDetail(DeviceLayer, "char handles: rx=%d, tx=%d, cccd=%d", mRxHandle, mTxHandle, mTxCCCDHandle); |
| |
| ble_interface.gattServer().setEventHandler(this); |
| return CHIP_NO_ERROR; |
| } |
| |
| // Write authorization callback |
| void onWriteAuth(GattWriteAuthCallbackParams * params) |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params->connHandle, params->handle); |
| if (params->handle == mRxHandle) |
| { |
| ChipLogDetail(DeviceLayer, "Received BLE packet on RX"); |
| |
| // Allocate a buffer, copy the data. They will be passed into the event |
| auto buf = System::PacketBufferHandle::NewWithData(params->data, params->len); |
| if (buf.IsNull()) |
| { |
| params->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_REQUEST_REJECTED; |
| ChipLogError(DeviceLayer, "Dropping packet, not enough memory"); |
| return; |
| } |
| |
| params->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; |
| |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEWriteReceived; |
| chip_event.CHIPoBLEWriteReceived.ConId = params->connHandle; |
| chip_event.CHIPoBLEWriteReceived.Data = std::move(buf).UnsafeRelease(); |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| } |
| else |
| { |
| params->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_HANDLE; |
| } |
| } |
| |
| // overrides of GattServerEvent Handler |
| void onAttMtuChange(ble::connection_handle_t connectionHandle, uint16_t attMtuSize) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s", __FUNCTION__); |
| CHIP_ERROR err = sConnectionInfo.setStatus(connectionHandle, attMtuSize); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DeviceLayer, "Unable to store connection status, error: %s ", ErrorStr(err)); |
| } |
| } |
| |
| void onDataSent(const GattDataSentCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.attHandle); |
| |
| // Note: This is applicable to both notification and indication: If a |
| // notification is sent then onDataSent is called as soon as the data |
| // has been pushed into the Bluetooth controller. For indication, onDataSent |
| // is called when the confirmation has been received. |
| onConfirmationReceived(params); |
| } |
| |
| void onDataWritten(const GattWriteCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.handle); |
| } |
| |
| void onDataRead(const GattReadCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.handle); |
| } |
| |
| void onShutdown(const ble::GattServer & server) override { ChipLogDetail(DeviceLayer, "GATT %s", __FUNCTION__); } |
| |
| void onUpdatesEnabled(const GattUpdatesEnabledCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.attHandle); |
| if (params.attHandle == mTxCCCDHandle) |
| { |
| ChipLogDetail(DeviceLayer, "Updates enabled on TX CCCD"); |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLESubscribe; |
| chip_event.CHIPoBLESubscribe.ConId = params.connHandle; |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| } |
| } |
| |
| void onUpdatesDisabled(const GattUpdatesDisabledCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.attHandle); |
| if (params.attHandle == mTxCCCDHandle) |
| { |
| ChipLogDetail(DeviceLayer, "Updates disabled on TX CCCD"); |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEUnsubscribe; |
| chip_event.CHIPoBLEUnsubscribe.ConId = params.connHandle; |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| } |
| } |
| |
| void onConfirmationReceived(const GattConfirmationReceivedCallbackParams & params) override |
| { |
| ChipLogDetail(DeviceLayer, "GATT %s, connHandle=%d, attHandle=%d", __FUNCTION__, params.connHandle, params.attHandle); |
| if (params.attHandle == mTxHandle) |
| { |
| ChipLogDetail(DeviceLayer, "Confirmation received for TX transfer"); |
| ChipDeviceEvent chip_event; |
| chip_event.Type = DeviceEventType::kCHIPoBLEIndicateConfirm; |
| chip_event.CHIPoBLEIndicateConfirm.ConId = params.connHandle; |
| PlatformMgrImpl().PostEventOrDie(&chip_event); |
| } |
| } |
| |
| ble::attribute_handle_t getTxHandle() const { return mTxHandle; } |
| ble::attribute_handle_t getTxCCCDHandle() const { return mTxCCCDHandle; } |
| ble::attribute_handle_t getRxHandle() const { return mRxHandle; } |
| |
| GattCharacteristic * mCHIPoBLEChar_RX = nullptr; |
| GattCharacteristic * mCHIPoBLEChar_TX = nullptr; |
| ble::attribute_handle_t mRxHandle = 0; |
| ble::attribute_handle_t mTxCCCDHandle = 0; |
| ble::attribute_handle_t mTxHandle = 0; |
| }; |
| |
| #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" |
| class SecurityManagerEventHandler : private mbed::NonCopyable<SecurityManagerEventHandler>, |
| public ble::SecurityManager::EventHandler |
| { |
| void pairingRequest(ble::connection_handle_t connectionHandle) override |
| { |
| ChipLogDetail(DeviceLayer, "SM %s, connHandle=%d", __FUNCTION__, connectionHandle); |
| ble::SecurityManager & security_mgr = ble::BLE::Instance().securityManager(); |
| |
| auto mbed_err = security_mgr.acceptPairingRequest(connectionHandle); |
| if (mbed_err == BLE_ERROR_NONE) |
| { |
| ChipLogProgress(DeviceLayer, "Pairing request authorized."); |
| } |
| else |
| { |
| ChipLogError(DeviceLayer, "Pairing request not authorized, mbed-os err: %d", mbed_err); |
| } |
| } |
| |
| void pairingResult(ble::connection_handle_t connectionHandle, SecurityManager::SecurityCompletionStatus_t result) override |
| { |
| ChipLogDetail(DeviceLayer, "SM %s, connHandle=%d", __FUNCTION__, connectionHandle); |
| if (result == SecurityManager::SEC_STATUS_SUCCESS) |
| { |
| ChipLogProgress(DeviceLayer, "Pairing successful."); |
| } |
| else |
| { |
| ChipLogError(DeviceLayer, "Pairing failed, status: 0x%X.", result); |
| } |
| } |
| |
| void linkEncryptionResult(ble::connection_handle_t connectionHandle, ble::link_encryption_t result) override |
| { |
| ChipLogDetail(DeviceLayer, "SM %s, connHandle=%d", __FUNCTION__, connectionHandle); |
| if (result == ble::link_encryption_t::NOT_ENCRYPTED) |
| { |
| ChipLogDetail(DeviceLayer, "Link NOT_ENCRYPTED."); |
| } |
| else if (result == ble::link_encryption_t::ENCRYPTION_IN_PROGRESS) |
| { |
| ChipLogDetail(DeviceLayer, "Link ENCRYPTION_IN_PROGRESS."); |
| } |
| else if (result == ble::link_encryption_t::ENCRYPTED) |
| { |
| ChipLogDetail(DeviceLayer, "Link ENCRYPTED."); |
| } |
| else if (result == ble::link_encryption_t::ENCRYPTED_WITH_MITM) |
| { |
| ChipLogDetail(DeviceLayer, "Link ENCRYPTED_WITH_MITM."); |
| } |
| else if (result == ble::link_encryption_t::ENCRYPTED_WITH_SC_AND_MITM) |
| { |
| ChipLogDetail(DeviceLayer, "Link ENCRYPTED_WITH_SC_AND_MITM."); |
| } |
| else |
| { |
| ChipLogDetail(DeviceLayer, "Link encryption status UNKNOWN."); |
| } |
| } |
| }; |
| |
| BLEManagerImpl BLEManagerImpl::sInstance; |
| static GapEventHandler sMbedGapEventHandler; |
| static CHIPService sCHIPService; |
| static SecurityManagerEventHandler sSecurityManagerEventHandler; |
| |
| /* Initialize the mbed-os BLE subsystem. Register the BLE event processing |
| * callback to the system event queue. Register the BLE initialization complete |
| * callback that handles the rest of the setup commands. Register the BLE GAP |
| * event handler. |
| */ |
| CHIP_ERROR BLEManagerImpl::_Init() |
| { |
| ble_error_t mbed_err = BLE_ERROR_NONE; |
| |
| mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Enabled; |
| mFlags = BitFlags<Flags>(CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART ? kFlag_AdvertisingEnabled : 0); |
| mGAPConns = 0; |
| |
| if (!mInitialized) |
| { |
| ble::BLE & ble_interface = ble::BLE::Instance(); |
| |
| ble_interface.gap().setEventHandler(&sMbedGapEventHandler); |
| ReturnErrorOnFailure(sCHIPService.init(ble_interface)); |
| |
| ble_interface.onEventsToProcess(FunctionPointerWithContext<ble::BLE::OnEventsToProcessCallbackContext *>{ |
| [](ble::BLE::OnEventsToProcessCallbackContext * context) { PlatformMgr().ScheduleWork(DoBLEProcessing, 0); } }); |
| |
| mbed_err = ble_interface.init([](ble::BLE::InitializationCompleteCallbackContext * context) { |
| BLEMgrImpl().HandleInitComplete(context->error == BLE_ERROR_NONE); |
| }); |
| VerifyOrReturnError(mbed_err == BLE_ERROR_NONE, CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| mInitialized = true; |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| /* Process all the events from the mbed-os BLE subsystem. |
| */ |
| void BLEManagerImpl::DoBLEProcessing(intptr_t arg) |
| { |
| #if _BLEMGRIMPL_USE_LEDS |
| led1 = !led1; |
| #endif |
| ble::BLE::Instance().processEvents(); |
| } |
| |
| /* This is the mbed-os BLE subsystem init complete callback. Initialize the |
| * BLELayer and update the state based on the flags. |
| */ |
| void BLEManagerImpl::HandleInitComplete(bool no_error) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ble_error_t mbed_err = BLE_ERROR_NONE; |
| |
| ble::Gap & gap = ble::BLE::Instance().gap(); |
| ble::SecurityManager & security_mgr = ble::BLE::Instance().securityManager(); |
| ble::own_address_type_t addr_type; |
| ble::address_t addr; |
| |
| VerifyOrExit(no_error, err = CHIP_ERROR_INTERNAL); |
| |
| gap.getAddress(addr_type, addr); |
| ChipLogDetail(DeviceLayer, "Device address: %02X:%02X:%02X:%02X:%02X:%02X", addr[5], addr[4], addr[3], addr[2], addr[1], |
| addr[0]); |
| |
| mbed_err = security_mgr.init( |
| /*bool enableBonding */ false, |
| /*bool requireMITM */ true, |
| /*SecurityIOCapabilities_t iocaps*/ SecurityManager::IO_CAPS_NONE, |
| /*const Passkey_t passkey */ nullptr, |
| /*bool signing */ true, |
| /*const char *dbFilepath */ nullptr); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| mbed_err = security_mgr.setPairingRequestAuthorisation(true); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| security_mgr.setSecurityManagerEventHandler(&sSecurityManagerEventHandler); |
| |
| err = BleLayer::Init(this, this, &DeviceLayer::SystemLayer()); |
| SuccessOrExit(err); |
| PlatformMgr().ScheduleWork(DriveBLEState, 0); |
| #if _BLEMGRIMPL_USE_LEDS |
| led2 = 0; |
| #endif |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DeviceLayer, "BLEManager init error: %s ", ErrorStr(err)); |
| ChipLogError(DeviceLayer, "Disabling CHIPoBLE service."); |
| mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; |
| mInitialized = false; |
| PlatformMgr().ScheduleWork(DriveBLEState, 0); |
| } |
| } |
| |
| void BLEManagerImpl::DriveBLEState(intptr_t arg) |
| { |
| BLEMgrImpl().DriveBLEState(); |
| } |
| |
| /* Update the advertising state based on the flags. |
| */ |
| void BLEManagerImpl::DriveBLEState() |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| // Perform any initialization actions that must occur after the CHIP task is running. |
| if (!mFlags.Has(kFlag_AsyncInitCompleted)) |
| { |
| mFlags.Set(kFlag_AsyncInitCompleted); |
| } |
| |
| // If the application has enabled CHIPoBLE and BLE advertising... |
| if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && |
| mFlags.Has(kFlag_AdvertisingEnabled) |
| #if CHIP_DEVICE_CONFIG_CHIPOBLE_SINGLE_CONNECTION |
| // and no connections are active... |
| && (_NumConnections() == 0) |
| #endif |
| ) |
| { |
| // Start/re-start advertising if not already advertising, or if the |
| // advertising state needs to be refreshed. |
| if (!mFlags.Has(kFlag_Advertising) || mFlags.Has(kFlag_AdvertisingRefreshNeeded)) |
| { |
| err = StartAdvertising(); |
| SuccessOrExit(err); |
| } |
| } |
| // Otherwise, stop advertising if currently active. |
| else |
| { |
| err = StopAdvertising(); |
| SuccessOrExit(err); |
| } |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); |
| mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; |
| } |
| } |
| |
| CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) |
| { |
| switch (mode) |
| { |
| case BLEAdvertisingMode::kFastAdvertising: |
| mFlags.Set(Flags::kFlag_FastAdvertisingEnabled, true); |
| break; |
| case BLEAdvertisingMode::kSlowAdvertising: |
| mFlags.Set(Flags::kFlag_AdvertisingEnabled, false); |
| break; |
| default: |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| mFlags.Set(Flags::kFlag_AdvertisingRefreshNeeded); |
| PlatformMgr().ScheduleWork(DriveBLEState, 0); |
| return CHIP_NO_ERROR; |
| } |
| |
| /* Build the advertising data and start advertising. |
| */ |
| CHIP_ERROR BLEManagerImpl::StartAdvertising(void) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ble_error_t mbed_err = BLE_ERROR_NONE; |
| |
| ble::Gap & gap = ble::BLE::Instance().gap(); |
| ble::AdvertisingDataBuilder adv_data_builder(mAdvertisingDataBuffer); |
| |
| ChipBLEDeviceIdentificationInfo dev_id_info; |
| |
| // Advertise CONNECTABLE if we haven't reached the maximum number of connections. |
| uint16_t num_conns = _NumConnections(); |
| bool connectable = (num_conns < kMaxConnections); |
| ble::advertising_type_t adv_type = |
| connectable ? ble::advertising_type_t::CONNECTABLE_UNDIRECTED : ble::advertising_type_t::SCANNABLE_UNDIRECTED; |
| |
| // Advertise in fast mode if not fully provisioned and there are no CHIPoBLE connections, or |
| // if the application has expressly requested fast advertising. |
| ble::adv_interval_t adv_interval = (num_conns == 0 && !ConfigurationMgr().IsFullyProvisioned()) |
| ? ble::adv_interval_t(CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX) |
| : ble::adv_interval_t(CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX); |
| // minInterval and maxInterval are equal for CHIP. |
| ble::AdvertisingParameters adv_params(adv_type, adv_interval, adv_interval); |
| |
| // Restart advertising if already active. |
| if (gap.isAdvertisingActive(ble::LEGACY_ADVERTISING_HANDLE)) |
| { |
| mbed_err = gap.stopAdvertising(ble::LEGACY_ADVERTISING_HANDLE); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| ChipLogDetail(DeviceLayer, "Advertising already active. Restarting."); |
| } |
| |
| mbed_err = gap.setAdvertisingParameters(ble::LEGACY_ADVERTISING_HANDLE, adv_params); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| mbed_err = |
| adv_data_builder.setFlags(ble::adv_data_flags_t::BREDR_NOT_SUPPORTED | ble::adv_data_flags_t::LE_GENERAL_DISCOVERABLE); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| if (!mFlags.Has(kFlag_UseCustomDeviceName)) |
| { |
| uint16_t discriminator; |
| SuccessOrExit(err = GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator)); |
| memset(mDeviceName, 0, kMaxDeviceNameLength); |
| snprintf(mDeviceName, kMaxDeviceNameLength, "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator); |
| } |
| mbed_err = adv_data_builder.setName(mDeviceName); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| dev_id_info.Init(); |
| SuccessOrExit(err = ConfigurationMgr().GetBLEDeviceIdentificationInfo(dev_id_info)); |
| mbed_err = adv_data_builder.setServiceData( |
| ShortUUID_CHIPoBLEService, mbed::make_Span<const uint8_t>(reinterpret_cast<uint8_t *>(&dev_id_info), sizeof dev_id_info)); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| mbed_err = gap.setAdvertisingPayload(ble::LEGACY_ADVERTISING_HANDLE, adv_data_builder.getAdvertisingData()); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| adv_data_builder.clear(); |
| adv_data_builder.setLocalServiceList(mbed::make_Span<const UUID>(&ShortUUID_CHIPoBLEService, 1)); |
| mbed_err = gap.setAdvertisingScanResponse(ble::LEGACY_ADVERTISING_HANDLE, adv_data_builder.getAdvertisingData()); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| mbed_err = gap.startAdvertising(ble::LEGACY_ADVERTISING_HANDLE); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| ChipLogDetail(DeviceLayer, "Advertising started, type: 0x%x (%sconnectable), interval: [%lu:%lu] ms, device name: %s)", |
| adv_params.getType().value(), connectable ? "" : "non-", adv_params.getMinPrimaryInterval().valueInMs(), |
| adv_params.getMaxPrimaryInterval().valueInMs(), mDeviceName); |
| |
| exit: |
| if (mbed_err != BLE_ERROR_NONE) |
| { |
| ChipLogError(DeviceLayer, "StartAdvertising mbed-os error: %d", mbed_err); |
| } |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::StopAdvertising(void) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ble_error_t mbed_err = BLE_ERROR_NONE; |
| |
| ble::Gap & gap = ble::BLE::Instance().gap(); |
| |
| if (!gap.isAdvertisingActive(ble::LEGACY_ADVERTISING_HANDLE)) |
| { |
| ChipLogDetail(DeviceLayer, "No need to stop. Advertising inactive."); |
| return err; |
| } |
| mbed_err = gap.stopAdvertising(ble::LEGACY_ADVERTISING_HANDLE); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| exit: |
| if (mbed_err != BLE_ERROR_NONE) |
| { |
| ChipLogError(DeviceLayer, "StopAdvertising mbed-os error: %d", mbed_err); |
| } |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(mServiceMode != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (mFlags.Has(kFlag_AdvertisingEnabled) != val) |
| { |
| ChipLogDetail(DeviceLayer, "SetAdvertisingEnabled(%s)", val ? "true" : "false"); |
| |
| mFlags.Set(kFlag_AdvertisingEnabled, val); |
| PlatformMgr().ScheduleWork(DriveBLEState, 0); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::_SetFastAdvertisingEnabled(bool val) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (mFlags.Has(kFlag_FastAdvertisingEnabled) != val) |
| { |
| ChipLogDetail(DeviceLayer, "SetFastAdvertisingEnabled(%s)", val ? "true" : "false"); |
| |
| mFlags.Set(kFlag_FastAdvertisingEnabled, val); |
| PlatformMgr().ScheduleWork(DriveBLEState, 0); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::_GetDeviceName(char * buf, size_t bufSize) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(strlen(mDeviceName) < bufSize, err = CHIP_ERROR_BUFFER_TOO_SMALL); |
| strcpy(buf, mDeviceName); |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::_SetDeviceName(const char * deviceName) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(mServiceMode != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (deviceName != nullptr && deviceName[0] != '\0') |
| { |
| VerifyOrExit(strlen(deviceName) < kMaxDeviceNameLength, err = CHIP_ERROR_INVALID_ARGUMENT); |
| strcpy(mDeviceName, deviceName); |
| mFlags.Set(kFlag_UseCustomDeviceName); |
| ChipLogDetail(DeviceLayer, "Device name set to: %s", deviceName); |
| } |
| else |
| { |
| mDeviceName[0] = '\0'; |
| mFlags.Clear(kFlag_UseCustomDeviceName); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| uint16_t BLEManagerImpl::_NumConnections(void) |
| { |
| return mGAPConns; |
| } |
| |
| void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) |
| { |
| switch (event->Type) |
| { |
| case DeviceEventType::kCHIPoBLESubscribe: { |
| ChipDeviceEvent connEstEvent; |
| |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent kCHIPoBLESubscribe"); |
| HandleSubscribeReceived(event->CHIPoBLESubscribe.ConId, &CHIP_BLE_SVC_ID, &Ble::CHIP_BLE_CHAR_2_UUID); |
| connEstEvent.Type = DeviceEventType::kCHIPoBLEConnectionEstablished; |
| PlatformMgrImpl().PostEventOrDie(&connEstEvent); |
| } |
| break; |
| |
| case DeviceEventType::kCHIPoBLEUnsubscribe: { |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent kCHIPoBLEUnsubscribe"); |
| HandleUnsubscribeReceived(event->CHIPoBLEUnsubscribe.ConId, &CHIP_BLE_SVC_ID, &Ble::CHIP_BLE_CHAR_2_UUID); |
| } |
| break; |
| |
| case DeviceEventType::kCHIPoBLEWriteReceived: { |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent kCHIPoBLEWriteReceived"); |
| HandleWriteReceived(event->CHIPoBLEWriteReceived.ConId, &CHIP_BLE_SVC_ID, &Ble::CHIP_BLE_CHAR_1_UUID, |
| PacketBufferHandle::Adopt(event->CHIPoBLEWriteReceived.Data)); |
| } |
| break; |
| |
| case DeviceEventType::kCHIPoBLEConnectionError: { |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent kCHIPoBLEConnectionError"); |
| HandleConnectionError(event->CHIPoBLEConnectionError.ConId, event->CHIPoBLEConnectionError.Reason); |
| } |
| break; |
| |
| case DeviceEventType::kCHIPoBLEIndicateConfirm: { |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent kCHIPoBLEIndicateConfirm"); |
| HandleIndicationConfirmation(event->CHIPoBLEIndicateConfirm.ConId, &CHIP_BLE_SVC_ID, &Ble::CHIP_BLE_CHAR_2_UUID); |
| } |
| break; |
| |
| default: |
| ChipLogDetail(DeviceLayer, "_OnPlatformEvent default: event->Type = 0x%x", event->Type); |
| break; |
| } |
| } |
| |
| void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) |
| { |
| // no-op |
| } |
| |
| CHIP_ERROR BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, |
| const ChipBleUUID * charId) |
| { |
| ChipLogError(DeviceLayer, "%s: NOT IMPLEMENTED", __PRETTY_FUNCTION__); |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, |
| const ChipBleUUID * charId) |
| { |
| ChipLogError(DeviceLayer, "%s: NOT IMPLEMENTED", __PRETTY_FUNCTION__); |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId) |
| { |
| ChipLogProgress(DeviceLayer, "Closing BLE GATT connection, connHandle=%d", conId); |
| |
| ble::Gap & gap = ble::BLE::Instance().gap(); |
| |
| ble_error_t mbed_err = gap.disconnect(conId, ble::local_disconnection_reason_t::USER_TERMINATION); |
| VerifyOrReturnError(mbed_err == BLE_ERROR_NONE, CHIP_ERROR_INTERNAL); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const |
| { |
| return sConnectionInfo.getMTU(conId); |
| } |
| |
| CHIP_ERROR BLEManagerImpl::SendIndication(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId, |
| PacketBufferHandle pBuf) |
| { |
| ChipLogDetail(DeviceLayer, "BlePlatformDelegate %s", __FUNCTION__); |
| |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ble_error_t mbed_err = BLE_ERROR_NONE; |
| |
| ble::GattServer & gatt_server = ble::BLE::Instance().gattServer(); |
| ble::attribute_handle_t att_handle; |
| |
| // For BLE, the buffer is capped at UINT16_MAX. |
| VerifyOrExit(CanCastTo<uint16_t>(pBuf->DataLength()), err = CHIP_ERROR_MESSAGE_TOO_LONG); |
| |
| // No need to do anything fancy here. Only 3 handles are used in this impl. |
| if (UUIDsMatch(charId, &Ble::CHIP_BLE_CHAR_2_UUID)) |
| { |
| att_handle = sCHIPService.getTxHandle(); |
| } |
| else if (UUIDsMatch(charId, &Ble::CHIP_BLE_CHAR_1_UUID)) |
| { |
| // TODO does this make sense? |
| att_handle = sCHIPService.getRxHandle(); |
| } |
| else |
| { |
| ChipLogError(DeviceLayer, "Send indication failed, invalid charID."); |
| return BLE_ERROR_GATT_INDICATE_FAILED; |
| } |
| |
| ChipLogDetail(DeviceLayer, |
| "Sending indication for CHIPoBLE characteristic " |
| "(connHandle=%d, attHandle=%d, data_len=%u)", |
| conId, att_handle, pBuf->DataLength()); |
| |
| mbed_err = gatt_server.write(att_handle, pBuf->Start(), static_cast<uint16_t>(pBuf->DataLength()), false); |
| VerifyOrExit(mbed_err == BLE_ERROR_NONE, err = CHIP_ERROR(chip::ChipError::Range::kOS, mbed_err)); |
| |
| exit: |
| if (mbed_err != BLE_ERROR_NONE) |
| { |
| ChipLogError(DeviceLayer, "Send indication failed, mbed-os error: %d", mbed_err); |
| } |
| return err; |
| } |
| |
| CHIP_ERROR BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId, |
| PacketBufferHandle pBuf) |
| { |
| ChipLogError(DeviceLayer, "%s: NOT IMPLEMENTED", __PRETTY_FUNCTION__); |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| } // namespace Internal |
| } // namespace DeviceLayer |
| } // namespace chip |
| |
| #endif // CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE |