| // |
| // Copyright (C) 2015 Google, Inc. |
| // |
| // 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 "service/example/heart_rate/heart_rate_server.h" |
| |
| #include <base/bind.h> |
| #include <base/location.h> |
| #include <base/logging.h> |
| #include <base/rand_util.h> |
| |
| #include <bluetooth/low_energy_constants.h> |
| |
| #include "service/example/heart_rate/constants.h" |
| |
| namespace heart_rate { |
| |
| class CLIBluetoothLowEnergyCallback |
| : public ipc::binder::BnBluetoothLowEnergyCallback { |
| public: |
| CLIBluetoothLowEnergyCallback(android::sp<ipc::binder::IBluetooth> bt) |
| : bt_(bt) {} |
| |
| // IBluetoothLowEnergyCallback overrides: |
| void OnConnectionState(int status, int client_id, const char* address, |
| bool connected) override {} |
| void OnMtuChanged(int status, const char *address, int mtu) override {} |
| |
| void OnScanResult(const bluetooth::ScanResult& scan_result) override {} |
| |
| void OnClientRegistered(int status, int client_id){ |
| if (status != bluetooth::BLE_STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to register BLE client, will not start advertising"; |
| return; |
| } |
| |
| LOG(INFO) << "Registered BLE client with ID: " << client_id; |
| |
| /* Advertising data: 16-bit Service UUID: Heart Rate Service */ |
| std::vector<uint8_t> data{0x03, 0x03, 0x0D, 0x18}; |
| base::TimeDelta timeout; |
| |
| bluetooth::AdvertiseSettings settings( |
| bluetooth::AdvertiseSettings::MODE_LOW_POWER, |
| timeout, |
| bluetooth::AdvertiseSettings::TX_POWER_LEVEL_MEDIUM, |
| true); |
| |
| bluetooth::AdvertiseData adv_data(data); |
| adv_data.set_include_device_name(true); |
| adv_data.set_include_tx_power_level(true); |
| |
| bluetooth::AdvertiseData scan_rsp; |
| |
| bt_->GetLowEnergyInterface()-> |
| StartMultiAdvertising(client_id, adv_data, scan_rsp, settings); |
| } |
| |
| void OnMultiAdvertiseCallback(int status, bool is_start, |
| const bluetooth::AdvertiseSettings& /* settings */) { |
| LOG(INFO) << "Advertising" << (is_start?" started":" stopped"); |
| }; |
| |
| private: |
| android::sp<ipc::binder::IBluetooth> bt_; |
| DISALLOW_COPY_AND_ASSIGN(CLIBluetoothLowEnergyCallback); |
| }; |
| |
| |
| HeartRateServer::HeartRateServer( |
| android::sp<ipc::binder::IBluetooth> bluetooth, |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| bool advertise) |
| : simulation_started_(false), |
| bluetooth_(bluetooth), |
| server_if_(-1), |
| hr_notification_count_(0), |
| energy_expended_(0), |
| advertise_(advertise), |
| main_task_runner_(main_task_runner), |
| weak_ptr_factory_(this) { |
| CHECK(bluetooth_.get()); |
| } |
| |
| HeartRateServer::~HeartRateServer() { |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (!gatt_.get() || server_if_ == -1) |
| return; |
| |
| if (!android::IInterface::asBinder(gatt_.get())->isBinderAlive()) |
| return; |
| |
| // Manually unregister ourselves from the daemon. It's good practice to do |
| // this, even though the daemon will automatically unregister us if this |
| // process exits. |
| gatt_->UnregisterServer(server_if_); |
| } |
| |
| bool HeartRateServer::Run(const RunCallback& callback) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| if (pending_run_cb_) { |
| LOG(ERROR) << "Already started"; |
| return false; |
| } |
| |
| // Grab the IBluetoothGattServer binder from the Bluetooth daemon. |
| gatt_ = bluetooth_->GetGattServerInterface(); |
| if (!gatt_.get()) { |
| LOG(ERROR) << "Failed to obtain handle to IBluetoothGattServer interface"; |
| return false; |
| } |
| |
| // Register this instance as a GATT server. If this call succeeds, we will |
| // asynchronously receive a server ID via the OnServerRegistered callback. |
| if (!gatt_->RegisterServer(this)) { |
| LOG(ERROR) << "Failed to register with the server interface"; |
| return false; |
| } |
| |
| pending_run_cb_ = callback; |
| |
| return true; |
| } |
| |
| void HeartRateServer::ScheduleNextMeasurement() { |
| main_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&HeartRateServer::SendHeartRateMeasurement, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromSeconds(1)); |
| } |
| |
| void HeartRateServer::SendHeartRateMeasurement() { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| // Send a notification or indication to all enabled devices. |
| bool found = false; |
| for (const auto& iter : device_ccc_map_) { |
| uint8_t ccc_val = iter.second; |
| |
| if (!ccc_val) |
| continue; |
| |
| found = true; |
| |
| // Don't send a notification if one is already pending for this device. |
| if (pending_notification_map_[iter.first]) |
| continue; |
| |
| std::vector<uint8_t> value; |
| BuildHeartRateMeasurementValue(&value); |
| |
| if (gatt_->SendNotification(server_if_, iter.first, hr_measurement_id_, |
| false, value)) |
| pending_notification_map_[iter.first] = true; |
| } |
| |
| // Still enabled! |
| if (found) { |
| ScheduleNextMeasurement(); |
| return; |
| } |
| |
| // All clients disabled notifications. |
| simulation_started_ = false; |
| |
| // TODO(armansito): We should keep track of closed connections here so that we |
| // don't send notifications to uninterested clients. |
| } |
| |
| void HeartRateServer::BuildHeartRateMeasurementValue( |
| std::vector<uint8_t>* out_value) { |
| CHECK(out_value); // Assert that |out_value| is not nullptr. |
| |
| // Default flags field. Here is what we put in there: |
| // Bit 0: 0 - 8-bit Heart Rate value |
| // Bits 1 & 2: 11 - Sensor contact feature supported and contact detected. |
| uint8_t flags = kHRValueFormat8Bit | kHRSensorContactDetected; |
| |
| // Our demo's heart rate. Pick a value between 90 and 130. |
| uint8_t heart_rate = base::RandInt(90, 130); |
| |
| // On every tenth beat we include the Energy Expended value. |
| bool include_ee = false; |
| if (!(hr_notification_count_ % 10)) { |
| include_ee = true; |
| flags |= kHREnergyExpendedPresent; |
| } |
| |
| hr_notification_count_++; |
| energy_expended_ = std::min(UINT16_MAX, (int)energy_expended_ + 1); |
| |
| // Add all the value bytes. |
| out_value->push_back(flags); |
| out_value->push_back(heart_rate); |
| if (include_ee) { |
| out_value->push_back(energy_expended_); |
| out_value->push_back(energy_expended_ >> 8); |
| } |
| } |
| |
| void HeartRateServer::OnServerRegistered(int status, int server_if) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| if (status != bluetooth::BLE_STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to register GATT server"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| // Registration succeeded. Store our ID, as we need it for GATT server |
| // operations. |
| server_if_ = server_if; |
| |
| LOG(INFO) << "Heart Rate server registered - server_if: " << server_if_; |
| LOG(INFO) << "Populating attributes"; |
| |
| // Start service declaration. |
| std::unique_ptr<bluetooth::GattIdentifier> gatt_id; |
| if (!gatt_->BeginServiceDeclaration(server_if_, true, |
| kHRServiceUUID, |
| &gatt_id)) { |
| LOG(ERROR) << "Failed to begin service declaration"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| hr_service_id_ = *gatt_id; |
| |
| // Add Heart Rate Measurement characteristic. |
| if (!gatt_->AddCharacteristic( |
| server_if_, kHRMeasurementUUID, |
| bluetooth::kCharacteristicPropertyNotify, |
| 0, &gatt_id)) { |
| LOG(ERROR) << "Failed to add heart rate measurement characteristic"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| hr_measurement_id_ = *gatt_id; |
| |
| // Add Client Characteristic Configuration descriptor for the Heart Rate |
| // Measurement characteristic. |
| if (!gatt_->AddDescriptor( |
| server_if_, kCCCDescriptorUUID, |
| bluetooth::kAttributePermissionRead|bluetooth::kAttributePermissionWrite, |
| &gatt_id)) { |
| LOG(ERROR) << "Failed to add CCC descriptor"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| hr_measurement_cccd_id_ = *gatt_id; |
| |
| // Add Body Sensor Location characteristic. |
| if (!gatt_->AddCharacteristic( |
| server_if_, kBodySensorLocationUUID, |
| bluetooth::kCharacteristicPropertyRead, |
| bluetooth::kAttributePermissionRead, |
| &gatt_id)) { |
| LOG(ERROR) << "Failed to add body sensor location characteristic"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| body_sensor_loc_id_ = *gatt_id; |
| |
| // Add Heart Rate Control Point characteristic. |
| if (!gatt_->AddCharacteristic( |
| server_if_, kHRControlPointUUID, |
| bluetooth::kCharacteristicPropertyWrite, |
| bluetooth::kAttributePermissionWrite, |
| &gatt_id)) { |
| LOG(ERROR) << "Failed to add heart rate control point characteristic"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| hr_control_point_id_ = *gatt_id; |
| |
| // End service declaration. We will be notified whether or not this succeeded |
| // via the OnServiceAdded callback. |
| if (!gatt_->EndServiceDeclaration(server_if_)) { |
| LOG(ERROR) << "Failed to end service declaration"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| LOG(INFO) << "Initiated EndServiceDeclaration request"; |
| } |
| |
| void HeartRateServer::OnServiceAdded( |
| int status, |
| const bluetooth::GattIdentifier& service_id) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| if (status != bluetooth::BLE_STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to add Heart Rate service"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| if (service_id != hr_service_id_) { |
| LOG(ERROR) << "Received callback for the wrong service ID"; |
| pending_run_cb_(false); |
| return; |
| } |
| |
| // EndServiceDeclaration succeeded! Our Heart Rate service is now discoverable |
| // over GATT connections. |
| |
| LOG(INFO) << "Heart Rate service added"; |
| pending_run_cb_(true); |
| |
| if (advertise_) { |
| auto ble = bluetooth_->GetLowEnergyInterface(); |
| if (!ble.get()) { |
| LOG(ERROR) << "Failed to obtain handle to IBluetoothLowEnergy interface"; |
| return; |
| } |
| ble->RegisterClient(new CLIBluetoothLowEnergyCallback(bluetooth_)); |
| } |
| |
| } |
| |
| void HeartRateServer::OnCharacteristicReadRequest( |
| const std::string& device_address, |
| int request_id, int offset, bool /* is_long */, |
| const bluetooth::GattIdentifier& characteristic_id) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| // This is where we handle an incoming characteristic read. Only the body |
| // sensor location characteristic is readable. |
| CHECK(characteristic_id == body_sensor_loc_id_); |
| |
| std::vector<uint8_t> value; |
| bluetooth::GATTError error = bluetooth::GATT_ERROR_NONE; |
| if (offset > 1) |
| error = bluetooth::GATT_ERROR_INVALID_OFFSET; |
| else if (offset == 0) |
| value.push_back(kHRBodyLocationFoot); |
| |
| gatt_->SendResponse(server_if_, device_address, request_id, error, |
| offset, value); |
| } |
| |
| void HeartRateServer::OnDescriptorReadRequest( |
| const std::string& device_address, |
| int request_id, int offset, bool /* is_long */, |
| const bluetooth::GattIdentifier& descriptor_id) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| // This is where we handle an incoming characteristic descriptor read. There |
| // is only one descriptor. |
| if (descriptor_id != hr_measurement_cccd_id_) { |
| std::vector<uint8_t> value; |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_ATTRIBUTE_NOT_FOUND, |
| offset, value); |
| return; |
| } |
| |
| // 16-bit value encoded as little-endian. |
| const uint8_t value_bytes[] = { device_ccc_map_[device_address], 0x00 }; |
| |
| std::vector<uint8_t> value; |
| bluetooth::GATTError error = bluetooth::GATT_ERROR_NONE; |
| if (offset > 2) |
| error = bluetooth::GATT_ERROR_INVALID_OFFSET; |
| else |
| value.insert(value.begin(), value_bytes + offset, value_bytes + 2 - offset); |
| |
| gatt_->SendResponse(server_if_, device_address, request_id, error, |
| offset, value); |
| } |
| |
| void HeartRateServer::OnCharacteristicWriteRequest( |
| const std::string& device_address, |
| int request_id, int offset, bool is_prepare_write, bool need_response, |
| const std::vector<uint8_t>& value, |
| const bluetooth::GattIdentifier& characteristic_id) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| std::vector<uint8_t> dummy; |
| |
| // This is where we handle an incoming characteristic write. The Heart Rate |
| // service doesn't really support prepared writes, so we just reject them to |
| // keep things simple. |
| if (is_prepare_write) { |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, |
| offset, dummy); |
| return; |
| } |
| |
| // Heart Rate Control point is the only writable characteristic. |
| CHECK(characteristic_id == hr_control_point_id_); |
| |
| // Writes to the Heart Rate Control Point characteristic must contain a single |
| // byte with the value 0x01. |
| if (value.size() != 1 || value[0] != 0x01) { |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_OUT_OF_RANGE, |
| offset, dummy); |
| return; |
| } |
| |
| LOG(INFO) << "Heart Rate Control Point written; Enery Expended reset!"; |
| energy_expended_ = 0; |
| |
| if (!need_response) |
| return; |
| |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_NONE, offset, dummy); |
| } |
| |
| void HeartRateServer::OnDescriptorWriteRequest( |
| const std::string& device_address, |
| int request_id, int offset, bool is_prepare_write, bool need_response, |
| const std::vector<uint8_t>& value, |
| const bluetooth::GattIdentifier& descriptor_id) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| std::vector<uint8_t> dummy; |
| |
| // This is where we handle an incoming characteristic write. The Heart Rate |
| // service doesn't really support prepared writes, so we just reject them to |
| // keep things simple. |
| if (is_prepare_write) { |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, |
| offset, dummy); |
| return; |
| } |
| |
| // CCC is the only descriptor we have. |
| CHECK(descriptor_id == hr_measurement_cccd_id_); |
| |
| // CCC must contain 2 bytes for a 16-bit value in little-endian. The only |
| // allowed values here are 0x0000 and 0x0001. |
| if (value.size() != 2 || value[1] != 0x00 || value[0] > 0x01) { |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_CCCD_IMPROPERLY_CONFIGURED, |
| offset, dummy); |
| return; |
| } |
| |
| device_ccc_map_[device_address] = value[0]; |
| |
| LOG(INFO) << "Heart Rate Measurement CCC written - device: " |
| << device_address << " value: " << (int)value[0]; |
| |
| // Start the simulation. |
| if (!simulation_started_ && value[0]) { |
| simulation_started_ = true; |
| ScheduleNextMeasurement(); |
| } |
| |
| if (!need_response) |
| return; |
| |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_NONE, offset, dummy); |
| } |
| |
| void HeartRateServer::OnExecuteWriteRequest( |
| const std::string& device_address, |
| int request_id, |
| bool /* is_execute */) { |
| // We don't support Prepared Writes so, simply return Not Supported error. |
| std::vector<uint8_t> dummy; |
| gatt_->SendResponse(server_if_, device_address, request_id, |
| bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, 0, dummy); |
| } |
| |
| void HeartRateServer::OnNotificationSent( |
| const std::string& device_address, int status) { |
| LOG(INFO) << "Notification was sent - device: " << device_address |
| << " status: " << status; |
| std::lock_guard<std::mutex> lock(mutex_); |
| pending_notification_map_[device_address] = false; |
| } |
| |
| } // namespace heart_rate |