| Index: chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc
|
| diff --git a/chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc b/chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..85408b8a91c9d2b4729cd06deec51e2eb88fdef8
|
| --- /dev/null
|
| +++ b/chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc
|
| @@ -0,0 +1,342 @@
|
| +// Copyright 2014 The Chromium 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 "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/rand_util.h"
|
| +#include "base/time/time.h"
|
| +#include "third_party/cros_system_api/dbus/service_constants.h"
|
| +
|
| +namespace chromeos {
|
| +
|
| +namespace {
|
| +
|
| +const int kHeartRateMeasurementNotificationIntervalMs = 2000;
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +const char FakeBluetoothGattCharacteristicClient::
|
| + kHeartRateMeasurementPathComponent[] = "char0000";
|
| +const char FakeBluetoothGattCharacteristicClient::
|
| + kBodySensorLocationPathComponent[] = "char0001";
|
| +const char FakeBluetoothGattCharacteristicClient::
|
| + kHeartRateControlPointPathComponent[] = "char0002";
|
| +
|
| +// static
|
| +const char FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID[] =
|
| + "00002a37-0000-1000-8000-00805f9b34fb";
|
| +const char FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID[] =
|
| + "00002a38-0000-1000-8000-00805f9b34fb";
|
| +const char FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID[] =
|
| + "00002a39-0000-1000-8000-00805f9b34fb";
|
| +
|
| +FakeBluetoothGattCharacteristicClient::Properties::Properties(
|
| + const PropertyChangedCallback& callback)
|
| + : BluetoothGattCharacteristicClient::Properties(
|
| + NULL,
|
| + bluetooth_gatt_characteristic::kBluetoothGattCharacteristicInterface,
|
| + callback) {
|
| +}
|
| +
|
| +FakeBluetoothGattCharacteristicClient::Properties::~Properties() {
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::Properties::Get(
|
| + dbus::PropertyBase* property,
|
| + dbus::PropertySet::GetCallback callback) {
|
| + VLOG(1) << "Get " << property->name();
|
| + callback.Run(false);
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::Properties::GetAll() {
|
| + VLOG(1) << "GetAll";
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::Properties::Set(
|
| + dbus::PropertyBase* property,
|
| + dbus::PropertySet::SetCallback callback) {
|
| + VLOG(1) << "Set " << property->name();
|
| + if (property->name() != value.name()) {
|
| + callback.Run(false);
|
| + return;
|
| + }
|
| + // Allow writing to only certain characteristics that are defined with the
|
| + // write permission.
|
| + // TODO(armansito): Actually check against the permissions property instead of
|
| + // UUID, once that property is fully defined in the API.
|
| + if (uuid.value() != kHeartRateControlPointUUID) {
|
| + callback.Run(false);
|
| + return;
|
| + }
|
| + callback.Run(true);
|
| + property->ReplaceValueWithSetValue();
|
| +}
|
| +
|
| +FakeBluetoothGattCharacteristicClient::FakeBluetoothGattCharacteristicClient()
|
| + : heart_rate_visible_(false),
|
| + calories_burned_(0),
|
| + weak_ptr_factory_(this) {
|
| +}
|
| +
|
| +FakeBluetoothGattCharacteristicClient::
|
| + ~FakeBluetoothGattCharacteristicClient() {
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus* bus) {
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::AddObserver(Observer* observer) {
|
| + observers_.AddObserver(observer);
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer* observer) {
|
| + observers_.RemoveObserver(observer);
|
| +}
|
| +
|
| +std::vector<dbus::ObjectPath>
|
| +FakeBluetoothGattCharacteristicClient::GetCharacteristics() {
|
| + std::vector<dbus::ObjectPath> paths;
|
| + if (IsHeartRateVisible()) {
|
| + paths.push_back(dbus::ObjectPath(heart_rate_measurement_path_));
|
| + paths.push_back(dbus::ObjectPath(body_sensor_location_path_));
|
| + paths.push_back(dbus::ObjectPath(heart_rate_control_point_path_));
|
| + }
|
| + return paths;
|
| +}
|
| +
|
| +FakeBluetoothGattCharacteristicClient::Properties*
|
| +FakeBluetoothGattCharacteristicClient::GetProperties(
|
| + const dbus::ObjectPath& object_path) {
|
| + if (object_path.value() == heart_rate_measurement_path_) {
|
| + DCHECK(heart_rate_measurement_properties_.get());
|
| + return heart_rate_measurement_properties_.get();
|
| + }
|
| + if (object_path.value() == body_sensor_location_path_) {
|
| + DCHECK(heart_rate_measurement_properties_.get());
|
| + return heart_rate_measurement_properties_.get();
|
| + }
|
| + if (object_path.value() == heart_rate_control_point_path_) {
|
| + DCHECK(heart_rate_control_point_properties_.get());
|
| + return heart_rate_control_point_properties_.get();
|
| + }
|
| + return NULL;
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::ExposeHeartRateCharacteristics(
|
| + const dbus::ObjectPath& service_path) {
|
| + if (IsHeartRateVisible()) {
|
| + VLOG(2) << "Fake Heart Rate characteristics are already visible.";
|
| + return;
|
| + }
|
| +
|
| + VLOG(2) << "Exposing fake Heart Rate characteristics.";
|
| +
|
| + // ==== Heart Rate Measurement Characteristic ====
|
| + heart_rate_measurement_path_ =
|
| + service_path.value() + "/" + kHeartRateMeasurementPathComponent;
|
| + heart_rate_measurement_properties_.reset(new Properties(base::Bind(
|
| + &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + dbus::ObjectPath(heart_rate_measurement_path_))));
|
| + heart_rate_measurement_properties_->uuid.ReplaceValue(
|
| + kHeartRateMeasurementUUID);
|
| + heart_rate_measurement_properties_->service.ReplaceValue(service_path);
|
| +
|
| + // TODO(armansito): Fill out the flags field once bindings for the values have
|
| + // been added. For now, leave it empty.
|
| +
|
| + std::vector<uint8> measurement_value = GetHeartRateMeasurementValue();
|
| + heart_rate_measurement_properties_->value.ReplaceValue(measurement_value);
|
| +
|
| + // ==== Body Sensor Location Characteristic ====
|
| + body_sensor_location_path_ =
|
| + service_path.value() + "/" + kBodySensorLocationPathComponent;
|
| + body_sensor_location_properties_.reset(new Properties(base::Bind(
|
| + &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + dbus::ObjectPath(body_sensor_location_path_))));
|
| + body_sensor_location_properties_->uuid.ReplaceValue(kBodySensorLocationUUID);
|
| + body_sensor_location_properties_->service.ReplaceValue(service_path);
|
| +
|
| + // TODO(armansito): Fill out the flags field once bindings for the values have
|
| + // been added. For now, leave it empty.
|
| +
|
| + // The sensor is in the "Other" location.
|
| + std::vector<uint8> body_sensor_location_value;
|
| + body_sensor_location_value.push_back(0);
|
| + body_sensor_location_properties_->value.ReplaceValue(
|
| + body_sensor_location_value);
|
| +
|
| + // ==== Heart Rate Control Point Characteristic ====
|
| + heart_rate_control_point_path_ =
|
| + service_path.value() + "/" + kHeartRateControlPointPathComponent;
|
| + heart_rate_control_point_properties_.reset(new Properties(base::Bind(
|
| + &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + dbus::ObjectPath(heart_rate_control_point_path_))));
|
| + heart_rate_control_point_properties_->uuid.ReplaceValue(
|
| + kHeartRateControlPointUUID);
|
| + heart_rate_control_point_properties_->service.ReplaceValue(service_path);
|
| +
|
| + // TODO(armansito): Fill out the flags field once bindings for the values have
|
| + // been added. For now, leave it empty.
|
| +
|
| + // Set the initial value to 0. Whenever this gets set to 1, we will reset the
|
| + // total calories burned and change the value back to 0.
|
| + std::vector<uint8> heart_rate_control_point_value;
|
| + heart_rate_control_point_value.push_back(0);
|
| + heart_rate_control_point_properties_->value.ReplaceValue(
|
| + heart_rate_control_point_value);
|
| +
|
| + heart_rate_visible_ = true;
|
| +
|
| + NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_measurement_path_));
|
| + NotifyCharacteristicAdded(dbus::ObjectPath(body_sensor_location_path_));
|
| + NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_control_point_path_));
|
| +
|
| + // Set up notifications for heart rate measurement.
|
| + // TODO(armansito): Do this based on the value of the "client characteristic
|
| + // configuration" descriptor. Since it's still unclear how descriptors will
|
| + // be handled by BlueZ, automatically set up notifications for now.
|
| + ScheduleHeartRateMeasurementValueChange();
|
| +
|
| + // TODO(armansito): Add descriptors.
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() {
|
| + VLOG(2) << "Hiding fake Heart Rate characteristics.";
|
| + heart_rate_measurement_properties_.reset();
|
| + body_sensor_location_properties_.reset();
|
| + heart_rate_control_point_properties_.reset();
|
| +
|
| + std::string hrm_path = heart_rate_measurement_path_;
|
| + heart_rate_measurement_path_.clear();
|
| + std::string bsl_path = body_sensor_location_path_;
|
| + body_sensor_location_path_.clear();
|
| + std::string hrcp_path = heart_rate_control_point_path_;
|
| + heart_rate_control_point_path_.clear();
|
| + heart_rate_visible_ = false;
|
| +
|
| + NotifyCharacteristicRemoved(dbus::ObjectPath(hrm_path));
|
| + NotifyCharacteristicRemoved(dbus::ObjectPath(bsl_path));
|
| + NotifyCharacteristicRemoved(dbus::ObjectPath(hrcp_path));
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::OnPropertyChanged(
|
| + const dbus::ObjectPath& object_path,
|
| + const std::string& property_name) {
|
| + VLOG(2) << "Characteristic property changed: " << object_path.value()
|
| + << ": " << property_name;
|
| +
|
| + FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
|
| + GattCharacteristicPropertyChanged(
|
| + object_path, property_name));
|
| +
|
| + // If the heart rate control point was set, reset the calories burned.
|
| + if (object_path.value() != heart_rate_control_point_path_)
|
| + return;
|
| + DCHECK(heart_rate_control_point_properties_.get());
|
| + dbus::Property<std::vector<uint8> >* value_prop =
|
| + &heart_rate_control_point_properties_->value;
|
| + if (property_name != value_prop->name())
|
| + return;
|
| +
|
| + std::vector<uint8> value = value_prop->value();
|
| + DCHECK(value.size() == 1);
|
| + if (value[0] == 0)
|
| + return;
|
| +
|
| + DCHECK(value[0] == 1);
|
| + calories_burned_ = 0;
|
| + value[0] = 0;
|
| + value_prop->ReplaceValue(value);
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded(
|
| + const dbus::ObjectPath& object_path) {
|
| + VLOG(2) << "GATT characteristic added: " << object_path.value();
|
| + FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
|
| + GattCharacteristicAdded(object_path));
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved(
|
| + const dbus::ObjectPath& object_path) {
|
| + VLOG(2) << "GATT characteristic removed: " << object_path.value();
|
| + FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
|
| + GattCharacteristicRemoved(object_path));
|
| +}
|
| +
|
| +void FakeBluetoothGattCharacteristicClient::
|
| + ScheduleHeartRateMeasurementValueChange() {
|
| + if (!IsHeartRateVisible())
|
| + return;
|
| + VLOG(2) << "Updating heart rate value.";
|
| + std::vector<uint8> measurement = GetHeartRateMeasurementValue();
|
| + heart_rate_measurement_properties_->value.ReplaceValue(measurement);
|
| +
|
| + base::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE,
|
| + base::Bind(&FakeBluetoothGattCharacteristicClient::
|
| + ScheduleHeartRateMeasurementValueChange,
|
| + weak_ptr_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(
|
| + kHeartRateMeasurementNotificationIntervalMs));
|
| +}
|
| +
|
| +std::vector<uint8>
|
| +FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementValue() {
|
| + // TODO(armansito): We should make sure to properly pack this struct to ensure
|
| + // correct byte alignment and endianness. It doesn't matter too much right now
|
| + // as this is a fake and GCC on Linux seems to do the right thing.
|
| + struct {
|
| + uint8 flags;
|
| + uint8 bpm;
|
| + uint16 energy_expanded;
|
| + uint16 rr_interval;
|
| + } value;
|
| +
|
| + // Flags in LSB: 0 11 1 1 000
|
| + // | | | | |
|
| + // 8-bit bpm format -- | | | |
|
| + // Sensor contact supported -- | | |
|
| + // Energy expanded field present -- | |
|
| + // RR-Interval values present ------- |
|
| + // Reserved for future use ------------
|
| + value.flags = 0x0;
|
| + value.flags |= (0x03 << 1);
|
| + value.flags |= (0x01 << 3);
|
| + value.flags |= (0x01 << 4);
|
| +
|
| + // Pick a value between 117 bpm and 153 bpm for heart rate.
|
| + value.bpm = static_cast<uint8>(base::RandInt(117, 153));
|
| +
|
| + // Total calories burned in kJoules since the last reset. Increment this by 1
|
| + // every time. It's fine if it overflows: it becomes 0 when the user resets
|
| + // the heart rate monitor (or pretend that he had a lot of cheeseburgers).
|
| + value.energy_expanded = calories_burned_++;
|
| +
|
| + // Include one RR-Interval value, in seconds.
|
| + value.rr_interval = 60/value.bpm;
|
| +
|
| + // Return the bytes in an array.
|
| + uint8* bytes = reinterpret_cast<uint8*>(&value);
|
| + std::vector<uint8> return_value;
|
| + return_value.assign(bytes, bytes + sizeof(value));
|
| + return return_value;
|
| +}
|
| +
|
| +bool FakeBluetoothGattCharacteristicClient::IsHeartRateVisible() const {
|
| + DCHECK(heart_rate_visible_ != heart_rate_measurement_path_.empty());
|
| + DCHECK(heart_rate_visible_ != body_sensor_location_path_.empty());
|
| + DCHECK(heart_rate_visible_ != heart_rate_control_point_path_.empty());
|
| + DCHECK(heart_rate_visible_ == !!heart_rate_measurement_properties_.get());
|
| + DCHECK(heart_rate_visible_ == !!body_sensor_location_properties_.get());
|
| + DCHECK(heart_rate_visible_ == !!heart_rate_control_point_properties_.get());
|
| + return heart_rate_visible_;
|
| +}
|
| +
|
| +} // namespace chromeos
|
|
|