You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
16 KiB
493 lines
16 KiB
// |
|
// 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 <base/bind.h> |
|
#include <base/location.h> |
|
#include <base/logging.h> |
|
#include <base/rand_util.h> |
|
|
|
#include <android/bluetooth/BnBluetoothLeAdvertiserCallback.h> |
|
#include <android/bluetooth/IBluetoothLeAdvertiser.h> |
|
#include <bluetooth/low_energy_constants.h> |
|
|
|
#include "constants.h" |
|
#include "heart_rate_server.h" |
|
|
|
using android::binder::Status; |
|
using android::String8; |
|
using android::String16; |
|
|
|
using android::bluetooth::IBluetoothLeAdvertiser; |
|
using android::bluetooth::BluetoothGattService; |
|
|
|
namespace heart_rate { |
|
|
|
class CLIBluetoothLeAdvertiserCallback |
|
: public android::bluetooth::BnBluetoothLeAdvertiserCallback { |
|
public: |
|
explicit CLIBluetoothLeAdvertiserCallback( |
|
android::sp<android::bluetooth::IBluetooth> bt) |
|
: bt_(bt) {} |
|
|
|
// IBluetoothLeAdvertiserCallback overrides: |
|
Status OnAdvertiserRegistered(int status, int advertiser_id) { |
|
if (status != bluetooth::BLE_STATUS_SUCCESS) { |
|
LOG(ERROR) |
|
<< "Failed to register BLE advertiser, will not start advertising"; |
|
return Status::ok(); |
|
} |
|
|
|
LOG(INFO) << "Registered BLE advertiser with ID: " << advertiser_id; |
|
|
|
String16 name_param; |
|
bt_->GetName(&name_param); |
|
std::string name(String8(name_param).string()); |
|
|
|
/* Advertising data: 16-bit Service UUID: Heart Rate Service, Tx power*/ |
|
std::vector<uint8_t> data{0x03, bluetooth::kEIRTypeComplete16BitUUIDs, |
|
0x0D, 0x18, |
|
0x02, bluetooth::kEIRTypeTxPower, |
|
0x00}; |
|
data.push_back(name.length() + 1); |
|
data.push_back(bluetooth::kEIRTypeCompleteLocalName); |
|
data.insert(data.end(), name.c_str(), name.c_str() + name.length()); |
|
|
|
base::TimeDelta timeout; |
|
|
|
bluetooth::AdvertiseSettings settings( |
|
bluetooth::AdvertiseSettings::MODE_LOW_POWER, timeout, |
|
bluetooth::AdvertiseSettings::TX_POWER_LEVEL_MEDIUM, true); |
|
|
|
bluetooth::AdvertiseData adv_data(data); |
|
bluetooth::AdvertiseData scan_rsp; |
|
|
|
android::sp<IBluetoothLeAdvertiser> ble; |
|
bt_->GetLeAdvertiserInterface(&ble); |
|
bool start_status; |
|
ble->StartMultiAdvertising(advertiser_id, adv_data, scan_rsp, settings, |
|
&start_status); |
|
return Status::ok(); |
|
} |
|
|
|
Status OnMultiAdvertiseCallback( |
|
int status, bool is_start, |
|
const android::bluetooth::AdvertiseSettings& /* settings */) { |
|
LOG(INFO) << "Advertising" << (is_start ? " started" : " stopped"); |
|
return Status::ok(); |
|
}; |
|
|
|
private: |
|
android::sp<android::bluetooth::IBluetooth> bt_; |
|
DISALLOW_COPY_AND_ASSIGN(CLIBluetoothLeAdvertiserCallback); |
|
}; |
|
|
|
HeartRateServer::HeartRateServer( |
|
android::sp<android::bluetooth::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. |
|
bluetooth_->GetGattServerInterface(&gatt_); |
|
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. |
|
bool status; |
|
gatt_->RegisterServer(this, &status); |
|
if (!status) { |
|
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); |
|
|
|
bool status; |
|
gatt_->SendNotification(server_if_, String16(String8(iter.first.c_str())), |
|
hr_measurement_handle_, false, value, &status); |
|
if (status) 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); |
|
} |
|
} |
|
|
|
Status 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 Status::ok(); |
|
} |
|
|
|
// 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_; |
|
|
|
bluetooth::Service hrService( |
|
0, true, kHRServiceUUID, |
|
{{0, |
|
kHRMeasurementUUID, |
|
bluetooth::kCharacteristicPropertyNotify, |
|
0, |
|
{{0, kCCCDescriptorUUID, (bluetooth::kAttributePermissionRead | |
|
bluetooth::kAttributePermissionWrite)}}}, |
|
{0, |
|
kBodySensorLocationUUID, |
|
bluetooth::kCharacteristicPropertyRead, |
|
bluetooth::kAttributePermissionRead, |
|
{}}, |
|
{0, |
|
kHRControlPointUUID, |
|
bluetooth::kCharacteristicPropertyWrite, |
|
bluetooth::kAttributePermissionWrite, |
|
{}}}, |
|
{}); |
|
|
|
bool op_status = true; |
|
|
|
Status stat = gatt_->AddService(server_if_, (BluetoothGattService)hrService, |
|
&op_status); |
|
if (!stat.isOk()) { |
|
LOG(ERROR) << "Failed to add service, status is: " /*<< stat*/; |
|
pending_run_cb_(false); |
|
return Status::ok(); |
|
} |
|
|
|
if (!op_status) { |
|
LOG(ERROR) << "Failed to add service"; |
|
pending_run_cb_(false); |
|
return Status::ok(); |
|
} |
|
|
|
LOG(INFO) << "Initiated AddService request"; |
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnServiceAdded( |
|
int status, const android::bluetooth::BluetoothGattService& service) { |
|
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 Status::ok(); |
|
} |
|
|
|
hr_service_handle_ = service.handle(); |
|
hr_measurement_handle_ = service.characteristics()[0].handle(); |
|
hr_measurement_cccd_handle_ = |
|
service.characteristics()[0].descriptors()[0].handle(); |
|
body_sensor_loc_handle_ = service.characteristics()[1].handle(); |
|
hr_control_point_handle_ = service.characteristics()[2].handle(); |
|
|
|
LOG(INFO) << "Heart Rate service added"; |
|
pending_run_cb_(true); |
|
|
|
if (advertise_) { |
|
android::sp<IBluetoothLeAdvertiser> ble; |
|
bluetooth_->GetLeAdvertiserInterface(&ble); |
|
bool status; |
|
ble->RegisterAdvertiser(new CLIBluetoothLeAdvertiserCallback(bluetooth_), |
|
&status); |
|
} |
|
|
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnCharacteristicReadRequest( |
|
const String16& device_address, int request_id, int offset, |
|
bool /* is_long */, int handle) { |
|
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(handle == body_sensor_loc_handle_); |
|
|
|
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); |
|
|
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, error, offset, |
|
value, &status); |
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnDescriptorReadRequest(const String16& device_address, |
|
int request_id, int offset, |
|
bool /* is_long */, |
|
int handle) { |
|
std::lock_guard<std::mutex> lock(mutex_); |
|
|
|
// This is where we handle an incoming characteristic descriptor read. There |
|
// is only one descriptor. |
|
if (handle != hr_measurement_cccd_handle_) { |
|
std::vector<uint8_t> value; |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_ATTRIBUTE_NOT_FOUND, offset, |
|
value, &status); |
|
return Status::ok(); |
|
} |
|
|
|
// 16-bit value encoded as little-endian. |
|
const uint8_t value_bytes[] = { |
|
device_ccc_map_[std::string(String8(device_address).string())], 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); |
|
|
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, error, offset, |
|
value, &status); |
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnCharacteristicWriteRequest( |
|
const String16& device_address, int request_id, int offset, |
|
bool is_prepare_write, bool need_response, |
|
const std::vector<uint8_t>& value, int handle) { |
|
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) { |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, offset, |
|
dummy, &status); |
|
return Status::ok(); |
|
} |
|
|
|
// Heart Rate Control point is the only writable characteristic. |
|
CHECK(handle == hr_control_point_handle_); |
|
|
|
// Writes to the Heart Rate Control Point characteristic must contain a single |
|
// byte with the value 0x01. |
|
if (value.size() != 1 || value[0] != 0x01) { |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_OUT_OF_RANGE, offset, dummy, |
|
&status); |
|
return Status::ok(); |
|
} |
|
|
|
LOG(INFO) << "Heart Rate Control Point written; Enery Expended reset!"; |
|
energy_expended_ = 0; |
|
|
|
if (!need_response) return Status::ok(); |
|
|
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_NONE, offset, dummy, &status); |
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnDescriptorWriteRequest( |
|
const String16& device_address, int request_id, int offset, |
|
bool is_prepare_write, bool need_response, |
|
const std::vector<uint8_t>& value, int handle) { |
|
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) { |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, offset, |
|
dummy, &status); |
|
return Status::ok(); |
|
} |
|
|
|
// CCC is the only descriptor we have. |
|
CHECK(handle == hr_measurement_cccd_handle_); |
|
|
|
// 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) { |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_CCCD_IMPROPERLY_CONFIGURED, |
|
offset, dummy, &status); |
|
return Status::ok(); |
|
} |
|
|
|
device_ccc_map_[std::string(String8(device_address).string())] = 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 Status::ok(); |
|
|
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_NONE, offset, dummy, &status); |
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnExecuteWriteRequest(const String16& 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; |
|
bool status; |
|
gatt_->SendResponse(server_if_, device_address, request_id, |
|
bluetooth::GATT_ERROR_REQUEST_NOT_SUPPORTED, 0, dummy, |
|
&status); |
|
|
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnNotificationSent(const String16& device_address, |
|
int status) { |
|
LOG(INFO) << "Notification was sent - device: " << device_address |
|
<< " status: " << status; |
|
std::lock_guard<std::mutex> lock(mutex_); |
|
pending_notification_map_[std::string(String8(device_address).string())] = |
|
false; |
|
|
|
return Status::ok(); |
|
} |
|
|
|
Status HeartRateServer::OnConnectionStateChanged(const String16& device_address, |
|
bool connected) { |
|
LOG(INFO) << "Connection state changed - device: " << device_address |
|
<< " connected: " << (connected ? "true" : "false"); |
|
return Status::ok(); |
|
} |
|
} // namespace heart_rate
|
|
|