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