herald  2.0.0
ble_coordinator.h
1 // Copyright 2021 Herald Project Contributors
2 // SPDX-License-Identifier: Apache-2.0
3 //
4 
5 #ifndef HERALD_BLE_COORDINATION_PROVIDER_H
6 #define HERALD_BLE_COORDINATION_PROVIDER_H
7 
8 #include "../context.h"
9 #include "../sensor.h"
10 #include "ble_database.h"
11 #include "ble_protocols.h"
12 #include "ble_coordinator.h"
13 #include "../engine/activities.h"
14 #include "ble_protocols.h"
15 #include "../data/sensor_logger.h"
16 #include "ble_sensor_configuration.h"
17 
18 #include <memory>
19 #include <functional>
20 #include <optional>
21 #include <tuple>
22 
23 namespace herald {
24 namespace ble {
25 
26 template <typename ContextT, typename BLEDBT, typename ProviderT>
28 public:
29  HeraldProtocolBLECoordinationProvider(ContextT& ctx, BLEDBT& bledb,
30  ProviderT& provider)
31  : context(ctx),
32  db(bledb),
33  pp(provider),
34  previouslyProvisioned(),
35  iterationsSinceBreak(0),
36  breakEvery(10),
37  breakFor(10)
38  HLOGGERINIT(ctx,"heraldble","coordinationprovider")
39  {}
40 
42 
43  // Overrides
44 
46  std::vector<FeatureTag> connectionsProvided() override {
47  return std::vector<FeatureTag>(1,herald::engine::Features::HeraldBluetoothProtocolConnection);
48  }
49 
50  // void provision(const std::vector<PrioritisedPrerequisite>& requested,
51  // const ConnectionCallback& connCallback) override;
52  std::vector<PrioritisedPrerequisite> provision(
53  const std::vector<PrioritisedPrerequisite>& requested) override {
54  if (requested.empty()) {
55  // HTDBG("No connections requested for provisioning");
56  } else {
57  HTDBG("Provisioning connections");
58  }
59 
60  // Don't close connections if we've only paused for advertising/scanning
61  if (iterationsSinceBreak >= breakEvery &&
62  iterationsSinceBreak < (breakEvery + breakFor) ) {
63  return previouslyProvisioned;
64  }
65 
66  // Remove those previously provisoned that we no longer require
67  for (auto& previous : previouslyProvisioned) {
68  bool found = false;
69  for (auto& req : requested) {
70  found = found || std::get<2>(req)==std::get<2>(previous); // comparing target values
71  }
72  if (!found) {
73  HTDBG(" - Found connection to remove");
74  auto& optTarget = std::get<2>(previous);
75  // We can't disconnect from 'all', so check for target
76  if (optTarget.has_value()) {
77  // Ignoring closed true/false result
78  pp.closeConnection(optTarget.value());
79  }
80  }
81  }
82 
83  // Now provision new connections
84  std::vector<PrioritisedPrerequisite> provisioned;
85  // For this provider, a prerequisite is a connection to a remote target identifier over Bluetooth
86  auto requestIter = requested.cbegin();
87  bool lastConnectionSuccessful = true;
88  while (/*currentConnections < maxConnections && */
89  lastConnectionSuccessful &&
90  requestIter != requested.cend()) {
91  HTDBG(" - Satisfying prereq");
92  // HTDBG(" - currentConnections currently:-");
93  // HTDBG(std::to_string(currentConnections));
94  auto& req = *requestIter;
95  // See if we're already connected
96  // If so, add to provisioned list
97  // If not, try to connect
98  auto& optTarget = std::get<2>(req);
99  if (optTarget.has_value()) {
100  HTDBG(" - Have defined target for this prerequisite. Requesting connection be made available.");
101  // std::future<void> fut = std::async(std::launch::async,
102  // &HeraldProtocolV1Provider::openConnection,pp,
103  // optTarget.value(),[&lastConnectionSuccessful] (
104  // const TargetIdentifier& targetForConnection, bool success) -> void {
105  // lastConnectionSuccessful = success;
106  // });
107  // fut.get(); // TODO FIND OUT HOW TO DO THIS FUTURE WAITING FUNCTIONALITY
108 
109  lastConnectionSuccessful = pp.openConnection(optTarget.value());
110 
111  // If successful, add to provisioned list
112  if (lastConnectionSuccessful) {
113  HTDBG(" - Ensuring connection successful");
114  provisioned.push_back(req);
115  // currentConnections++;
116  } else {
117  HTDBG(" - Ensuring connection UNSUCCESSFUL");
118  }
119  } else {
120  HTDBG(" - No defined target, returning satisfied - always true for Herald BLE");
121  // if target not specified then it just means any open connection, so return OK
122  provisioned.push_back(req);
123  // TODO determine what to do here if sensor stopped, bluetooth disabled, or no connections open
124  }
125  // move forward in iterator
126  requestIter++;
127 
128  lastConnectionSuccessful = true;
129  }
130 
131  previouslyProvisioned = provisioned;
132 
133  // TODO schedule disconnection from not required items (E.g. after minimum connection time)
134  // - we already do this if provision is called, but not after a time period
135  // - Bluetooth timeout will deal with the underlying connection, but not any intermediate state in the PP instance
136 
137  // HTDBG("Returning from provision");
138  // connCallback(provisioned);
139  return provisioned;
140  }
141 
142  // Runtime coordination callbacks
144  std::vector<PrioritisedPrerequisite> requiredConnections() override {
145  std::vector<std::tuple<FeatureTag,Priority,std::optional<TargetIdentifier>>> results;
146 
147  // This ensures we break from making connections to allow advertising and scanning
148  iterationsSinceBreak++;
149  if (iterationsSinceBreak >= breakEvery &&
150  iterationsSinceBreak < (breakEvery + breakFor) ) {
151  HTDBG("###### Skipping connections - giving advertising & scanning a chance");
152  // if (iterationsSinceBreak == breakEvery) { // incase it fails
153  pp.restartScanningAndAdvertising();
154  // }
155  return results;
156  } else if (iterationsSinceBreak == (breakEvery + breakFor) ) {
157  // reset
158  iterationsSinceBreak = 0;
159  }
160 
161  // Remove expired devices
162  auto expired = db.matches([/*this*/] (const BLEDevice& device) -> bool {
163  auto interval = device.timeIntervalSinceLastUpdate();
164  bool notZero = interval != TimeInterval::zero();
165  bool isOld = interval > TimeInterval::minutes(15);
166  // HTDBG("ID, created, Now, interval, notZero, isOld:-");
167  // HTDBG((std::string)device.identifier());
168  // HTDBG(std::to_string((long)device.created()));
169  // HTDBG(std::to_string((long)Date()));
170  // HTDBG((std::string)interval);
171  // HTDBG(notZero?"true":"false");
172  // HTDBG(isOld?"true":"false");
173  return notZero && isOld;
174  });
175  for (auto& exp : expired) {
176  db.remove(exp.get().identifier());
177  HTDBG("Removing expired device with ID: ");
178  HTDBG((std::string)exp.get().identifier());
179  HTDBG("time since last update:-");
180  HTDBG(std::to_string(exp.get().timeIntervalSinceLastUpdate()));
181  }
182 
183  // Allow updates from ignored (for a time) status, to retry status
184  auto tempIgnoredOS = db.matches([](const BLEDevice& device) -> bool {
185  return device.operatingSystem() == BLEDeviceOperatingSystem::ignore;
186  });
187  for (auto& device : tempIgnoredOS) {
188  // don't bother with separate activity right now - no connection required
189  device.get().operatingSystem(BLEDeviceOperatingSystem::unknown);
190  }
191 
192 
193  // Add all targets in database that are not known
194  auto newConns = db.matches([this](const BLEDevice& device) -> bool {
195  return !device.ignore() &&
196  (
197  !device.hasService(context.getSensorConfiguration().serviceUUID)
198  ||
199  device.payloadData().size() == 0 // Know the OS, but not the payload (ID)
200  // ||
201  // device.immediateSendData().has_value()
202  )
203  ;
204  });
205  for (auto& device : newConns) {
206  results.emplace_back(herald::engine::Features::HeraldBluetoothProtocolConnection,
207  herald::engine::Priorities::High,
208  device.get().identifier()
209  );
210  }
211 
212  // TODO any other devices we may have outstanding work for that requires connections
213 
214  // DEBUG ONLY ELEMENTS
215  if (newConns.size() > 0) {
216  // print debug info about the BLE Database
217  HTDBG("BLE DATABASE CURRENT CONTENTS:-");
218  auto allDevices = db.matches([](const BLEDevice& device) -> bool {
219  return true;
220  });
221  for (auto& device : allDevices) {
222  std::string di(" - ");
223  BLEMacAddress mac(device.get().identifier().underlyingData());
224  di += (std::string)mac;
225  // di += ", created=";
226  // di += std::to_string(device.get().created());
227  di += ", pseudoAddress=";
228  auto pseudo = device.get().pseudoDeviceAddress();
229  if (pseudo.has_value()) {
230  di += (std::string)pseudo.value();
231  } else {
232  di += "unset";
233  }
234  di += ", os=";
235  auto os = device.get().operatingSystem();
236  // if (os.has_value()) {
237  if (herald::ble::BLEDeviceOperatingSystem::ios == os) {
238  di += "ios";
239  } else if (herald::ble::BLEDeviceOperatingSystem::android == os) {
240  di += "android";
241  } else if (herald::ble::BLEDeviceOperatingSystem::unknown == os) {
242  di += "unknown";
243  } else if (herald::ble::BLEDeviceOperatingSystem::ignore == os) {
244  di += "ignore";
245  } else if (herald::ble::BLEDeviceOperatingSystem::android_tbc == os) {
246  di += "android_tbc";
247  } else if (herald::ble::BLEDeviceOperatingSystem::ios_tbc == os) {
248  di += "ios_tbc";
249  } else if (herald::ble::BLEDeviceOperatingSystem::shared == os) {
250  di += "shared";
251  }
252  // } else {
253  // di += "unknown/unset";
254  // }
255  di += ", ignore=";
256  auto ignore = device.get().ignore();
257  if (ignore) {
258  di += "true (for ";
259  di += std::to_string(device.get().timeIntervalUntilIgnoreExpired().millis());
260  di += " more secs)";
261  } else {
262  di += "false";
263  }
264  // di += ", hasServices=";
265  // di += (device.get().hasServicesSet() ? "true" : "false");
266  di += ", hasReadPayload=";
267  di += (device.get().payloadData().size() > 0 ? device.get().payloadData().hexEncodedString() : "false");
268  HTDBG(di);
269  }
270  } else {
271  // restart scanning when no connection activity is expected
272  pp.restartScanningAndAdvertising();
273  }
274 
275  return results;
276  }
277 
278  std::vector<Activity> requiredActivities() override {
279  std::vector<Activity> results;
280 
281  // General activities first - no connections required
282  // taskRemoveExpiredDevices
283  auto expiredDevices = db.matches([this](const BLEDevice& device) -> bool {
284  return device.timeIntervalSinceLastUpdate() > context.getSensorConfiguration().peripheralCleanInterval;
285  });
286  std::size_t dbSizeBefore = db.size();
287  for (auto& device : expiredDevices) {
288  // remove now so we don't get tasks later for expired devices
289  HTDBG("taskRemoveExpiredDevices (remove={})", (std::string)device.get().identifier());
290  db.remove(device.get().identifier());
291  }
292  std::size_t dbSizeAfter = db.size();
293  if (dbSizeAfter < dbSizeBefore) {
294  HTDBG(" db size has reduced");
295  }
296 
297 
298  // State 0 - New device -> Read full advert data to see if DCT/Herald -> State Z, 1 or State 3
299  // State 1 - Discover services for DCT/Herald on this device -> State Z, or 2
300  // State 2 - New Herald BLE device -> Read payload -> State 3
301  // State 3 - Steady state - do nothing
302  // State 4 - Has immediateSend data -> Send immediate -> State 3
303  // TODO check for nearby payloads
304  // State 3 - Not seen in a while -> State X
305  // State X - Out of range. No actions.
306  // State Z - Ignore (not a relevant device for Herald... right now). Ignore for a period of time. No actions.
307 
308  // TODO is IOS and needs payload sharing
309 
310 
311  // auto state0Devices = db.matches([](BLEDevice device) -> bool {
312  // return !device.ignore() && !device.pseudoDeviceAddress().has_value();
313  // });
314  auto state1Devices = db.matches([this](const BLEDevice& device) -> bool {
315  return !device.ignore() &&
316  // !device.receiveOnly() &&
317  !device.hasService(context.getSensorConfiguration().serviceUUID);
318  });
319  auto state2Devices = db.matches([this](const BLEDevice& device) -> bool {
320  return !device.ignore() &&
321  // !device.receiveOnly() &&
322  device.hasService(context.getSensorConfiguration().serviceUUID) &&
323  device.payloadData().size() == 0; // TODO check for Herald transferred payload data (not legacy)
324  });
325  // auto state4Devices = db.matches([this](const BLEDevice& device) -> bool {
326  // return !device.ignore() &&
327  // // !device.receiveOnly() &&
328  // device.hasService(context.getSensorConfiguration().serviceUUID)
329  // // && device.immediateSendData().has_value()
330  // ;
331  // });
332  // TODO State X (timed out / out of range) devices filter check -> Then remove from BLEDatabase
333 
334  // NOTE State 0 is handled by the Herald BLE scan function, and so has no specific activity
335 
336  // State 1 - discovery Herald service
337  for (auto& device : state1Devices) {
338  results.emplace_back(Activity{
339  .priority = Priorities::High + 10,
340  .name = "herald-service-discovery",
341  .prerequisites = std::vector<std::tuple<FeatureTag,std::optional<TargetIdentifier>>>{
342  1,
343  std::tuple<FeatureTag,std::optional<TargetIdentifier>>{
344  herald::engine::Features::HeraldBluetoothProtocolConnection,
345  device.get().identifier()
346  }
347  },
348  // .executor = [this](const Activity activity, CompletionCallback callback) -> void {
349  // // fill this out
350  // pp->serviceDiscovery(activity,callback);
351  // }
352  .executor = [this](const Activity activity) -> std::optional<Activity> {
353  // fill this out
354  pp.serviceDiscovery(activity);
355  return {};
356  }
357  });
358  }
359 
360  // State 2 - read herald payload(s)
361  for (auto& device : state2Devices) {
362  results.emplace_back(Activity{
363  .priority = Priorities::High + 9,
364  .name = "herald-read-payload",
365  .prerequisites = std::vector<std::tuple<FeatureTag,std::optional<TargetIdentifier>>>{
366  1,
367  std::tuple<FeatureTag,std::optional<TargetIdentifier>>{
368  herald::engine::Features::HeraldBluetoothProtocolConnection,
369  device.get().identifier()
370  }
371  },
372  // .executor = [this](const Activity activity, CompletionCallback callback) -> void {
373  // // fill this out
374  // pp->readPayload(activity,callback);
375  // }
376  .executor = [this](const Activity activity) -> std::optional<Activity> {
377  // fill this out
378  pp.readPayload(activity);
379  return {};
380  }
381  });
382  }
383  // TODO add check for sensor config payload timeout in above IF
384  // TODO add BLESensorConfiguration.deviceIntrospectionEnabled && device.supportsModelCharacteristic() && device.model() == null
385  // TODO add BLESensorConfiguration.deviceIntrospectionEnabled && device.supportsDeviceNameCharacteristic() && device.deviceName() == null
386 
387  // State 4 - Has data for immediate send
388  // for (auto& device : state4Devices) {
389  // results.emplace_back(Activity{
390  // .priority = Priorities::Default + 10,
391  // .name = "herald-immediate-send-targeted",
392  // .prerequisites = std::vector<std::tuple<FeatureTag,std::optional<TargetIdentifier>>>{
393  // 1,
394  // std::tuple<FeatureTag,std::optional<TargetIdentifier>>{
395  // herald::engine::Features::HeraldBluetoothProtocolConnection,
396  // device.get().identifier()
397  // }
398  // },
399  // // For std::async based platforms:-
400  // // .executor = [this](const Activity activity, CompletionCallback callback) -> void {
401  // // // fill this out
402  // // pp->immediateSend(activity,callback);
403  // // }
404  // .executor = [this](const Activity activity) -> std::optional<Activity> {
405  // // fill this out
406  // pp.immediateSend(activity);
407  // return {};
408  // }
409  // });
410  // // TODO add immediate send all support
411  // // TODO add read of nearby payload data from remotes
412  // }
413  return results;
414  }
415 
416 private:
417  ContextT& context;
418  BLEDBT& db;
419  ProviderT& pp;
420 
421  std::vector<PrioritisedPrerequisite> previouslyProvisioned;
422 
423  int iterationsSinceBreak;
424  int breakEvery;
425  int breakFor;
426 
427  HLOGGER(ContextT);
428 };
429 
430 }
431 }
432 
433 #endif
Definition: ble_device.h:181
bool hasService(const UUID &serviceUUID) const
Definition: ble_mac_address.h:18
std::vector< Activity > requiredActivities() override
Get a list of activities that are currently outstanding in this iteration.
Definition: ble_coordinator.h:278
std::vector< PrioritisedPrerequisite > requiredConnections() override
Definition: ble_coordinator.h:144
std::vector< PrioritisedPrerequisite > provision(const std::vector< PrioritisedPrerequisite > &requested) override
Runtime connection provisioning (if it isn't requested, it can be closed)
Definition: ble_coordinator.h:52
std::vector< FeatureTag > connectionsProvided() override
Definition: ble_coordinator.h:46
std::string hexEncodedString() const noexcept
Returns a hex encoded string of this binary data.
Definition: data.h:443
std::size_t size() const noexcept
Returns the size in allocated bytes of this instance.
Definition: data.h:469
Coordination management class that arranges Sensor's periodic requirements and activity interdependen...
Definition: activities.h:93
Acts as a non-global memory arena for arbitrary classes.
Definition: aggregates.h:15
An activity that needs to be performed due to some state being achieved in a Sensor.
Definition: activities.h:75
Priority priority
The relative priority for this Activity compared to the scale.
Definition: activities.h:78