diff options
author | armansito@chromium.org <armansito@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-24 21:51:10 +0000 |
---|---|---|
committer | armansito@chromium.org <armansito@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-24 21:51:10 +0000 |
commit | 95d00f3223b240ef386e185e24bfae2d662dd5bd (patch) | |
tree | 31877c666f0ee6074ee14edb9fecca661682ad57 /chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc | |
parent | f215a25fe8773b80f624e1b667e0cb30ec9a7f35 (diff) | |
download | chromium_src-95d00f3223b240ef386e185e24bfae2d662dd5bd.zip chromium_src-95d00f3223b240ef386e185e24bfae2d662dd5bd.tar.gz chromium_src-95d00f3223b240ef386e185e24bfae2d662dd5bd.tar.bz2 |
chromeos/dbus: Add fake D-Bus clients for GATT client-mode.
Added fake implementations of GATT service, characteristic, and descriptor
clients. A new "Low Energy" fake device is introduced, which uses the fake
GATT clients to simulate the Heart Rate Service.
BUG=351229
TEST=1. device_unittests, chromeos_unittests.
2. Build and run chrome for Linux with "chromeos=1". Run Bluetooth device
discovery, connect to the device named "Bluetooth 4.0 Heart Rate Monitor".
Watch the debug output (vmodule="*bluetooth_gatt*=2") for the GATT clients,
which should create fake heart rate service and descriptors and update the
measurement value at regular intervals.
R=keybuk@chromium.org, stevenjb@chromium.org
Review URL: https://codereview.chromium.org/206443009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259022 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc')
-rw-r--r-- | chromeos/dbus/fake_bluetooth_gatt_characteristic_client.cc | 342 |
1 files changed, 342 insertions, 0 deletions
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 0000000..85408b8 --- /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 |