fake_bluetooth_gatt_characteristic_client.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h"
6
7#include "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "base/rand_util.h"
10#include "base/time/time.h"
11#include "third_party/cros_system_api/dbus/service_constants.h"
12
13namespace chromeos {
14
15namespace {
16
17const int kHeartRateMeasurementNotificationIntervalMs = 2000;
18
19}  // namespace
20
21// static
22const char FakeBluetoothGattCharacteristicClient::
23    kHeartRateMeasurementPathComponent[] = "char0000";
24const char FakeBluetoothGattCharacteristicClient::
25    kBodySensorLocationPathComponent[] = "char0001";
26const char FakeBluetoothGattCharacteristicClient::
27    kHeartRateControlPointPathComponent[] = "char0002";
28
29// static
30const char FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID[] =
31    "00002a37-0000-1000-8000-00805f9b34fb";
32const char FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID[] =
33    "00002a38-0000-1000-8000-00805f9b34fb";
34const char FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID[] =
35    "00002a39-0000-1000-8000-00805f9b34fb";
36
37FakeBluetoothGattCharacteristicClient::Properties::Properties(
38    const PropertyChangedCallback& callback)
39    : BluetoothGattCharacteristicClient::Properties(
40          NULL,
41          bluetooth_gatt_characteristic::kBluetoothGattCharacteristicInterface,
42          callback) {
43}
44
45FakeBluetoothGattCharacteristicClient::Properties::~Properties() {
46}
47
48void FakeBluetoothGattCharacteristicClient::Properties::Get(
49    dbus::PropertyBase* property,
50    dbus::PropertySet::GetCallback callback) {
51  VLOG(1) << "Get " << property->name();
52  callback.Run(false);
53}
54
55void FakeBluetoothGattCharacteristicClient::Properties::GetAll() {
56  VLOG(1) << "GetAll";
57}
58
59void FakeBluetoothGattCharacteristicClient::Properties::Set(
60    dbus::PropertyBase* property,
61    dbus::PropertySet::SetCallback callback) {
62  VLOG(1) << "Set " << property->name();
63  if (property->name() != value.name()) {
64    callback.Run(false);
65    return;
66  }
67  // Allow writing to only certain characteristics that are defined with the
68  // write permission.
69  // TODO(armansito): Actually check against the permissions property instead of
70  // UUID, once that property is fully defined in the API.
71  if (uuid.value() != kHeartRateControlPointUUID) {
72    callback.Run(false);
73    return;
74  }
75  callback.Run(true);
76  property->ReplaceValueWithSetValue();
77}
78
79FakeBluetoothGattCharacteristicClient::FakeBluetoothGattCharacteristicClient()
80    : heart_rate_visible_(false),
81      calories_burned_(0),
82      weak_ptr_factory_(this) {
83}
84
85FakeBluetoothGattCharacteristicClient::
86    ~FakeBluetoothGattCharacteristicClient() {
87}
88
89void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus* bus) {
90}
91
92void FakeBluetoothGattCharacteristicClient::AddObserver(Observer* observer) {
93  observers_.AddObserver(observer);
94}
95
96void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer* observer) {
97  observers_.RemoveObserver(observer);
98}
99
100std::vector<dbus::ObjectPath>
101FakeBluetoothGattCharacteristicClient::GetCharacteristics() {
102  std::vector<dbus::ObjectPath> paths;
103  if (IsHeartRateVisible()) {
104    paths.push_back(dbus::ObjectPath(heart_rate_measurement_path_));
105    paths.push_back(dbus::ObjectPath(body_sensor_location_path_));
106    paths.push_back(dbus::ObjectPath(heart_rate_control_point_path_));
107  }
108  return paths;
109}
110
111FakeBluetoothGattCharacteristicClient::Properties*
112FakeBluetoothGattCharacteristicClient::GetProperties(
113    const dbus::ObjectPath& object_path) {
114  if (object_path.value() == heart_rate_measurement_path_) {
115    DCHECK(heart_rate_measurement_properties_.get());
116    return heart_rate_measurement_properties_.get();
117  }
118  if (object_path.value() == body_sensor_location_path_) {
119    DCHECK(heart_rate_measurement_properties_.get());
120    return heart_rate_measurement_properties_.get();
121  }
122  if (object_path.value() == heart_rate_control_point_path_) {
123    DCHECK(heart_rate_control_point_properties_.get());
124    return heart_rate_control_point_properties_.get();
125  }
126  return NULL;
127}
128
129void FakeBluetoothGattCharacteristicClient::ExposeHeartRateCharacteristics(
130    const dbus::ObjectPath& service_path) {
131  if (IsHeartRateVisible()) {
132    VLOG(2) << "Fake Heart Rate characteristics are already visible.";
133    return;
134  }
135
136  VLOG(2) << "Exposing fake Heart Rate characteristics.";
137
138  // ==== Heart Rate Measurement Characteristic ====
139  heart_rate_measurement_path_ =
140      service_path.value() + "/" + kHeartRateMeasurementPathComponent;
141  heart_rate_measurement_properties_.reset(new Properties(base::Bind(
142      &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
143      weak_ptr_factory_.GetWeakPtr(),
144      dbus::ObjectPath(heart_rate_measurement_path_))));
145  heart_rate_measurement_properties_->uuid.ReplaceValue(
146      kHeartRateMeasurementUUID);
147  heart_rate_measurement_properties_->service.ReplaceValue(service_path);
148
149  // TODO(armansito): Fill out the flags field once bindings for the values have
150  // been added. For now, leave it empty.
151
152  std::vector<uint8> measurement_value = GetHeartRateMeasurementValue();
153  heart_rate_measurement_properties_->value.ReplaceValue(measurement_value);
154
155  // ==== Body Sensor Location Characteristic ====
156  body_sensor_location_path_ =
157      service_path.value() + "/" + kBodySensorLocationPathComponent;
158  body_sensor_location_properties_.reset(new Properties(base::Bind(
159      &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
160      weak_ptr_factory_.GetWeakPtr(),
161      dbus::ObjectPath(body_sensor_location_path_))));
162  body_sensor_location_properties_->uuid.ReplaceValue(kBodySensorLocationUUID);
163  body_sensor_location_properties_->service.ReplaceValue(service_path);
164
165  // TODO(armansito): Fill out the flags field once bindings for the values have
166  // been added. For now, leave it empty.
167
168  // The sensor is in the "Other" location.
169  std::vector<uint8> body_sensor_location_value;
170  body_sensor_location_value.push_back(0);
171  body_sensor_location_properties_->value.ReplaceValue(
172      body_sensor_location_value);
173
174  // ==== Heart Rate Control Point Characteristic ====
175  heart_rate_control_point_path_ =
176      service_path.value() + "/" + kHeartRateControlPointPathComponent;
177  heart_rate_control_point_properties_.reset(new Properties(base::Bind(
178      &FakeBluetoothGattCharacteristicClient::OnPropertyChanged,
179      weak_ptr_factory_.GetWeakPtr(),
180      dbus::ObjectPath(heart_rate_control_point_path_))));
181  heart_rate_control_point_properties_->uuid.ReplaceValue(
182      kHeartRateControlPointUUID);
183  heart_rate_control_point_properties_->service.ReplaceValue(service_path);
184
185  // TODO(armansito): Fill out the flags field once bindings for the values have
186  // been added. For now, leave it empty.
187
188  // Set the initial value to 0. Whenever this gets set to 1, we will reset the
189  // total calories burned and change the value back to 0.
190  std::vector<uint8> heart_rate_control_point_value;
191  heart_rate_control_point_value.push_back(0);
192  heart_rate_control_point_properties_->value.ReplaceValue(
193      heart_rate_control_point_value);
194
195  heart_rate_visible_ = true;
196
197  NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_measurement_path_));
198  NotifyCharacteristicAdded(dbus::ObjectPath(body_sensor_location_path_));
199  NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_control_point_path_));
200
201  // Set up notifications for heart rate measurement.
202  // TODO(armansito): Do this based on the value of the "client characteristic
203  // configuration" descriptor. Since it's still unclear how descriptors will
204  // be handled by BlueZ, automatically set up notifications for now.
205  ScheduleHeartRateMeasurementValueChange();
206
207  // TODO(armansito): Add descriptors.
208}
209
210void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() {
211  VLOG(2) << "Hiding fake Heart Rate characteristics.";
212  heart_rate_measurement_properties_.reset();
213  body_sensor_location_properties_.reset();
214  heart_rate_control_point_properties_.reset();
215
216  std::string hrm_path = heart_rate_measurement_path_;
217  heart_rate_measurement_path_.clear();
218  std::string bsl_path =  body_sensor_location_path_;
219  body_sensor_location_path_.clear();
220  std::string hrcp_path = heart_rate_control_point_path_;
221  heart_rate_control_point_path_.clear();
222  heart_rate_visible_ = false;
223
224  NotifyCharacteristicRemoved(dbus::ObjectPath(hrm_path));
225  NotifyCharacteristicRemoved(dbus::ObjectPath(bsl_path));
226  NotifyCharacteristicRemoved(dbus::ObjectPath(hrcp_path));
227}
228
229void FakeBluetoothGattCharacteristicClient::OnPropertyChanged(
230    const dbus::ObjectPath& object_path,
231    const std::string& property_name) {
232  VLOG(2) << "Characteristic property changed: " << object_path.value()
233          << ": " << property_name;
234
235  FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
236                    GattCharacteristicPropertyChanged(
237                        object_path, property_name));
238
239  // If the heart rate control point was set, reset the calories burned.
240  if (object_path.value() != heart_rate_control_point_path_)
241    return;
242  DCHECK(heart_rate_control_point_properties_.get());
243  dbus::Property<std::vector<uint8> >* value_prop =
244      &heart_rate_control_point_properties_->value;
245  if (property_name != value_prop->name())
246    return;
247
248  std::vector<uint8> value = value_prop->value();
249  DCHECK(value.size() == 1);
250  if (value[0] == 0)
251    return;
252
253  DCHECK(value[0] == 1);
254  calories_burned_ = 0;
255  value[0] = 0;
256  value_prop->ReplaceValue(value);
257}
258
259void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded(
260    const dbus::ObjectPath& object_path) {
261  VLOG(2) << "GATT characteristic added: " << object_path.value();
262  FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
263                    GattCharacteristicAdded(object_path));
264}
265
266void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved(
267    const dbus::ObjectPath& object_path) {
268  VLOG(2) << "GATT characteristic removed: " << object_path.value();
269  FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
270                    GattCharacteristicRemoved(object_path));
271}
272
273void FakeBluetoothGattCharacteristicClient::
274    ScheduleHeartRateMeasurementValueChange() {
275  if (!IsHeartRateVisible())
276    return;
277  VLOG(2) << "Updating heart rate value.";
278  std::vector<uint8> measurement = GetHeartRateMeasurementValue();
279  heart_rate_measurement_properties_->value.ReplaceValue(measurement);
280
281  base::MessageLoop::current()->PostDelayedTask(
282      FROM_HERE,
283      base::Bind(&FakeBluetoothGattCharacteristicClient::
284                     ScheduleHeartRateMeasurementValueChange,
285                 weak_ptr_factory_.GetWeakPtr()),
286                 base::TimeDelta::FromMilliseconds(
287                     kHeartRateMeasurementNotificationIntervalMs));
288}
289
290std::vector<uint8>
291FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementValue() {
292  // TODO(armansito): We should make sure to properly pack this struct to ensure
293  // correct byte alignment and endianness. It doesn't matter too much right now
294  // as this is a fake and GCC on Linux seems to do the right thing.
295  struct {
296    uint8 flags;
297    uint8 bpm;
298    uint16 energy_expanded;
299    uint16 rr_interval;
300  } value;
301
302  // Flags in LSB:     0       11   1 1 000
303  //                   |       |    | | |
304  // 8-bit bpm format --       |    | | |
305  // Sensor contact supported --    | | |
306  // Energy expanded field present -- | |
307  // RR-Interval values present ------- |
308  // Reserved for future use ------------
309  value.flags = 0x0;
310  value.flags |= (0x03 << 1);
311  value.flags |= (0x01 << 3);
312  value.flags |= (0x01 << 4);
313
314  // Pick a value between 117 bpm and 153 bpm for heart rate.
315  value.bpm = static_cast<uint8>(base::RandInt(117, 153));
316
317  // Total calories burned in kJoules since the last reset. Increment this by 1
318  // every time. It's fine if it overflows: it becomes 0 when the user resets
319  // the heart rate monitor (or pretend that he had a lot of cheeseburgers).
320  value.energy_expanded = calories_burned_++;
321
322  // Include one RR-Interval value, in seconds.
323  value.rr_interval = 60/value.bpm;
324
325  // Return the bytes in an array.
326  uint8* bytes = reinterpret_cast<uint8*>(&value);
327  std::vector<uint8> return_value;
328  return_value.assign(bytes, bytes + sizeof(value));
329  return return_value;
330}
331
332bool FakeBluetoothGattCharacteristicClient::IsHeartRateVisible() const {
333  DCHECK(heart_rate_visible_ != heart_rate_measurement_path_.empty());
334  DCHECK(heart_rate_visible_ != body_sensor_location_path_.empty());
335  DCHECK(heart_rate_visible_ != heart_rate_control_point_path_.empty());
336  DCHECK(heart_rate_visible_ == !!heart_rate_measurement_properties_.get());
337  DCHECK(heart_rate_visible_ == !!body_sensor_location_properties_.get());
338  DCHECK(heart_rate_visible_ == !!heart_rate_control_point_properties_.get());
339  return heart_rate_visible_;
340}
341
342}  // namespace chromeos
343