blob: 87ade356856d61517f3f5abdce20047ad03f63a5 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "service.h"
#include <algorithm>
#include <iostream>
#include <iterator>
#include <limits>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fxl/logging.h>
using fuchsia::bluetooth::Status;
namespace gatt = fuchsia::bluetooth::gatt;
namespace bt_le_heart_rate {
namespace {
// See Heart Rate Service v1.0, 3.1.1.1 Flags Field
constexpr uint8_t kHeartRateValueFormat = 1 << 0; // 1 for 16 bit rate value
constexpr uint8_t kSensorContactStatus = 1 << 1;
constexpr uint8_t kSensorContactSupported = 1 << 2;
constexpr uint8_t kEnergyExpendedStatus = 1 << 3;
constexpr uint8_t kRrInterval = 1 << 4;
// Convert an integer type to a smaller integer type with saturation.
template <typename To, typename From>
To Narrow(From value) {
static_assert(sizeof(To) <= sizeof(From), "Can't narrow to a larger type");
constexpr From lower_limit = std::numeric_limits<To>::min();
static_assert(lower_limit >= std::numeric_limits<From>::min(), "Not 1:1");
constexpr From upper_limit = std::numeric_limits<To>::max();
static_assert(upper_limit <= std::numeric_limits<From>::max(), "Not 1:1");
value = std::max(value, lower_limit);
return static_cast<To>(std::min(value, upper_limit));
}
std::vector<uint8_t> MakeMeasurementPayload(int rate,
const bool* sensor_contact,
const int* energy_expended,
const int* rr_interval) {
std::vector<uint8_t> payload(1);
// Compute width of field necessary for the heart rate.
// Heart Rate Service v1.0, 3.1.1.1.1: "Heart Rate Value Format bit may change
// during a connection."
const uint8_t rate_8bit = Narrow<uint8_t>(rate);
if (rate_8bit == rate) {
payload.push_back(rate_8bit);
} else {
payload[0] |= kHeartRateValueFormat;
const auto rate_16bit = Narrow<uint16_t>(rate);
payload.push_back(static_cast<uint8_t>(rate_16bit));
payload.push_back(static_cast<uint8_t>(rate_16bit >> 8));
}
if (sensor_contact) {
payload[0] |= kSensorContactSupported;
payload[0] |= *sensor_contact ? kSensorContactStatus : 0;
}
// Heart Rate Service v1.0, 3.1.1.3: "If the maximum value of 65535 kilo
// Joules is attained (0xFFFF), the field value should remain at 0xFFFF."
if (energy_expended) {
payload[0] |= kEnergyExpendedStatus;
const auto energy_expended_16bit = Narrow<uint16_t>(*energy_expended);
payload.push_back(static_cast<uint8_t>(energy_expended_16bit));
payload.push_back(static_cast<uint8_t>(energy_expended_16bit >> 8));
}
if (rr_interval) {
payload[0] |= kRrInterval;
const auto rr_interval_16bit = Narrow<uint16_t>(*rr_interval);
payload.push_back(static_cast<uint8_t>(rr_interval_16bit));
payload.push_back(static_cast<uint8_t>(rr_interval_16bit >> 8));
}
return payload;
}
void PrintBytes(const fidl::VectorPtr<uint8_t>& value) {
const auto fmtflags = std::cout.flags();
std::cout << std::hex;
std::copy(value->begin(), value->end(),
std::ostream_iterator<unsigned>(std::cout));
std::cout.flags(fmtflags);
}
} // namespace
constexpr char Service::kServiceUuid[];
constexpr uint64_t Service::kHeartRateMeasurementId;
constexpr char Service::kHeartRateMeasurementUuid[];
constexpr uint64_t Service::kBodySensorLocationId;
constexpr char Service::kBodySensorLocationUuid[];
constexpr uint64_t Service::kHeartRateControlPointId;
constexpr char Service::kHeartRateControlPointUuid[];
Service::Service(std::unique_ptr<HeartModel> heart_model)
: heart_model_(std::move(heart_model)),
binding_(this),
notify_scheduled_(false),
weak_factory_(this) {
FXL_DCHECK(heart_model_);
}
void Service::PublishService(gatt::ServerPtr* gatt_server) {
// Heart Rate Measurement
// Allow update with default security of "none required."
gatt::Characteristic hrm;
hrm.id = kHeartRateMeasurementId;
hrm.type = kHeartRateMeasurementUuid;
hrm.properties = gatt::kPropertyNotify;
hrm.permissions = gatt::AttributePermissions::New();
hrm.permissions->update = gatt::SecurityRequirements::New();
// Body Sensor Location
gatt::Characteristic bsl;
bsl.id = kBodySensorLocationId;
bsl.type = kBodySensorLocationUuid;
bsl.properties = gatt::kPropertyRead;
bsl.permissions = gatt::AttributePermissions::New();
bsl.permissions->read = gatt::SecurityRequirements::New();
// Heart Rate Control Point
gatt::Characteristic hrcp;
hrcp.id = kHeartRateControlPointId;
hrcp.type = kHeartRateControlPointUuid;
hrcp.properties = gatt::kPropertyWrite;
hrcp.permissions = gatt::AttributePermissions::New();
hrcp.permissions->write = gatt::SecurityRequirements::New();
fidl::VectorPtr<gatt::Characteristic> characteristics;
characteristics.push_back(std::move(hrm));
characteristics.push_back(std::move(bsl));
characteristics.push_back(std::move(hrcp));
gatt::ServiceInfo si;
si.primary = true;
si.type = kServiceUuid;
si.characteristics = std::move(characteristics);
std::cout << "Publishing service..." << std::endl;
auto publish_svc_result_cb = [](Status status) {
std::cout << "PublishService status: " << bool(status.error) << std::endl;
};
(*gatt_server)
->PublishService(std::move(si), binding_.NewBinding(),
service_.NewRequest(), std::move(publish_svc_result_cb));
}
void Service::NotifyMeasurement() {
HeartModel::Measurement measurement = {};
if (!heart_model_->ReadMeasurement(&measurement))
return;
const auto payload =
MakeMeasurementPayload(measurement.rate, &measurement.contact,
&measurement.energy_expended, nullptr);
for (const auto& peer_id : measurement_peers_) {
service_->NotifyValue(0, peer_id,
fidl::VectorPtr<uint8_t>(std::move(payload)), false);
}
}
void Service::ScheduleNotification() {
auto self = weak_factory_.GetWeakPtr();
async::PostDelayedTask(async_get_default_dispatcher(),
[self] {
if (!self)
return;
if (!self->measurement_peers_.empty()) {
self->NotifyMeasurement();
self->ScheduleNotification();
} else {
self->notify_scheduled_ = false;
}
},
zx::msec(measurement_interval_));
notify_scheduled_ = true;
}
void Service::OnCharacteristicConfiguration(uint64_t characteristic_id,
fidl::StringPtr peer_id,
bool notify, bool indicate) {
std::cout << "CharacteristicConfiguration on peer " << peer_id
<< " (notify: " << notify << ", indicate: " << indicate << ")"
<< std::endl;
if (characteristic_id != kHeartRateMeasurementId) {
std::cout << "Ignoring configuration for characteristic other than Heart "
"Rate Measurement"
<< std::endl;
return;
}
if (notify) {
std::cout << "Enabling heart rate measurements for peer " << peer_id
<< std::endl;
auto insert_res = measurement_peers_.insert(peer_id);
if (insert_res.second && measurement_peers_.size() == 1) {
if (!notify_scheduled_)
ScheduleNotification();
}
} else {
std::cout << "Disabling heart rate measurements for peer " << peer_id
<< std::endl;
measurement_peers_.erase(peer_id);
}
}
void Service::OnReadValue(uint64_t id, int32_t offset,
OnReadValueCallback callback) {
std::cout << "ReadValue on characteristic " << id << " at offset " << offset
<< std::endl;
if (id != kBodySensorLocationId) {
callback(nullptr, gatt::ErrorCode::NOT_PERMITTED);
return;
}
// Body Sensor Location payload
fidl::VectorPtr<uint8_t> value;
value.push_back(static_cast<uint8_t>(BodySensorLocation::kOther));
callback(std::move(value), gatt::ErrorCode::NO_ERROR);
}
void Service::OnWriteValue(uint64_t id, uint16_t offset,
fidl::VectorPtr<uint8_t> value,
OnWriteValueCallback callback) {
std::cout << "WriteValue on characteristic " << id << " at offset " << offset
<< " (";
PrintBytes(value);
std::cout << ")" << std::endl;
if (id != kHeartRateControlPointId) {
std::cout << "Ignoring writes to characteristic other than Heart Rate "
"Control Point"
<< std::endl;
callback(gatt::ErrorCode::NOT_PERMITTED);
return;
}
if (offset != 0) {
std::cout << "Write to control point at invalid offset" << std::endl;
callback(gatt::ErrorCode::INVALID_OFFSET);
return;
}
if (value->size() != 1) {
std::cout << "Write to control point of invalid length" << std::endl;
callback(gatt::ErrorCode::INVALID_VALUE_LENGTH);
return;
}
if ((*value)[0] != kResetEnergyExpendedValue) {
std::cout << "Write value other than \"Reset Energy Expended\" to "
"Heart Rate Control Point characteristic"
<< std::endl;
callback(CONTROL_POINT_NOT_SUPPORTED);
return;
}
std::cout << "Resetting Energy Expended" << std::endl;
heart_model_->ResetEnergyExpended();
callback(gatt::ErrorCode::NO_ERROR);
}
void Service::OnWriteWithoutResponse(uint64_t id, uint16_t offset,
fidl::VectorPtr<uint8_t> value) {
std::cout << "WriteWithoutResponse on characteristic " << id << " at offset "
<< offset << " (";
PrintBytes(value);
std::cout << ")" << std::endl;
}
} // namespace bt_le_heart_rate