PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
ClariusBLE.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2  Program: Plus
3  Copyright (c) Verdure Imaging Inc, Stockton, California. All rights reserved.
4  See License.txt for details.
5  We would like to acknowledge Verdure Imaging Inc for generously open-sourcing
6  this support for the Clarius OEM interface to the PLUS & Slicer communities.
7 =========================================================Plus=header=end*/
8 
9 // local includes
10 #include "ClariusBLE.h"
11 
12 // WinRT includes
13 #include <winrt/base.h>
14 #include <winrt/windows.devices.bluetooth.h>
15 #include <winrt/windows.devices.bluetooth.genericattributeprofile.h>
16 #include <winrt/windows.devices.enumeration.h>
17 #include <winrt/windows.foundation.h>
18 #include <winrt/windows.foundation.collections.h>
19 #include <winrt/windows.storage.h>
20 #include <winrt/windows.storage.streams.h>
21 
22 // WinRT using directives
23 using namespace winrt;
24 using namespace Windows::Devices::Bluetooth;
25 using namespace Windows::Devices::Bluetooth::GenericAttributeProfile;
26 using namespace Windows::Devices::Enumeration;
27 using namespace Windows::Foundation;
28 using namespace Windows::Foundation::Collections;
29 using namespace Windows::Storage;
30 using namespace Windows::Storage::Streams;
31 
32 // STL includes
33 #include <atomic>
34 #include <codecvt>
35 #include <future>
36 #include <mutex>
37 #include <iomanip>
38 #include <locale>
39 #include <sstream>
40 #include <stdexcept>
41 
42 // link to Windows Runtime library
43 #pragma comment(lib, "windowsapp")
44 
45 // power service uuid (8C853B6A-2297-44C1-8277-73627C8D2ABC)
46 static const winrt::guid POWER_SERVICE_UUID
47 { 0x8c853b6a, 0x2297, 0x44c1, { 0x82, 0x77, 0x73, 0x62, 0x7c, 0x8d, 0x2a, 0xbc } };
48 
49 // power published characteristic uuid (8c853b6a-2297-44c1-8277-73627c8d2abd)
50 static const winrt::guid POWER_PUBLISHED_CHAR_UUID
51 { 0x8c853b6a, 0x2297, 0x44c1, { 0x82, 0x77, 0x73, 0x62, 0x7c, 0x8d, 0x2a, 0xbd } };
52 
53 // power request characteristic uuid (8c853b6a-2297-44c1-8277-73627c8d2abe)
54 static const winrt::guid POWER_REQUEST_CHAR_UUID
55 { 0x8c853b6a, 0x2297, 0x44c1, { 0x82, 0x77, 0x73, 0x62, 0x7c, 0x8d, 0x2a, 0xbe } };
56 
57 // wifi service uuid (f9eb3fae-947a-4e5b-ab7c-c799e91ed780)
58 static const winrt::guid WIFI_SERVICE_UUID
59 { 0xf9eb3fae, 0x947a, 0x4e5b, { 0xab, 0x7c, 0xc7, 0x99, 0xe9, 0x1e, 0xd7, 0x80 } };
60 
61 // wifi published characteristic uuid (f9eb3fae-947a-4e5b-ab7c-c799e91ed781)
62 static const winrt::guid WIFI_PUBLISHED_CHAR_UUID
63 { 0xf9eb3fae, 0x947a, 0x4e5b, { 0xab, 0x7c, 0xc7, 0x99, 0xe9, 0x1e, 0xd7, 0x81 } };
64 
65 // wifi request characteristic uuid (f9eb3fae-947a-4e5b-ab7c-c799e91ed782)
66 static const winrt::guid WIFI_REQUEST_CHAR_UUID
67 { 0xf9eb3fae, 0x947a, 0x4e5b, { 0xab, 0x7c, 0xc7, 0x99, 0xe9, 0x1e, 0xd7, 0x82 } };
68 
69 // max duration to wait for probe boot sequence to complete
70 static const uint64_t POWER_ON_TIMEOUT_SEC = 30;
71 static const uint64_t POWER_ON_POLL_INTERVAL_SEC = 1;
72 
73 // max duration to block while waiting for a BLE operation to complete
74 static const uint64_t BLE_OP_TIMEOUT_SEC = 5;
75 
76 static const int MAX_BLE_CONNECTION_ATTEMPTS = 20;
77 
78 //-----------------------------------------------------------------------------
79 // free helper functions
80 //-----------------------------------------------------------------------------
81 
82 // busy wait for IAsyncOperation to complete
83 template <typename TAsyncOp>
84 PlusStatus await_async(TAsyncOp op)
85 {
86  std::promise<void> wait_prom;
87  std::future<void> wait_future = wait_prom.get_future();
88 
89  auto busy_wait = [&](TAsyncOp op)
90  {
91  while (op.Status() == AsyncStatus::Started);
92 
93  // successful completion
94  wait_prom.set_value();
95  };
96 
97  busy_wait(op);
98 
99  std::future_status status = wait_future.wait_for(std::chrono::seconds(BLE_OP_TIMEOUT_SEC));
100  switch (status)
101  {
102  case std::future_status::ready:
103  break;
104  case std::future_status::timeout:
105  LOG_ERROR("Awaiting asynchronous operation timed out");
106  break;
107  case std::future_status::deferred:
108  LOG_ERROR("Awaiting asynchronous operation deferred");
109  break;
110  default:
111  LOG_ERROR("Unexpected error occurred while awaiting completion of C++/WinRT asynchronous operation");
112  break;
113  }
114 
115  // process error
116  switch (op.Status())
117  {
118  case AsyncStatus::Completed:
119  break;
120  case AsyncStatus::Canceled:
121  LOG_ERROR("Awaiting asynchronous operation which terminated with status AsyncStatus::Canceled");
122  break;
123  case AsyncStatus::Started:
124  LOG_ERROR("Awaiting asynchronous operation which timed out with status AsyncStatus::Started");
125  break;
126  case AsyncStatus::Error:
127  LOG_ERROR("Error occurred while awaiting asynchronous operation. Error code was: " << op.ErrorCode());
128  break;
129  default:
130  LOG_ERROR("Unexpected error occurred causing timeout while awaiting completion of C++/WinRT asynchronous operation");
131  break;
132  }
133 
134  // async operation was successful
135  return op.Status() == AsyncStatus::Completed ? PLUS_SUCCESS : PLUS_FAIL;
136 }
137 
138 //-----------------------------------------------------------------------------
139 std::wstring to_wide_string(std::string str)
140 {
141  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
142  return converter.from_bytes(str);
143 }
144 
145 //-----------------------------------------------------------------------------
146 std::string to_narrow_string(std::wstring wstr)
147 {
148  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
149  return converter.to_bytes(wstr);
150 }
151 
152 //-----------------------------------------------------------------------------
153 std::string to_narrow_string(winrt::hstring hstr)
154 {
155  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
156  return converter.to_bytes(hstr.c_str());
157 }
158 
159 //-----------------------------------------------------------------------------
160 // from https://github.com/bucienator/ble-win-cpp
161 std::string uuid_to_string(const winrt::guid& uuid)
162 {
163  std::stringstream str;
164  str << std::uppercase << std::hex;
165  str << std::setw(8) << std::setfill('0') << uuid.Data1 << "-";
166  str << std::setw(4) << std::setfill('0') << uuid.Data2 << "-";
167  str << std::setw(4) << std::setfill('0') << uuid.Data3 << "-";
168  str << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[0])
169  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[1])
170  << '-'
171  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[2])
172  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[3])
173  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[4])
174  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[5])
175  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[6])
176  << std::setw(2) << std::setfill('0') << static_cast<short>(uuid.Data4[7]);
177  str << std::nouppercase;
178  return str.str();
179 }
180 
181 //-----------------------------------------------------------------------------
182 // ClariusBLEPrivate declaration
183 //-----------------------------------------------------------------------------
184 class ClariusBLEPrivate
185 {
186 public:
187 
188  ClariusBLEPrivate(ClariusBLE* ext);
189 
190  virtual ~ClariusBLEPrivate() = default;
191 
192  // WinRT callback called whenever a new BLE device is discovered
193  void DeviceAdded(DeviceWatcher sender, DeviceInformation deviceInfo);
194 
195  // WinRT callback called whenever the state of an existing found BLE device
196  // is update
197  // NOTE: it seems that without this function, the DeviceAdded callback
198  // doesn't function correctly
199  void DeviceUpdated(DeviceWatcher sender, DeviceInformationUpdate devInfoUpdate);
200 
201  // WinRT callback subscribed to changes in the PowerPublished characteristic
202  void PowerStateChanged(GattCharacteristic sender, GattValueChangedEventArgs args);
203 
204  // WinRT callback subscribed to changes in the WifiPublished characteristic
205  void WifiStateChanged(GattCharacteristic sender, GattValueChangedEventArgs args);
206 
207  // setup function for the Clarius power service and related characteristics
208  PlusStatus SetupPowerService();
209 
210  // setup function for the Clarius WiFi service and related characteristics
211  PlusStatus SetupWifiService();
212 
213  // call this immediately after SetupPowerService / SetupWifiService members
214  // to ensure state is initialized correctly
215  PlusStatus InitializeState();
216 
217  // thread safe accessor for wifi info
218  ClariusWifiInfo GetWifiInfo();
219 
220  // convert GattCommunicationStatus enum to string representations
221  std::string GattCommunicationStatusToString(GattCommunicationStatus status);
222 
223  // parses Clarius WiFi info string into this->WifiInfo
224  void ProcessWifiInfo(std::string info);
225 
226  // members
227 
228  // pointer to ClariusBLE instance
229  ClariusBLE* External;
230 
231  // desired Clarius probe ID (serial-number)
232  std::wstring ProbeId;
233 
234  // list of nearby probes populated by call to FindBySerial / the DeviceAdded callback
235  std::vector<std::string> FoundProbeIds;
236 
237  // last error message
238  std::string LastError;
239 
240  // BLE device
241  DeviceInformation DeviceInfo{ nullptr };
242  std::promise<void> DeviceInfoPromise;
243  BluetoothLEDevice Device{ nullptr };
244 
245  // BLE services
246  GattDeviceService PowerService{ nullptr };
247  GattDeviceService WifiService{ nullptr };
248 
249  // BLE characteristics
250  GattCharacteristic PowerPublishedChar{ nullptr };
251  GattCharacteristic PowerRequestChar{ nullptr };
252  GattCharacteristic WifiPublishedChar{ nullptr };
253  GattCharacteristic WifiRequestChar{ nullptr };
254 
255  // power state
256  std::atomic<bool> PowerState;
257 
258  // wifi state
259  std::promise<void> WifiInfoPromise; // use to await setting on power-up
260  std::atomic<bool> WifiInfoSet; // use to check if already set
261 
262 private:
263 
264  // helper to retrieve a GattDeviceService by UUID
265  PlusStatus RetrieveService(const guid& serviceUuid, GattDeviceService& service);
266 
267  // helper to retrieve a GattCharacteristic from a service by UUID
268  PlusStatus RetrieveCharacteristic(
269  const GattDeviceService& service,
270  const guid charUuid,
271  GattCharacteristic& characteristic
272  );
273 
274  // helper for ProcessWifiInfo to split string up
275  std::vector<std::string> TokenizeString(std::string str, const char delimiter);
276 
277  // wifi connection information for Clarius probe
278  std::mutex WifiInfoMutex;
279  ClariusWifiInfo WifiInfo;
280 };
281 
282 //-----------------------------------------------------------------------------
283 // ClariusBLEPrivate method definitions
284 //-----------------------------------------------------------------------------
285 ClariusBLEPrivate::ClariusBLEPrivate(ClariusBLE* ext)
286  : External(ext)
287  , PowerState(false)
288  , WifiInfoSet(false)
289 {
290 }
291 
292 //-----------------------------------------------------------------------------
293 void ClariusBLEPrivate::DeviceAdded(
294  DeviceWatcher sender,
295  DeviceInformation info)
296 {
297  (void)sender;
298 
299  std::wstring name{ info.Name().c_str() };
300  if (name == this->ProbeId)
301  {
302  this->DeviceInfo = info;
303  this->DeviceInfoPromise.set_value();
304  }
305  else if (name.find(L"CUS") != std::wstring::npos)
306  {
307  this->FoundProbeIds.push_back(to_narrow_string(name));
308  }
309 }
310 
311 //-----------------------------------------------------------------------------
312 void ClariusBLEPrivate::DeviceUpdated(
313  winrt::Windows::Devices::Enumeration::DeviceWatcher sender,
314  winrt::Windows::Devices::Enumeration::DeviceInformationUpdate devInfoUpdate)
315 {
316  (void)sender;
317  (void)devInfoUpdate;
318 }
319 
320 //-----------------------------------------------------------------------------
321 void ClariusBLEPrivate::PowerStateChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
322 {
323  DataReader reader = DataReader::FromBuffer(args.CharacteristicValue());
324  uint8_t powered = reader.ReadByte();
325  this->PowerState = bool(powered);
326 }
327 
328 //-----------------------------------------------------------------------------
329 void ClariusBLEPrivate::WifiStateChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
330 {
331  DataReader reader = DataReader::FromBuffer(args.CharacteristicValue());
332  winrt::hstring wifiHString = reader.ReadString(reader.UnconsumedBufferLength());
333  std::string wifiStr = to_narrow_string(wifiHString);
334  this->ProcessWifiInfo(wifiStr);
335 
336  // signal info ready
337  this->WifiInfoPromise.set_value();
338 }
339 
340 //-----------------------------------------------------------------------------
341 PlusStatus ClariusBLEPrivate::SetupPowerService()
342 {
343  LOG_DEBUG("Setting up Clarius power service");
344 
345  if (this->RetrieveService(POWER_SERVICE_UUID, this->PowerService) != PLUS_SUCCESS)
346  {
347  // last error already set
348  return PLUS_FAIL;
349  }
350 
351  if (this->RetrieveCharacteristic(this->PowerService, POWER_PUBLISHED_CHAR_UUID, this->PowerPublishedChar) != PLUS_SUCCESS)
352  {
353  // last error already set
354  return PLUS_FAIL;
355  }
356 
357  if (this->RetrieveCharacteristic(this->PowerService, POWER_REQUEST_CHAR_UUID, this->PowerRequestChar) != PLUS_SUCCESS)
358  {
359  // last error already set
360  return PLUS_FAIL;
361  }
362 
363  if (!this->PowerPublishedChar)
364  {
365  this->LastError = "Could not find PowerPublished characteristic";
366  return PLUS_FAIL;
367  }
368 
369  if (!this->PowerRequestChar)
370  {
371  this->LastError = "Could not find PowerRequest characteristic";
372  return PLUS_FAIL;
373  }
374 
375  // subscribe to power state changes
376  this->PowerPublishedChar.WriteClientCharacteristicConfigurationDescriptorAsync(
377  GattClientCharacteristicConfigurationDescriptorValue::Notify);
378  this->PowerPublishedChar.ValueChanged({ this, &ClariusBLEPrivate::PowerStateChanged });
379 
380  return PLUS_SUCCESS;
381 }
382 
383 //-----------------------------------------------------------------------------
384 PlusStatus ClariusBLEPrivate::SetupWifiService()
385 {
386  LOG_DEBUG("Setting up Clarius wifi service");
387 
388  if (this->RetrieveService(WIFI_SERVICE_UUID, this->WifiService) != PLUS_SUCCESS)
389  {
390  // last error already set
391  return PLUS_FAIL;
392  }
393 
394  if (this->RetrieveCharacteristic(this->WifiService, WIFI_PUBLISHED_CHAR_UUID, this->WifiPublishedChar) != PLUS_SUCCESS)
395  {
396  // last error already set
397  return PLUS_FAIL;
398  }
399 
400  if (this->RetrieveCharacteristic(this->WifiService, WIFI_REQUEST_CHAR_UUID, this->WifiRequestChar) != PLUS_SUCCESS)
401  {
402  // last error already set
403  return PLUS_FAIL;
404  }
405 
406  if (!this->WifiPublishedChar)
407  {
408  this->LastError = "Could not find WifiPublished characteristic";
409  return PLUS_FAIL;
410  }
411 
412  if (!this->WifiRequestChar)
413  {
414  this->LastError = "Could not find WifiRequest characteristic";
415  return PLUS_FAIL;
416  }
417 
418  // subscribe to wifi state changes
419  // TODO: Even after subscribing, WifiStateChanged callback doesn't seem to be called.
420  this->WifiPublishedChar.WriteClientCharacteristicConfigurationDescriptorAsync(
421  GattClientCharacteristicConfigurationDescriptorValue::Notify);
422  this->WifiPublishedChar.ValueChanged({ this, &ClariusBLEPrivate::WifiStateChanged });
423 
424  return PLUS_SUCCESS;
425 }
426 
427 //-----------------------------------------------------------------------------
428 PlusStatus ClariusBLEPrivate::InitializeState()
429 {
430  LOG_DEBUG("Initializing Clarius BLE state");
431 
432  // initialize power state
433  IAsyncOperation<GattReadResult> powerOp =
434  this->PowerPublishedChar.ReadValueAsync(BluetoothCacheMode::Uncached);
435  if (await_async(powerOp) != PLUS_SUCCESS)
436  {
437  this->LastError = "Failed to async read power state from BLE device";
438  return PLUS_FAIL;
439  }
440 
441  GattReadResult powerResult = powerOp.GetResults();
442  if (powerResult.Status() != GattCommunicationStatus::Success)
443  {
444  std::stringstream msg;
445  msg << "Failed to initialize power state in InitializeState with non-successful "
446  "GATT communication. Status was: "
447  << this->GattCommunicationStatusToString(powerResult.Status());
448  this->LastError = msg.str();
449  return PLUS_FAIL;
450  }
451 
452  DataReader powerReader = DataReader::FromBuffer(powerResult.Value());
453  this->PowerState = bool(powerReader.ReadByte());
454 
455  if (!this->PowerState)
456  {
457  // Clarius not powered, wifi info will be initialized correctly on power
458  // up completion
459  return PLUS_SUCCESS;
460  }
461 
462  // initialize wifi state
463  IAsyncOperation<GattReadResult> wifiOp =
464  this->WifiPublishedChar.ReadValueAsync(BluetoothCacheMode::Uncached);
465  if (await_async(wifiOp) != PLUS_SUCCESS)
466  {
467  this->LastError = "Failed to async read wifi state from BLE device";
468  return PLUS_FAIL;
469  }
470 
471  GattReadResult wifiResult = wifiOp.GetResults();
472  if (wifiResult.Status() != GattCommunicationStatus::Success)
473  {
474  std::stringstream msg;
475  msg << "Failed to initialize wifi state in InitializeState with non-successful "
476  "GATT communication. Status was: " << this->GattCommunicationStatusToString(wifiResult.Status());
477  this->LastError = msg.str();
478  return PLUS_FAIL;
479  }
480 
481  DataReader wifiReader = DataReader::FromBuffer(wifiResult.Value());
482  winrt::hstring wifiHString = wifiReader.ReadString(wifiReader.UnconsumedBufferLength());
483  std::string wifiStr = to_narrow_string(wifiHString);
484  this->ProcessWifiInfo(wifiStr);
485 
486  return PLUS_SUCCESS;
487 }
488 
489 //-----------------------------------------------------------------------------
490 ClariusWifiInfo ClariusBLEPrivate::GetWifiInfo()
491 {
492  std::unique_lock<std::mutex> wifiInfoLock(this->WifiInfoMutex);
493  return this->WifiInfo;
494 }
495 
496 //-----------------------------------------------------------------------------
497 std::string ClariusBLEPrivate::GattCommunicationStatusToString(GattCommunicationStatus status)
498 {
499  switch (status)
500  {
501  case GattCommunicationStatus::AccessDenied:
502  return "AccessDenied";
503  case GattCommunicationStatus::ProtocolError:
504  return "ProtocolError";
505  case GattCommunicationStatus::Success:
506  return "Success";
507  case GattCommunicationStatus::Unreachable:
508  return "Unreachable";
509  default:
510  return "Unknown GattCommunicationStatus";
511  }
512 }
513 
514 //-----------------------------------------------------------------------------
515 PlusStatus ClariusBLEPrivate::RetrieveService(const guid& serviceUuid, GattDeviceService& service)
516 {
517  IAsyncOperation<GattDeviceServicesResult> getServicesOp =
518  this->Device.GetGattServicesForUuidAsync(serviceUuid, BluetoothCacheMode::Uncached);
519  if (await_async(getServicesOp) != PLUS_SUCCESS)
520  {
521  this->LastError = "ClariusBLEPrivate::RetrieveService async operation failed";
522  return PLUS_FAIL;
523  }
524 
525  GattDeviceServicesResult servicesResult = getServicesOp.GetResults();
526 
527  // check that GetGattServicesForUuidAsync returned a successful status
528  if (servicesResult.Status() != GattCommunicationStatus::Success)
529  {
530  std::stringstream msg;
531  msg << "ClariusBLEPrivate::RetrieveService failed for UUID " << uuid_to_string(serviceUuid)
532  << " with non-successful GATT communication. Status was: "
533  << this->GattCommunicationStatusToString(servicesResult.Status());
534  this->LastError = msg.str();
535  return PLUS_FAIL;
536  }
537 
538  // check that at least one service was found for this uuid
539  if (!servicesResult.Services().Size())
540  {
541  std::stringstream msg;
542  msg << "ClariusBLEPrivate::RetrieveService failed for UUID " << uuid_to_string(serviceUuid)
543  << ". No services with this UUID were found.";
544  this->LastError = msg.str();
545  return PLUS_FAIL;
546  }
547 
548  service = *servicesResult.Services().First();
549  if (!service)
550  {
551  std::stringstream msg;
552  msg << "ClariusBLEPrivate::RetrieveService could not find service " << uuid_to_string(serviceUuid);
553  this->LastError = msg.str();
554  return PLUS_FAIL;
555  }
556  return PLUS_SUCCESS;
557 }
558 
559 //-----------------------------------------------------------------------------
560 PlusStatus ClariusBLEPrivate::RetrieveCharacteristic(
561  const GattDeviceService& service,
562  const guid charUuid,
563  GattCharacteristic& characteristic)
564 {
565  IAsyncOperation<GattCharacteristicsResult> getCharsOp =
566  service.GetCharacteristicsForUuidAsync(charUuid, BluetoothCacheMode::Uncached);
567  if (await_async(getCharsOp) != PLUS_SUCCESS)
568  {
569  std::stringstream msg;
570  msg << "ClariusBLEPrivate::RetrieveCharacteristic async operation failed for UUID";
571  msg << uuid_to_string(charUuid);
572  this->LastError = msg.str();
573  return PLUS_FAIL;
574  }
575 
576  // check that GetCharacteristicsForUuidAsync returned a successful status
577  GattCharacteristicsResult charsResult = getCharsOp.GetResults();
578  if (charsResult.Status() != GattCommunicationStatus::Success)
579  {
580  std::stringstream msg;
581  msg << "ClariusBLEPrivate::RetrieveCharacteristic failed for UUID " << uuid_to_string(charUuid)
582  << " with non-successful GATT communication. Status was: "
583  << this->GattCommunicationStatusToString(charsResult.Status());
584  this->LastError = msg.str();
585  return PLUS_FAIL;
586  }
587 
588  // check that at least one characteristic was found for this uuid
589  if (!charsResult.Characteristics().Size())
590  {
591  std::stringstream msg;
592  msg << "ClariusBLEPrivate::RetrieveCharacteristic failed for UUID " << uuid_to_string(charUuid)
593  << ". No characteristics with this UUID were found.";
594  this->LastError = msg.str();
595  return PLUS_FAIL;
596  }
597 
598  characteristic = *charsResult.Characteristics().First();
599  return PLUS_SUCCESS;
600 }
601 
602 //-----------------------------------------------------------------------------
603 void ClariusBLEPrivate::ProcessWifiInfo(std::string info)
604 {
605  std::unique_lock<std::mutex> wifiInfoLock(this->WifiInfoMutex);
606  std::vector<std::string> infoList = this->TokenizeString(info, '\n');
607  if (infoList.size() == 1)
608  {
609  // wifi disabled, so only state returned
610  if (infoList.at(0).find("state") == std::string::npos)
611  {
612  throw std::runtime_error("Format of ClariusWifiInfo string has changed. Please report "
613  "this info the the PLUS developers");
614  }
615 
616  ClariusWifiInfo newInfo;
617  newInfo.Ready = false;
618  this->WifiInfo = newInfo;
619  return;
620  }
621  else if (infoList.size() != 10)
622  {
623  throw std::runtime_error("Format of ClariusWifiInfo string has changed. Please report "
624  "this info the the PLUS developers");
625  }
626 
627  ClariusWifiInfo newInfo;
628 
629  // parse IsConnected (= Clarius "state")
630  std::string stateStr = infoList.at(0);
631  newInfo.Ready = (stateStr.find("connected") != std::string::npos);
632 
633  // parse IsAvailable
634  std::string availStr = infoList.at(7);
635  std::string availTag = "avail: ";
636  availStr = availStr.replace(0, availTag.length(), "");
637  if (availStr == "available")
638  {
640  }
641  else if (availStr == "listen")
642  {
644  }
645  else
646  {
648  }
649 
650  // parse wifi Mode
651  std::string apStr = infoList.at(1);
652  if (apStr.find("true") != std::string::npos)
653  {
655  }
656  else
657  {
658  newInfo.WifiMode = ClariusWifiMode::LAN;
659  }
660 
661  // parse SSID
662  std::string ssidStr = infoList.at(2);
663  std::string ssidTag = "ssid: ";
664  newInfo.SSID = ssidStr.replace(0, ssidTag.length(), "");
665 
666  // parse Password
667  std::string pwStr = infoList.at(3);
668  std::string pwTag = "pw: ";
669  newInfo.Password = pwStr.replace(0, pwTag.length(), "");
670 
671  // parse IPv4
672  std::string ipv4Str = infoList.at(4);
673  std::string ipv4Tag = "ip4: ";
674  newInfo.IPv4 = ipv4Str.replace(0, ipv4Tag.length(), "");
675 
676  // parse MacAddress
677  std::string macStr = infoList.at(9);
678  std::string macTag = "mac: ";
679  newInfo.MacAddress = macStr.replace(0, macTag.length(), "");
680 
681  // parse ControlPort
682  std::string ctlStr = infoList.at(5);
683  std::string ctlTag = "ctl: ";
684  std::string ctlStrInt = ctlStr.replace(0, ctlTag.length(), "");
685  newInfo.ControlPort = std::stoi(ctlStrInt);
686 
687  // parse CastPort
688  std::string castStr = infoList.at(6);
689  std::string castTag = "cast: ";
690  std::string castStrInt = castStr.replace(0, castTag.length(), "");
691  newInfo.CastPort = std::stoi(castStrInt);
692 
693  // parse Channel
694  std::string channelStr = infoList.at(8);
695  std::string channelTag = "channel: ";
696  std::string channelStrInt = channelStr.replace(0, channelTag.length(), "");
697  newInfo.Channel = std::stoi(channelStrInt);
698 
699  // set WifiInfo member variable and set variable
700  this->WifiInfo = newInfo;
701  this->WifiInfoSet = true;
702 }
703 
704 //-----------------------------------------------------------------------------
705 std::vector<std::string> ClariusBLEPrivate::TokenizeString(std::string str, const char delimiter)
706 {
707  std::stringstream ss(str);
708  std::vector<std::string> tokens;
709  std::string tmp;
710 
711  while (getline(ss, tmp, delimiter))
712  {
713  tokens.push_back(tmp);
714  }
715 
716  return tokens;
717 }
718 
719 //-----------------------------------------------------------------------------
720 // ClariusBLE method definitions
721 //-----------------------------------------------------------------------------
723  : _impl(std::make_unique<ClariusBLEPrivate>(this))
724 {
725 }
726 
727 //-----------------------------------------------------------------------------
729 {
730  this->RequestProbeOff();
731  this->Disconnect();
732 }
733 
734 //-----------------------------------------------------------------------------
735 PlusStatus ClariusBLE::FindBySerial(std::string serialNum)
736 {
737  std::string fullBleName = "CUS-" + serialNum;
738  _impl->ProbeId = to_wide_string(fullBleName);
739 
740  DeviceWatcher deviceWatcher{ nullptr };
741  winrt::hstring aqsFilter{ BluetoothLEDevice::GetDeviceSelectorFromPairingState(true) };
742  deviceWatcher = DeviceInformation::CreateWatcher(
743  aqsFilter,
744  nullptr,
745  DeviceInformationKind::AssociationEndpoint
746  );
747  deviceWatcher.Added({ _impl.get(), &ClariusBLEPrivate::DeviceAdded });
748  deviceWatcher.Updated({ _impl.get(), &ClariusBLEPrivate::DeviceUpdated });
749 
750  std::future<void> deviceInfoFuture = _impl->DeviceInfoPromise.get_future();
751  deviceWatcher.Start();
752 
753  if (deviceInfoFuture.wait_for(std::chrono::milliseconds(1000)) == std::future_status::ready)
754  {
755  deviceWatcher.Stop();
756  return PLUS_SUCCESS;
757  }
758 
759  deviceWatcher.Stop();
760  return PLUS_FAIL;
761 }
762 
763 //-----------------------------------------------------------------------------
764 std::vector<std::string> ClariusBLE::RetrieveFoundProbeIds()
765 {
766  return _impl->FoundProbeIds;
767 }
768 
769 //-----------------------------------------------------------------------------
771 {
772  return (_impl->Device && _impl->Device.ConnectionStatus() == BluetoothConnectionStatus::Connected);
773 }
774 
775 //-----------------------------------------------------------------------------
777 {
778  if (!_impl->DeviceInfo)
779  {
780  _impl->LastError = "Attempted to call ClariusBLE::Connect with unset m_deviceInfo, "
781  "ensure ClariusBLE::FindBySerial is called successfully before calling Connect";
782  return PLUS_FAIL;
783  }
784  if (_impl->Device && _impl->Device.ConnectionStatus() == BluetoothConnectionStatus::Connected)
785  {
786  _impl->LastError = "Connect called but probe is already connected";
787  return PLUS_FAIL;
788  }
789 
790  // get BLE device
791  PlusStatus success = PLUS_FAIL;
792 
793  int connectionAttemptCount = 0;
794  int retryDelayMs = 1000;
795  while (connectionAttemptCount < MAX_BLE_CONNECTION_ATTEMPTS && !success)
796  {
797  if (connectionAttemptCount > 0)
798  {
799  LOG_DEBUG("Attempt #" << connectionAttemptCount << " failed. Last error: \"" << this->GetLastError() << "\"");
800  this->CloseConnection();
801  std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
802  }
803 
804  ++connectionAttemptCount;
805  LOG_DEBUG("Trying to connect: Attempt #" << connectionAttemptCount);
806 
807  IAsyncOperation<BluetoothLEDevice> deviceOp = BluetoothLEDevice::FromIdAsync(_impl->DeviceInfo.Id());
808  if (await_async(deviceOp) != PLUS_SUCCESS)
809  {
810  _impl->LastError = "Failed to connect to device, async operation to get device failed";
811  continue;
812  }
813  _impl->Device = deviceOp.GetResults();
814  if (_impl->Device == nullptr)
815  {
816  _impl->LastError = "Failed to connect to device, unable to find device";
817  continue;
818  }
819 
820  // setup power & wifi services
821  if (_impl->SetupPowerService() != PLUS_SUCCESS)
822  {
823  continue;
824  }
825  if (_impl->SetupWifiService() != PLUS_SUCCESS)
826  {
827  continue;
828  }
829 
830  // initialize power state & wifi info correctly
831  if (_impl->InitializeState() != PLUS_SUCCESS)
832  {
833  continue;
834  }
835 
836  success = PLUS_SUCCESS;
837  LOG_DEBUG("Device connected: Attempt #" << connectionAttemptCount);
838  }
839 
840  return success;
841 }
842 
843 //-----------------------------------------------------------------------------
845 {
846  if (_impl->PowerService)
847  {
848  _impl->PowerService.Close();
849  }
850  if (_impl->WifiService)
851  {
852  _impl->WifiService.Close();
853  }
854  if (_impl->Device)
855  {
856  _impl->Device.Close();
857  }
858 
859  _impl->PowerPublishedChar = nullptr;
860  _impl->PowerRequestChar = nullptr;
861  _impl->WifiPublishedChar = nullptr;
862  _impl->WifiRequestChar = nullptr;
863 
864  _impl->PowerService = nullptr;
865  _impl->WifiService = nullptr;
866 
867  _impl->Device = nullptr;
868 
869  return PLUS_SUCCESS;
870 }
871 
872 //-----------------------------------------------------------------------------
874 {
875  if (!IsProbeConnected())
876  {
877  // already disconnected, nothing to do here...
878  return PLUS_SUCCESS;
879  }
880 
881  this->CloseConnection();
882 
883  _impl->DeviceInfo = nullptr;
884 
885  return PLUS_SUCCESS;
886 }
887 
888 //-----------------------------------------------------------------------------
890 {
891  return _impl->PowerState;
892 }
893 
894 //-----------------------------------------------------------------------------
896 {
897  if (!IsProbeConnected())
898  {
899  _impl->LastError = "RequestProbeOn called but no probe is connected";
900  return PLUS_FAIL;
901  }
902 
903  DataWriter writer;
904  writer.WriteByte(0x01); // 0x01 = request power on
905  IBuffer buf = writer.DetachBuffer();
906 
907  IAsyncOperation<GattWriteResult> writeOp =
908  _impl->PowerRequestChar.WriteValueWithResultAsync(buf, GattWriteOption::WriteWithResponse);
909  if (await_async(writeOp) != PLUS_SUCCESS)
910  {
911  _impl->LastError = "RequestProbeOn Failed to async write to PowerRequestChar";
912  return PLUS_FAIL;
913  }
914 
915  GattWriteResult writeResult = writeOp.GetResults();
916  if (writeResult.Status() != GattCommunicationStatus::Success)
917  {
918  std::stringstream msg;
919  msg << "ClariusBLE::ProbeOn failed with non-successful GATT communication. Status was: "
920  << _impl->GattCommunicationStatusToString(writeResult.Status());
921  _impl->LastError = msg.str();
922  return PLUS_FAIL;
923  }
924 
925  return PLUS_SUCCESS;
926 }
927 
928 //-----------------------------------------------------------------------------
930 {
931  if (!IsProbeConnected())
932  {
933  _impl->LastError = "RequestProbeOff called but no probe is connected";
934  return PLUS_FAIL;
935  }
936 
937  DataWriter writer;
938  writer.WriteByte(0x00); // 0x00 = request power off
939  IBuffer buf = writer.DetachBuffer();
940 
941  if (_impl->PowerRequestChar)
942  {
943  IAsyncOperation<GattWriteResult> writeOp =
944  _impl->PowerRequestChar.WriteValueWithResultAsync(buf, GattWriteOption::WriteWithResponse);
945  if (await_async(writeOp) != PLUS_SUCCESS)
946  {
947  _impl->LastError = "RequestProbeOff Failed to async write to PowerRequestChar";
948  return PLUS_FAIL;
949  }
950 
951  GattWriteResult writeResult = writeOp.GetResults();
952 
953  if (writeResult.Status() != GattCommunicationStatus::Success)
954  {
955  std::stringstream msg;
956  msg << "ClariusBLE::ProbeOff failed with non-successful GATT communication. Status was: "
957  << _impl->GattCommunicationStatusToString(writeResult.Status());
958  _impl->LastError = msg.str();
959  return PLUS_FAIL;
960  }
961  }
962  else
963  {
964  LOG_ERROR("Unable to send probe off signal. Power request characteristic not availiable.");
965  return PLUS_FAIL;
966  }
967 
968  return PLUS_SUCCESS;
969 }
970 
971 //-----------------------------------------------------------------------------
973 {
974  if (_impl->WifiInfoSet)
975  {
976  // wifi info has already been set
977  return true;
978  }
979 
980  // check to see if wifi info is already availiable in the case that the probe is already on
981  // and wifi is availiable
982  this->ReadWifiInfo();
983  if (_impl->WifiInfoSet)
984  {
985  return true;
986  }
987 
988  // wait for wifi info to be set
989  std::future<void> wifiInfoFuture = _impl->WifiInfoPromise.get_future();
990 
991  std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now();
992  std::chrono::duration<double> duration;
993  do
994  {
995  // TODO: WifiStateChanged callback is not currently being called.
996  // After waiting for the timeout, try once to see if WifiPublishedChar has been updated.
997  this->ReadWifiInfo();
998  if (_impl->WifiInfoSet || wifiInfoFuture.wait_for(std::chrono::seconds(POWER_ON_POLL_INTERVAL_SEC)) == std::future_status::ready)
999  {
1000  duration = std::chrono::steady_clock::now() - start_time;
1001  LOG_DEBUG("WiFi info set: " << duration.count() << "s");
1002  return true;
1003  }
1004 
1005  duration = std::chrono::steady_clock::now() - start_time;
1006 
1007  LOG_DEBUG("Waiting for WiFi info: " << duration.count() << "s");
1008  } while (duration.count() < POWER_ON_TIMEOUT_SEC);
1009 
1010  std::stringstream msg;
1011  msg << "Clarius probe took longer than the maximum allowed " << POWER_ON_TIMEOUT_SEC
1012  << " to boot up and provide Wifi info, please try again" << std::endl;
1013  _impl->LastError = msg.str();
1014  return false;
1015 }
1016 
1017 //-----------------------------------------------------------------------------
1019 {
1020  IAsyncOperation<GattReadResult> wifiOp =
1021  _impl->WifiPublishedChar.ReadValueAsync(BluetoothCacheMode::Uncached);
1022  if (await_async(wifiOp) == PLUS_SUCCESS)
1023  {
1024  GattReadResult wifiResult = wifiOp.GetResults();
1025  if (wifiResult.Status() != GattCommunicationStatus::Success)
1026  {
1027  std::stringstream msg;
1028  msg << "ClariusBLE::ReadWifiInfo failed with non-successful GATT communication. Status was: "
1029  << _impl->GattCommunicationStatusToString(wifiResult.Status());
1030  _impl->LastError = msg.str();
1031  return PLUS_FAIL;
1032  }
1033 
1034  DataReader wifiReader = DataReader::FromBuffer(wifiResult.Value());
1035  winrt::hstring wifiHString = wifiReader.ReadString(wifiReader.UnconsumedBufferLength());
1036  std::string wifiStr = to_narrow_string(wifiHString);
1037  _impl->ProcessWifiInfo(wifiStr);
1038  return PLUS_SUCCESS;
1039  }
1040 
1041  return PLUS_FAIL;
1042 }
1043 
1044 //-----------------------------------------------------------------------------
1046 {
1047  if (!IsProbeConnected())
1048  {
1049  _impl->LastError = "ConfigureWifiAP called but no probe is connected";
1050  return PLUS_FAIL;
1051  }
1052 
1053  std::stringstream ss;
1054  ss << "ap: true\n";
1055  ss << "channel: auto\n";
1056 
1057  DataWriter writer;
1058  writer.WriteString(to_wide_string(ss.str()));
1059  IBuffer buf = writer.DetachBuffer();
1060 
1061  IAsyncOperation<GattWriteResult> writeOp =
1062  _impl->WifiRequestChar.WriteValueWithResultAsync(buf, GattWriteOption::WriteWithResponse);
1063  if (await_async(writeOp) != PLUS_SUCCESS)
1064  {
1065  _impl->LastError = "ConfigureWifiAP Failed to write to WifiRequestChar";
1066  return PLUS_FAIL;
1067  }
1068 
1069  GattWriteResult writeResult = writeOp.GetResults();
1070  if (writeResult.Status() != GattCommunicationStatus::Success)
1071  {
1072  std::stringstream msg;
1073  msg << "ClariusBLE::ConfigureWifiAP failed with non-successful GATT communication. Status was: "
1074  << _impl->GattCommunicationStatusToString(writeResult.Status());
1075  _impl->LastError = msg.str();
1076  return PLUS_FAIL;
1077  }
1078 
1079  return PLUS_SUCCESS;
1080 }
1081 
1082 //-----------------------------------------------------------------------------
1083 PlusStatus ClariusBLE::ConfigureWifiLAN(std::string ssid, std::string password)
1084 {
1085  if (!IsProbeConnected())
1086  {
1087  _impl->LastError = "ConfigureWifiLAN called but no probe is connected";
1088  return PLUS_FAIL;
1089  }
1090 
1091  if (!ssid.length())
1092  {
1093  _impl->LastError = "ClariusBLE::ConfigureWifiLAN called with ssid parameter of invalid length (0)";
1094  return PLUS_FAIL;
1095  }
1096  if (!password.length())
1097  {
1098  _impl->LastError = "ClariusBLE::ConfigureWifiLAN called with password parameter of invalid length (0)";
1099  return PLUS_FAIL;
1100  }
1101 
1102  std::stringstream ss;
1103  ss << "ap: false\n";
1104  ss << "ssid: " << ssid << "\n";
1105  ss << "pw: " << password << "\n";
1106 
1107  DataWriter writer;
1108  writer.WriteString(to_wide_string(ss.str()));
1109  IBuffer buf = writer.DetachBuffer();
1110 
1111  IAsyncOperation<GattWriteResult> writeOp =
1112  _impl->PowerRequestChar.WriteValueWithResultAsync(buf, GattWriteOption::WriteWithResponse);
1113  if (await_async(writeOp) != PLUS_SUCCESS)
1114  {
1115  _impl->LastError = "ConfigureWifiLAN failed to write to WifiRequestChar";
1116  return PLUS_FAIL;
1117  }
1118 
1119  GattWriteResult writeResult = writeOp.GetResults();
1120  if (writeResult.Status() != GattCommunicationStatus::Success)
1121  {
1122  std::stringstream msg;
1123  msg << "ClariusBLE::ConfigureWifiLAN failed with non-successful GATT communication. Status was: "
1124  << _impl->GattCommunicationStatusToString(writeResult.Status());
1125  _impl->LastError = msg.str();
1126  return PLUS_FAIL;
1127  }
1128 
1129  return PLUS_SUCCESS;
1130 }
1131 
1132 //-----------------------------------------------------------------------------
1133 std::pair<PlusStatus, ClariusWifiInfo> ClariusBLE::GetWifiInfo()
1134 {
1135  if (!_impl->WifiInfoSet)
1136  {
1137  _impl->LastError = "Clarius wifi info not set yet...";
1138  return { PLUS_FAIL, {} };
1139  }
1140 
1141  return { PLUS_SUCCESS, _impl->GetWifiInfo() };
1142 }
1143 
1144 //-----------------------------------------------------------------------------
1146 {
1147  return _impl->LastError;
1148 }
static const winrt::guid WIFI_SERVICE_UUID
Definition: ClariusBLE.cxx:59
PlusStatus Disconnect()
Definition: ClariusBLE.cxx:873
std::string Password
Definition: ClariusBLE.h:46
PlusStatus FindBySerial(std::string serialNum)
Definition: ClariusBLE.cxx:735
igsioStatus PlusStatus
Definition: PlusCommon.h:40
std::string IPv4
Definition: ClariusBLE.h:47
std::string MacAddress
Definition: ClariusBLE.h:48
#define PLUS_FAIL
Definition: PlusCommon.h:43
std::string to_narrow_string(std::wstring wstr)
Definition: ClariusBLE.cxx:146
PlusStatus CloseConnection()
Definition: ClariusBLE.cxx:844
std::string uuid_to_string(const winrt::guid &uuid)
Definition: ClariusBLE.cxx:161
PlusStatus ConfigureWifiAP()
std::pair< PlusStatus, ClariusWifiInfo > GetWifiInfo()
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
PlusStatus await_async(TAsyncOp op)
Definition: ClariusBLE.cxx:84
static const winrt::guid POWER_SERVICE_UUID
Definition: ClariusBLE.cxx:47
bool IsProbePowered()
Definition: ClariusBLE.cxx:889
PlusStatus RequestProbeOn()
Definition: ClariusBLE.cxx:895
const char int const char * password
Definition: phidget22.h:2552
static const int MAX_BLE_CONNECTION_ATTEMPTS
Definition: ClariusBLE.cxx:76
ClariusAvailability Available
Definition: ClariusBLE.h:43
bool AwaitWifiInfoReady()
Definition: ClariusBLE.cxx:972
ClariusWifiMode WifiMode
Definition: ClariusBLE.h:44
static const winrt::guid WIFI_REQUEST_CHAR_UUID
Definition: ClariusBLE.cxx:67
static const uint64_t POWER_ON_TIMEOUT_SEC
Definition: ClariusBLE.cxx:70
std::string GetLastError()
PlusStatus Connect()
Definition: ClariusBLE.cxx:776
static const uint64_t POWER_ON_POLL_INTERVAL_SEC
Definition: ClariusBLE.cxx:71
PlusStatus ReadWifiInfo()
static const uint64_t BLE_OP_TIMEOUT_SEC
Definition: ClariusBLE.cxx:74
static const winrt::guid WIFI_PUBLISHED_CHAR_UUID
Definition: ClariusBLE.cxx:63
static const winrt::guid POWER_PUBLISHED_CHAR_UUID
Definition: ClariusBLE.cxx:51
PlusStatus ConfigureWifiLAN(std::string ssid, std::string password)
PlusStatus RequestProbeOff()
Definition: ClariusBLE.cxx:929
static const winrt::guid POWER_REQUEST_CHAR_UUID
Definition: ClariusBLE.cxx:55
std::wstring to_wide_string(std::string str)
Definition: ClariusBLE.cxx:139
std::string SSID
Definition: ClariusBLE.h:45
std::vector< std::string > RetrieveFoundProbeIds()
Definition: ClariusBLE.cxx:764
bool IsProbeConnected()
Definition: ClariusBLE.cxx:770