brilliantsole 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +16 -10
  2. package/assets/3d/anchor.glb +0 -0
  3. package/assets/3d/coin.glb +0 -0
  4. package/assets/3d/glasses.glb +0 -0
  5. package/assets/3d/rightHand.glb +0 -0
  6. package/assets/audio/bounceMedium.wav +0 -0
  7. package/assets/audio/bounceStrong.wav +0 -0
  8. package/assets/audio/bounceWeak.wav +0 -0
  9. package/assets/audio/coin.wav +0 -0
  10. package/assets/audio/getUp.wav +0 -0
  11. package/assets/audio/grab.wav +0 -0
  12. package/assets/audio/kick.wav +0 -0
  13. package/assets/audio/platterFadeIn old.wav +0 -0
  14. package/assets/audio/platterFadeIn.wav +0 -0
  15. package/assets/audio/platterFadeOut.wav +0 -0
  16. package/assets/audio/punch.wav +0 -0
  17. package/assets/audio/punchSqueak.wav +0 -0
  18. package/assets/audio/purr.wav +0 -0
  19. package/assets/audio/purrFadeOut.wav +0 -0
  20. package/assets/audio/release.wav +0 -0
  21. package/assets/audio/splat.wav +0 -0
  22. package/assets/audio/stomp.wav +0 -0
  23. package/assets/images/ukaton-pressure-0.svg +9 -0
  24. package/assets/images/ukaton-pressure-1.svg +9 -0
  25. package/assets/images/ukaton-pressure-10.svg +9 -0
  26. package/assets/images/ukaton-pressure-11.svg +9 -0
  27. package/assets/images/ukaton-pressure-12.svg +9 -0
  28. package/assets/images/ukaton-pressure-13.svg +9 -0
  29. package/assets/images/ukaton-pressure-14.svg +9 -0
  30. package/assets/images/ukaton-pressure-15.svg +9 -0
  31. package/assets/images/ukaton-pressure-2.svg +9 -0
  32. package/assets/images/ukaton-pressure-3.svg +9 -0
  33. package/assets/images/ukaton-pressure-4.svg +9 -0
  34. package/assets/images/ukaton-pressure-5.svg +9 -0
  35. package/assets/images/ukaton-pressure-6.svg +9 -0
  36. package/assets/images/ukaton-pressure-7.svg +9 -0
  37. package/assets/images/ukaton-pressure-8.svg +9 -0
  38. package/assets/images/ukaton-pressure-9.svg +9 -0
  39. package/assets/images/ukaton-right-insole.svg +798 -0
  40. package/build/brilliantsole.cjs +2870 -882
  41. package/build/brilliantsole.cjs.map +1 -1
  42. package/build/brilliantsole.js +2477 -782
  43. package/build/brilliantsole.js.map +1 -1
  44. package/build/brilliantsole.ls.js +2260 -592
  45. package/build/brilliantsole.ls.js.map +1 -1
  46. package/build/brilliantsole.min.js +1 -1
  47. package/build/brilliantsole.min.js.map +1 -1
  48. package/build/brilliantsole.module.d.ts +302 -116
  49. package/build/brilliantsole.module.js +2468 -782
  50. package/build/brilliantsole.module.js.map +1 -1
  51. package/build/brilliantsole.module.min.d.ts +302 -116
  52. package/build/brilliantsole.module.min.js +1 -1
  53. package/build/brilliantsole.module.min.js.map +1 -1
  54. package/build/brilliantsole.node.module.d.ts +295 -113
  55. package/build/brilliantsole.node.module.js +2860 -882
  56. package/build/brilliantsole.node.module.js.map +1 -1
  57. package/build/dts/BS-output.d.ts +10 -0
  58. package/build/dts/BS.d.ts +21 -9
  59. package/build/dts/CameraManager.d.ts +72 -0
  60. package/build/dts/Device.d.ts +53 -16
  61. package/build/dts/DeviceInformationManager.d.ts +4 -4
  62. package/build/dts/DeviceManager.d.ts +3 -0
  63. package/build/dts/FileTransferManager.d.ts +18 -8
  64. package/build/dts/InformationManager.d.ts +8 -5
  65. package/build/dts/TfliteManager.d.ts +22 -2
  66. package/build/dts/WifiManager.d.ts +61 -0
  67. package/build/dts/connection/BaseConnectionManager.d.ts +37 -3
  68. package/build/dts/connection/ClientConnectionManager.d.ts +11 -2
  69. package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +1 -0
  70. package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +3 -1
  71. package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +2 -0
  72. package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +2 -2
  73. package/build/dts/connection/udp/UDPConnectionManager.d.ts +28 -0
  74. package/build/dts/connection/webSocket/WebSocketConnectionManager.d.ts +25 -0
  75. package/build/dts/devicePair/DevicePair.d.ts +14 -10
  76. package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +8 -4
  77. package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +2 -2
  78. package/build/dts/scanner/BaseScanner.d.ts +4 -1
  79. package/build/dts/scanner/NobleScanner.d.ts +2 -1
  80. package/build/dts/sensor/MotionSensorDataManager.d.ts +5 -2
  81. package/build/dts/sensor/SensorDataManager.d.ts +5 -4
  82. package/build/dts/server/BaseClient.d.ts +6 -3
  83. package/build/dts/server/ServerUtils.d.ts +1 -1
  84. package/build/dts/server/websocket/WebSocketUtils.d.ts +1 -1
  85. package/build/dts/utils/CenterOfPressureHelper.d.ts +2 -2
  86. package/build/dts/utils/Console.d.ts +2 -0
  87. package/build/dts/utils/MathUtils.d.ts +2 -0
  88. package/build/dts/utils/ThrottleUtils.d.ts +2 -0
  89. package/build/dts/vibration/VibrationManager.d.ts +19 -2
  90. package/build/index.d.ts +299 -113
  91. package/build/index.node.d.ts +292 -110
  92. package/examples/3d/scene.html +19 -5
  93. package/examples/3d/script.js +90 -17
  94. package/examples/3d-generic/index.html +144 -0
  95. package/examples/3d-generic/script.js +266 -0
  96. package/examples/balance/script.js +2 -1
  97. package/examples/basic/index.html +232 -18
  98. package/examples/basic/script.js +746 -106
  99. package/examples/bottango/index.html +11 -1
  100. package/examples/bottango/script.js +2 -2
  101. package/examples/center-of-pressure/index.html +114 -114
  102. package/examples/center-of-pressure/script.js +1 -1
  103. package/examples/device-pair/index.html +58 -58
  104. package/examples/device-pair/script.js +12 -8
  105. package/examples/edge-impulse/script.js +135 -44
  106. package/examples/edge-impulse-test/README.md +11 -0
  107. package/examples/edge-impulse-test/edge-impulse-standalone.js +7228 -0
  108. package/examples/edge-impulse-test/edge-impulse-standalone.wasm +0 -0
  109. package/examples/edge-impulse-test/index.html +75 -0
  110. package/examples/edge-impulse-test/run-impulse.js +135 -0
  111. package/examples/edge-impulse-test/script.js +200 -0
  112. package/examples/gloves/edge-impulse-standalone.js +7228 -0
  113. package/examples/gloves/edge-impulse-standalone.wasm +0 -0
  114. package/examples/gloves/index.html +119 -0
  115. package/examples/gloves/run-impulse.js +135 -0
  116. package/examples/gloves/scene.html +124 -0
  117. package/examples/gloves/script.js +931 -0
  118. package/examples/graph/index.html +11 -1
  119. package/examples/graph/script.js +94 -37
  120. package/examples/pressure/index.html +180 -12
  121. package/examples/pressure/script.js +144 -7
  122. package/examples/punch/index.html +135 -0
  123. package/examples/punch/punch.tflite +0 -0
  124. package/examples/punch/script.js +169 -0
  125. package/examples/recording/index.html +191 -183
  126. package/examples/server/index.html +109 -23
  127. package/examples/server/script.js +322 -111
  128. package/examples/ukaton-firmware-update/index.html +20 -0
  129. package/examples/ukaton-firmware-update/manifest.json +11 -0
  130. package/examples/ukaton-firmware-update/merged-firmware.bin +0 -0
  131. package/examples/utils/aframe/aframe-master.min.js +2 -0
  132. package/examples/utils/aframe/bs-vibration.js +150 -0
  133. package/examples/utils/aframe/force-pushable.js +80 -0
  134. package/examples/utils/aframe/grabbable-anchor.js +46 -0
  135. package/examples/utils/aframe/grabbable-listener.js +31 -0
  136. package/examples/utils/aframe/grabbable-physics-body.js +190 -0
  137. package/examples/utils/aframe/grow-shrink.js +25 -0
  138. package/examples/utils/aframe/hand-punch.js +119 -0
  139. package/examples/utils/aframe/my-obb-collider.js +293 -0
  140. package/examples/utils/aframe/occlude-hand-tracking-controls.js +47 -0
  141. package/examples/utils/aframe/occlude-mesh.js +42 -0
  142. package/examples/utils/aframe/palm-up-detector.js +47 -0
  143. package/examples/utils/aframe/shadow-material.js +20 -0
  144. package/examples/utils/aframe/soft-shadow-light.js +9 -0
  145. package/examples/webxr/script.js +3 -3
  146. package/examples/webxr-2/assets/3d/soccerBall.glb +0 -0
  147. package/examples/webxr-2/assets/audio/shellBounce.wav +0 -0
  148. package/examples/webxr-2/assets/audio/shellHit.wav +0 -0
  149. package/examples/webxr-2/assets/audio/shellKick.wav +0 -0
  150. package/examples/webxr-2/assets/audio/soccerBounce.wav +0 -0
  151. package/examples/webxr-2/assets/audio/soccerKick.mp3 +0 -0
  152. package/examples/webxr-2/assets/images/shellTexture.png +0 -0
  153. package/examples/webxr-2/components/bs-ankle.js +337 -0
  154. package/examples/webxr-2/components/coin.js +84 -0
  155. package/examples/webxr-2/components/custom-wrap.js +17 -0
  156. package/examples/webxr-2/components/goomba.js +3250 -0
  157. package/examples/webxr-2/components/init-shell-material.js +215 -0
  158. package/examples/webxr-2/components/platter.js +172 -0
  159. package/examples/webxr-2/components/shell.js +374 -0
  160. package/examples/webxr-2/components/soccer-ball.js +250 -0
  161. package/examples/webxr-2/components/squashed-goomba.js +249 -0
  162. package/examples/webxr-2/edge-impulse-standalone.js +7228 -0
  163. package/examples/webxr-2/edge-impulse-standalone.wasm +0 -0
  164. package/examples/webxr-2/index.html +996 -0
  165. package/examples/webxr-2/kick.tflite +0 -0
  166. package/examples/webxr-2/kick2.tflite +0 -0
  167. package/examples/webxr-2/run-impulse.js +135 -0
  168. package/examples/webxr-2/script.js +384 -0
  169. package/package.json +2 -1
  170. package/src/.prettierrc +4 -0
  171. package/src/BS.ts +66 -9
  172. package/src/CameraManager.ts +499 -0
  173. package/src/Device.ts +620 -92
  174. package/src/DeviceInformationManager.ts +22 -11
  175. package/src/DeviceManager.ts +94 -25
  176. package/src/FileTransferManager.ts +146 -21
  177. package/src/FirmwareManager.ts +1 -1
  178. package/src/InformationManager.ts +62 -20
  179. package/src/TfliteManager.ts +172 -26
  180. package/src/WifiManager.ts +323 -0
  181. package/src/connection/BaseConnectionManager.ts +145 -30
  182. package/src/connection/ClientConnectionManager.ts +47 -11
  183. package/src/connection/bluetooth/BluetoothConnectionManager.ts +14 -3
  184. package/src/connection/bluetooth/NobleConnectionManager.ts +155 -42
  185. package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +104 -35
  186. package/src/connection/bluetooth/bluetoothUUIDs.ts +40 -13
  187. package/src/connection/udp/UDPConnectionManager.ts +356 -0
  188. package/src/connection/websocket/WebSocketConnectionManager.ts +282 -0
  189. package/src/devicePair/DevicePair.ts +145 -49
  190. package/src/devicePair/DevicePairPressureSensorDataManager.ts +72 -24
  191. package/src/devicePair/DevicePairSensorDataManager.ts +5 -5
  192. package/src/scanner/BaseScanner.ts +49 -11
  193. package/src/scanner/NobleScanner.ts +81 -17
  194. package/src/sensor/BarometerSensorDataManager.ts +1 -1
  195. package/src/sensor/MotionSensorDataManager.ts +22 -7
  196. package/src/sensor/PressureSensorDataManager.ts +47 -13
  197. package/src/sensor/SensorConfigurationManager.ts +75 -24
  198. package/src/sensor/SensorDataManager.ts +107 -26
  199. package/src/server/BaseClient.ts +192 -37
  200. package/src/server/BaseServer.ts +201 -43
  201. package/src/server/ServerUtils.ts +39 -9
  202. package/src/server/udp/UDPServer.ts +74 -23
  203. package/src/server/udp/UDPUtils.ts +9 -2
  204. package/src/server/websocket/WebSocketClient.ts +30 -9
  205. package/src/server/websocket/WebSocketServer.ts +1 -1
  206. package/src/server/websocket/WebSocketUtils.ts +4 -2
  207. package/src/utils/CenterOfPressureHelper.ts +5 -5
  208. package/src/utils/Console.ts +62 -9
  209. package/src/utils/MathUtils.ts +31 -1
  210. package/src/utils/ParseUtils.ts +25 -6
  211. package/src/utils/ThrottleUtils.ts +62 -0
  212. package/src/utils/Timer.ts +1 -1
  213. package/src/utils/checksum.ts +1 -1
  214. package/src/utils/mcumgr.js +1 -1
  215. package/src/vibration/VibrationManager.ts +166 -40
@@ -1 +1 @@
1
- {"version":3,"file":"brilliantsole.js","sources":["../node_modules/tslib/tslib.es6.js","../brilliantsole/utils/environment.ts","../brilliantsole/utils/Console.ts","../brilliantsole/utils/EventDispatcher.ts","../brilliantsole/utils/Timer.ts","../brilliantsole/utils/checksum.ts","../brilliantsole/utils/Text.ts","../brilliantsole/utils/ArrayBufferUtils.ts","../node_modules/auto-bind/index.js","../brilliantsole/FileTransferManager.ts","../brilliantsole/utils/MathUtils.ts","../brilliantsole/utils/RangeHelper.ts","../brilliantsole/utils/CenterOfPressureHelper.ts","../brilliantsole/utils/ArrayUtils.ts","../brilliantsole/sensor/PressureSensorDataManager.ts","../brilliantsole/sensor/MotionSensorDataManager.ts","../brilliantsole/sensor/BarometerSensorDataManager.ts","../brilliantsole/utils/ParseUtils.ts","../brilliantsole/sensor/SensorDataManager.ts","../brilliantsole/sensor/SensorConfigurationManager.ts","../brilliantsole/TfliteManager.ts","../brilliantsole/DeviceInformationManager.ts","../brilliantsole/InformationManager.ts","../brilliantsole/vibration/VibrationWaveformEffects.ts","../brilliantsole/vibration/VibrationManager.ts","../brilliantsole/connection/BaseConnectionManager.ts","../brilliantsole/utils/stringUtils.ts","../brilliantsole/utils/EventUtils.ts","../brilliantsole/connection/bluetooth/bluetoothUUIDs.ts","../brilliantsole/connection/bluetooth/BluetoothConnectionManager.ts","../brilliantsole/connection/bluetooth/WebBluetoothConnectionManager.ts","../brilliantsole/utils/cbor.js","../brilliantsole/utils/mcumgr.js","../brilliantsole/FirmwareManager.ts","../brilliantsole/DeviceManager.ts","../brilliantsole/Device.ts","../brilliantsole/devicePair/DevicePairPressureSensorDataManager.ts","../brilliantsole/devicePair/DevicePairSensorDataManager.ts","../brilliantsole/devicePair/DevicePair.ts","../brilliantsole/server/ServerUtils.ts","../brilliantsole/connection/ClientConnectionManager.ts","../brilliantsole/server/BaseClient.ts","../brilliantsole/server/websocket/WebSocketUtils.ts","../brilliantsole/server/websocket/WebSocketClient.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","type ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\" | \"__BRILLIANTSOLE__PROD__\";\nconst __BRILLIANTSOLE__ENVIRONMENT__: ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\";\n\n//@ts-expect-error\nconst isInProduction = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__PROD__\";\nconst isInDev = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__DEV__\";\n\n// https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts\nconst isInBrowser = typeof window !== \"undefined\" && typeof window?.document !== \"undefined\";\nconst isInNode = typeof process !== \"undefined\" && process?.versions?.node != null;\n\nconst userAgent = (isInBrowser && navigator.userAgent) || \"\";\n\nlet isBluetoothSupported = false;\nif (isInBrowser) {\n isBluetoothSupported = Boolean(navigator.bluetooth);\n} else if (isInNode) {\n isBluetoothSupported = true;\n}\n\nconst isInBluefy = isInBrowser && /Bluefy/i.test(userAgent);\nconst isInWebBLE = isInBrowser && /WebBLE/i.test(userAgent);\n\nconst isAndroid = isInBrowser && /Android/i.test(userAgent);\nconst isSafari = isInBrowser && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);\n\nconst isIOS = isInBrowser && /iPad|iPhone|iPod/i.test(userAgent);\nconst isMac = isInBrowser && /Macintosh/i.test(userAgent);\n\n// @ts-expect-error\nconst isInLensStudio = !isInBrowser && !isInNode && typeof global !== \"undefined\" && typeof Studio !== \"undefined\";\n\nexport {\n isInDev,\n isInProduction,\n isInBrowser,\n isInNode,\n isAndroid,\n isInBluefy,\n isInWebBLE,\n isSafari,\n isInLensStudio,\n isIOS,\n isMac,\n isBluetoothSupported,\n};\n","import { isInDev, isInLensStudio } from \"./environment.ts\";\n\ndeclare var Studio: any | undefined;\n\nexport type LogFunction = (...data: any[]) => void;\nexport type AssertLogFunction = (condition: boolean, ...data: any[]) => void;\n\nexport interface ConsoleLevelFlags {\n log?: boolean;\n warn?: boolean;\n error?: boolean;\n assert?: boolean;\n table?: boolean;\n}\n\ninterface ConsoleLike {\n log?: LogFunction;\n warn?: LogFunction;\n error?: LogFunction;\n assert?: AssertLogFunction;\n table?: LogFunction;\n}\n\nvar __console: ConsoleLike;\nif (isInLensStudio) {\n const log = function (...args: any[]) {\n Studio.log(args.map((value) => new String(value)).join(\",\"));\n };\n __console = {};\n __console.log = log;\n __console.warn = log.bind(__console, \"WARNING\");\n __console.error = log.bind(__console, \"ERROR\");\n} else {\n __console = console;\n}\n\n// console.assert not supported in WebBLE\nif (!__console.assert) {\n const assert: AssertLogFunction = (condition, ...data) => {\n if (!condition) {\n __console.warn!(...data);\n }\n };\n __console.assert = assert;\n}\n\n// console.table not supported in WebBLE\nif (!__console.table) {\n const table: LogFunction = (...data) => {\n __console.log!(...data);\n };\n __console.table = table;\n}\n\nfunction emptyFunction() {}\n\nconst log: LogFunction = __console.log!.bind(__console);\nconst warn: LogFunction = __console.warn!.bind(__console);\nconst error: LogFunction = __console.error!.bind(__console);\nconst table: LogFunction = __console.table!.bind(__console);\nconst assert: AssertLogFunction = __console.assert.bind(__console);\n\nclass Console {\n static #consoles: { [type: string]: Console } = {};\n\n constructor(type: string) {\n if (Console.#consoles[type]) {\n throw new Error(`\"${type}\" console already exists`);\n }\n Console.#consoles[type] = this;\n }\n\n #levelFlags: ConsoleLevelFlags = {\n log: isInDev,\n warn: isInDev,\n assert: true,\n error: true,\n table: true,\n };\n\n setLevelFlags(levelFlags: ConsoleLevelFlags) {\n Object.assign(this.#levelFlags, levelFlags);\n }\n\n /** @throws {Error} if no console with type \"type\" is found */\n static setLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n if (!this.#consoles[type]) {\n throw new Error(`no console found with type \"${type}\"`);\n }\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n\n static setAllLevelFlags(levelFlags: ConsoleLevelFlags) {\n for (const type in this.#consoles) {\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n }\n\n static create(type: string, levelFlags?: ConsoleLevelFlags): Console {\n const console = this.#consoles[type] || new Console(type);\n if (isInDev && levelFlags) {\n console.setLevelFlags(levelFlags);\n }\n return console;\n }\n\n get log() {\n return this.#levelFlags.log ? log : emptyFunction;\n }\n\n get warn() {\n return this.#levelFlags.warn ? warn : emptyFunction;\n }\n\n get error() {\n return this.#levelFlags.error ? error : emptyFunction;\n }\n\n get assert() {\n return this.#levelFlags.assert ? assert : emptyFunction;\n }\n\n get table() {\n return this.#levelFlags.table ? table : emptyFunction;\n }\n\n /** @throws {Error} if condition is not met */\n assertWithError(condition: any, message: string) {\n if (!Boolean(condition)) {\n throw new Error(message);\n }\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertTypeWithError(value: any, type: string) {\n this.assertWithError(typeof value == type, `value ${value} of type \"${typeof value}\" not of type \"${type}\"`);\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertEnumWithError(value: string, enumeration: readonly string[]) {\n this.assertWithError(enumeration.includes(value), `invalid enum \"${value}\"`);\n }\n}\n\nexport function createConsole(type: string, levelFlags?: ConsoleLevelFlags): Console {\n return Console.create(type, levelFlags);\n}\n\n/** @throws {Error} if no console with type is found */\nexport function setConsoleLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n Console.setLevelFlagsForType(type, levelFlags);\n}\n\nexport function setAllConsoleLevelFlags(levelFlags: ConsoleLevelFlags) {\n Console.setAllLevelFlags(levelFlags);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { deepEqual } from \"./ObjectUtils.ts\";\n\nconst _console = createConsole(\"EventDispatcher\", { log: false });\n\nexport type EventMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: { type: T; target: Target; message: EventMessages[T] };\n};\nexport type EventListenerMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n};\n\nexport type Event<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = EventMap<Target, EventType, EventMessages>[keyof EventMessages];\n\ntype SpecificEvent<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>,\n SpecificEventType extends EventType\n> = { type: SpecificEventType; target: Target; message: EventMessages[SpecificEventType] };\n\nexport type BoundEventListeners<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [SpecificEventType in keyof EventMessages]?: (\n // @ts-expect-error\n event: SpecificEvent<Target, EventType, EventMessages, SpecificEventType>\n ) => void;\n};\n\nclass EventDispatcher<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> {\n private listeners: {\n [T in EventType]?: {\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n once?: boolean;\n shouldRemove?: boolean;\n }[];\n } = {};\n\n constructor(private target: Target, private validEventTypes: readonly EventType[]) {\n this.addEventListener = this.addEventListener.bind(this);\n this.removeEventListener = this.removeEventListener.bind(this);\n this.removeEventListeners = this.removeEventListeners.bind(this);\n this.removeAllEventListeners = this.removeAllEventListeners.bind(this);\n this.dispatchEvent = this.dispatchEvent.bind(this);\n this.waitForEvent = this.waitForEvent.bind(this);\n }\n\n private isValidEventType(type: any): type is EventType {\n return this.validEventTypes.includes(type);\n }\n\n private updateEventListeners(type: EventType) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type]!.filter((listenerObj) => {\n if (listenerObj.shouldRemove) {\n _console.log(`removing \"${type}\" eventListener`, listenerObj);\n }\n return !listenerObj.shouldRemove;\n });\n }\n\n addEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void,\n options: { once?: boolean } = { once: false }\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n _console.log(`creating \"${type}\" listeners array`, this.listeners[type]!);\n }\n const alreadyAdded = this.listeners[type].find((listenerObject) => {\n return listenerObject.listener == listener && listenerObject.once == options.once;\n });\n if (alreadyAdded) {\n _console.log(\"already added listener\");\n return;\n }\n _console.log(`adding \"${type}\" listener`, listener, options);\n this.listeners[type]!.push({ listener, once: options.once });\n\n _console.log(`currently have ${this.listeners[type]!.length} \"${type}\" listeners`);\n }\n\n removeEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listener...`, listener);\n this.listeners[type]!.forEach((listenerObj) => {\n const isListenerToRemove = listenerObj.listener === listener;\n if (isListenerToRemove) {\n _console.log(`flagging \"${type}\" listener`, listener);\n listenerObj.shouldRemove = true;\n }\n });\n\n this.updateEventListeners(type);\n }\n\n removeEventListeners<T extends EventType>(type: T): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listeners...`);\n this.listeners[type] = [];\n }\n\n removeAllEventListeners(): void {\n _console.log(`removing listeners...`);\n this.listeners = {};\n }\n\n dispatchEvent<T extends EventType>(type: T, message: EventMessages[T]): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n this.listeners[type]!.forEach((listenerObj) => {\n if (listenerObj.shouldRemove) {\n return;\n }\n\n _console.log(`dispatching \"${type}\" listener`, listenerObj);\n listenerObj.listener({ type, target: this.target, message });\n\n if (listenerObj.once) {\n _console.log(`flagging \"${type}\" listener`, listenerObj);\n listenerObj.shouldRemove = true;\n }\n });\n this.updateEventListeners(type);\n }\n\n waitForEvent<T extends EventType>(type: T): Promise<{ type: T; target: Target; message: EventMessages[T] }> {\n return new Promise((resolve) => {\n const onceListener = (event: { type: T; target: Target; message: EventMessages[T] }) => {\n resolve(event);\n };\n\n this.addEventListener(type, onceListener, { once: true });\n });\n }\n}\n\nexport default EventDispatcher;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"Timer\", { log: false });\n\nexport async function wait(delay: number) {\n _console.log(`waiting for ${delay} ms`);\n return new Promise((resolve: Function) => {\n setTimeout(() => resolve(), delay);\n });\n}\n\nclass Timer {\n #callback!: Function;\n get callback() {\n return this.#callback;\n }\n set callback(newCallback) {\n _console.assertTypeWithError(newCallback, \"function\");\n _console.log({ newCallback });\n this.#callback = newCallback;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n #interval!: number;\n get interval() {\n return this.#interval;\n }\n set interval(newInterval) {\n _console.assertTypeWithError(newInterval, \"number\");\n _console.assertWithError(newInterval > 0, \"interval must be above 0\");\n _console.log({ newInterval });\n this.#interval = newInterval;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n constructor(callback: Function, interval: number) {\n this.interval = interval;\n this.callback = callback;\n }\n\n #intervalId: number | undefined;\n get isRunning() {\n return this.#intervalId != undefined;\n }\n\n start(immediately = false) {\n if (this.isRunning) {\n _console.log(\"interval already running\");\n return;\n }\n _console.log(\"starting interval\");\n this.#intervalId = setInterval(this.#callback, this.#interval);\n if (immediately) {\n this.#callback();\n }\n }\n stop() {\n if (!this.isRunning) {\n _console.log(\"interval already not running\");\n return;\n }\n _console.log(\"stopping interval\");\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n restart(startImmediately = false) {\n this.stop();\n this.start(startImmediately);\n }\n}\nexport default Timer;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"checksum\", { log: true });\n\n// https://github.com/googlecreativelab/tiny-motion-trainer/blob/5fceb49f018ae0c403bf9f0ccc437309c2acb507/frontend/src/tf4micro-motion-kit/modules/bleFileTransfer#L195\n\n// See http://home.thep.lu.se/~bjorn/crc/ for more information on simple CRC32 calculations.\nexport function crc32ForByte(r: number) {\n for (let j = 0; j < 8; ++j) {\n r = (r & 1 ? 0 : 0xedb88320) ^ (r >>> 1);\n }\n return r ^ 0xff000000;\n}\n\nconst tableSize = 256;\nconst crc32Table = new Uint32Array(tableSize);\nfor (let i = 0; i < tableSize; ++i) {\n crc32Table[i] = crc32ForByte(i);\n}\n\nexport function crc32(dataIterable: ArrayBuffer | number[]) {\n let dataBytes = new Uint8Array(dataIterable);\n let crc = 0;\n for (let i = 0; i < dataBytes.byteLength; ++i) {\n const crcLowByte = crc & 0x000000ff;\n const dataByte = dataBytes[i];\n const tableIndex = crcLowByte ^ dataByte;\n // The last >>> is to convert this into an unsigned 32-bit integer.\n crc = (crc32Table[tableIndex] ^ (crc >>> 8)) >>> 0;\n }\n return crc;\n}\n\n// This is a small test function for the CRC32 implementation, not normally called but left in\n// for debugging purposes. We know the expected CRC32 of [97, 98, 99, 100, 101] is 2240272485,\n// or 0x8587d865, so if anything else is output we know there's an error in the implementation.\nexport function testCrc32() {\n const testArray = [97, 98, 99, 100, 101];\n const testArrayCrc32 = crc32(testArray);\n _console.log(\"CRC32 for [97, 98, 99, 100, 101] is 0x\" + testArrayCrc32.toString(16) + \" (\" + testArrayCrc32 + \")\");\n}\n","var _TextEncoder;\nif (typeof TextEncoder == \"undefined\") {\n _TextEncoder = class {\n encode(string: string) {\n const encoding = Array.from(string).map((char) => char.charCodeAt(0));\n return Uint8Array.from(encoding);\n }\n };\n} else {\n _TextEncoder = TextEncoder;\n}\n\nvar _TextDecoder;\nif (typeof TextDecoder == \"undefined\") {\n _TextDecoder = class {\n decode(data: ArrayBuffer) {\n const byteArray = Array.from(new Uint8Array(data));\n return byteArray\n .map((value) => {\n return String.fromCharCode(value);\n })\n .join(\"\");\n }\n };\n} else {\n _TextDecoder = TextDecoder;\n}\n\nexport const textEncoder = new _TextEncoder();\nexport const textDecoder = new _TextDecoder();\n","import { createConsole } from \"./Console.ts\";\nimport { textEncoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ArrayBufferUtils\", { log: false });\n\nexport function concatenateArrayBuffers(...arrayBuffers: any[]): ArrayBuffer {\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer != undefined || arrayBuffer != null);\n arrayBuffers = arrayBuffers.map((arrayBuffer) => {\n if (typeof arrayBuffer == \"number\") {\n const number = arrayBuffer;\n return Uint8Array.from([Math.floor(number)]);\n } else if (typeof arrayBuffer == \"boolean\") {\n const boolean = arrayBuffer;\n return Uint8Array.from([boolean ? 1 : 0]);\n } else if (typeof arrayBuffer == \"string\") {\n const string = arrayBuffer;\n return stringToArrayBuffer(string);\n } else if (arrayBuffer instanceof Array) {\n const array = arrayBuffer;\n return concatenateArrayBuffers(...array);\n } else if (arrayBuffer instanceof ArrayBuffer) {\n return arrayBuffer;\n } else if (\"buffer\" in arrayBuffer && arrayBuffer.buffer instanceof ArrayBuffer) {\n const bufferContainer = arrayBuffer;\n return bufferContainer.buffer;\n } else if (arrayBuffer instanceof DataView) {\n const dataView = arrayBuffer;\n return dataView.buffer;\n } else if (typeof arrayBuffer == \"object\") {\n const object = arrayBuffer;\n return objectToArrayBuffer(object);\n } else {\n return arrayBuffer;\n }\n });\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer && \"byteLength\" in arrayBuffer);\n const length = arrayBuffers.reduce((length, arrayBuffer) => length + arrayBuffer.byteLength, 0);\n const uint8Array = new Uint8Array(length);\n let byteOffset = 0;\n arrayBuffers.forEach((arrayBuffer) => {\n uint8Array.set(new Uint8Array(arrayBuffer), byteOffset);\n byteOffset += arrayBuffer.byteLength;\n });\n return uint8Array.buffer;\n}\n\nexport function dataToArrayBuffer(data: Buffer) {\n return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n}\n\nexport function stringToArrayBuffer(string: string) {\n const encoding = textEncoder.encode(string);\n return concatenateArrayBuffers(encoding.byteLength, encoding);\n}\n\nexport function objectToArrayBuffer(object: object) {\n return stringToArrayBuffer(JSON.stringify(object));\n}\n\nexport function sliceDataView(dataView: DataView, begin: number, length?: number) {\n let end;\n if (length != undefined) {\n end = dataView.byteOffset + begin + length;\n }\n _console.log({ dataView, begin, end, length });\n return new DataView(dataView.buffer.slice(dataView.byteOffset + begin, end));\n}\n\nexport type FileLike = number[] | ArrayBuffer | DataView | URL | string | File;\n\nexport async function getFileBuffer(file: FileLike) {\n let fileBuffer;\n if (file instanceof Array) {\n fileBuffer = Uint8Array.from(file);\n } else if (file instanceof DataView) {\n fileBuffer = file.buffer;\n } else if (typeof file == \"string\" || file instanceof URL) {\n const response = await fetch(file);\n fileBuffer = await response.arrayBuffer();\n } else if (file instanceof File) {\n fileBuffer = await file.arrayBuffer();\n } else if (file instanceof ArrayBuffer) {\n fileBuffer = file;\n } else {\n throw { error: \"invalid file type\", file };\n }\n return fileBuffer;\n}\n","// Gets all non-builtin properties up the prototype chain.\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nexport default function autoBind(self, {include, exclude} = {}) {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n}\n","import { createConsole } from \"./utils/Console.ts\";\nimport { crc32 } from \"./utils/checksum.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FileTransferManager\", { log: true });\n\nexport const FileTransferMessageTypes = [\n \"maxFileLength\",\n \"getFileType\",\n \"setFileType\",\n \"getFileLength\",\n \"setFileLength\",\n \"getFileChecksum\",\n \"setFileChecksum\",\n \"setFileTransferCommand\",\n \"fileTransferStatus\",\n \"getFileBlock\",\n \"setFileBlock\",\n \"fileBytesTransferred\",\n] as const;\nexport type FileTransferMessageType = (typeof FileTransferMessageTypes)[number];\n\nexport const FileTypes = [\"tflite\"] as const;\nexport type FileType = (typeof FileTypes)[number];\n\nexport const FileTransferStatuses = [\"idle\", \"sending\", \"receiving\"] as const;\nexport type FileTransferStatus = (typeof FileTransferStatuses)[number];\n\nexport const FileTransferCommands = [\"startSend\", \"startReceive\", \"cancel\"] as const;\nexport type FileTransferCommand = (typeof FileTransferCommands)[number];\n\nexport const FileTransferDirections = [\"sending\", \"receiving\"] as const;\nexport type FileTransferDirection = (typeof FileTransferDirections)[number];\n\nexport const FileTransferEventTypes = [\n ...FileTransferMessageTypes,\n \"fileTransferProgress\",\n \"fileTransferComplete\",\n \"fileReceived\",\n] as const;\nexport type FileTransferEventType = (typeof FileTransferEventTypes)[number];\n\nexport interface FileTransferEventMessages {\n maxFileLength: { maxFileLength: number };\n getFileType: { fileType: FileType };\n getFileLength: { fileLength: number };\n getFileChecksum: { fileChecksum: number };\n fileTransferStatus: { fileTransferStatus: FileTransferStatus };\n getFileBlock: { fileTransferBlock: DataView };\n fileTransferProgress: { progress: number };\n fileTransferComplete: { direction: FileTransferDirection };\n fileReceived: { file: File | Blob };\n}\n\nexport type FileTransferEventDispatcher = EventDispatcher<Device, FileTransferEventType, FileTransferEventMessages>;\nexport type SendFileTransferMessageCallback = SendMessageCallback<FileTransferMessageType>;\n\nclass FileTransferManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendFileTransferMessageCallback;\n\n eventDispatcher!: FileTransferEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #assertValidType(type: FileType) {\n _console.assertEnumWithError(type, FileTypes);\n }\n #assertValidTypeEnum(typeEnum: number) {\n _console.assertWithError(typeEnum in FileTypes, `invalid typeEnum ${typeEnum}`);\n }\n\n #assertValidStatusEnum(statusEnum: number) {\n _console.assertWithError(statusEnum in FileTransferStatuses, `invalid statusEnum ${statusEnum}`);\n }\n #assertValidCommand(command: FileTransferCommand) {\n _console.assertEnumWithError(command, FileTransferCommands);\n }\n\n static #MaxLength = 0; // kB\n static get MaxLength() {\n return this.#MaxLength;\n }\n #maxLength = FileTransferManager.MaxLength;\n /** kB */\n get maxLength() {\n return this.#maxLength;\n }\n #parseMaxLength(dataView: DataView) {\n _console.log(\"parseFileMaxLength\", dataView);\n const maxLength = dataView.getUint32(0, true);\n _console.log(`maxLength: ${maxLength / 1024}kB`);\n this.#updateMaxLength(maxLength);\n }\n #updateMaxLength(maxLength: number) {\n _console.log({ maxLength });\n this.#maxLength = maxLength;\n this.#dispatchEvent(\"maxFileLength\", { maxFileLength: maxLength });\n }\n #assertValidLength(length: number) {\n _console.assertWithError(\n length <= this.maxLength,\n `file length ${length}kB too large - must be ${this.maxLength}kB or less`\n );\n }\n\n #type: FileType | undefined;\n get type() {\n return this.#type;\n }\n #parseType(dataView: DataView) {\n _console.log(\"parseFileType\", dataView);\n const typeEnum = dataView.getUint8(0);\n this.#assertValidTypeEnum(typeEnum);\n const type = FileTypes[typeEnum];\n this.#updateType(type);\n }\n #updateType(type: FileType) {\n _console.log({ fileTransferType: type });\n this.#type = type;\n this.#dispatchEvent(\"getFileType\", { fileType: type });\n }\n async #setType(newType: FileType, sendImmediately?: boolean) {\n this.#assertValidType(newType);\n if (this.type == newType) {\n _console.log(`redundant type assignment ${newType}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileType\");\n\n const typeEnum = FileTypes.indexOf(newType);\n this.sendMessage([{ type: \"setFileType\", data: Uint8Array.from([typeEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #length = 0;\n get length() {\n return this.#length;\n }\n #parseLength(dataView: DataView) {\n _console.log(\"parseFileLength\", dataView);\n const length = dataView.getUint32(0, true);\n\n this.#updateLength(length);\n }\n #updateLength(length: number) {\n _console.log(`length: ${length / 1024}kB`);\n this.#length = length;\n this.#dispatchEvent(\"getFileLength\", { fileLength: length });\n }\n async #setLength(newLength: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newLength, \"number\");\n this.#assertValidLength(newLength);\n if (this.length == newLength) {\n _console.log(`redundant length assignment ${newLength}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileLength\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newLength, true);\n this.sendMessage([{ type: \"setFileLength\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #checksum = 0;\n get checksum() {\n return this.#checksum;\n }\n #parseChecksum(dataView: DataView) {\n _console.log(\"checksum\", dataView);\n const checksum = dataView.getUint32(0, true);\n this.#updateChecksum(checksum);\n }\n #updateChecksum(checksum: number) {\n _console.log({ checksum });\n this.#checksum = checksum;\n this.#dispatchEvent(\"getFileChecksum\", { fileChecksum: checksum });\n }\n async #setChecksum(newChecksum: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newChecksum, \"number\");\n if (this.checksum == newChecksum) {\n _console.log(`redundant checksum assignment ${newChecksum}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileChecksum\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newChecksum, true);\n this.sendMessage([{ type: \"setFileChecksum\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n async #setCommand(command: FileTransferCommand, sendImmediately?: boolean) {\n this.#assertValidCommand(command);\n\n const promise = this.waitForEvent(\"fileTransferStatus\");\n _console.log(`setting command ${command}`);\n const commandEnum = FileTransferCommands.indexOf(command);\n this.sendMessage(\n [{ type: \"setFileTransferCommand\", data: Uint8Array.from([commandEnum]).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #status: FileTransferStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #parseStatus(dataView: DataView) {\n _console.log(\"parseFileStatus\", dataView);\n const statusEnum = dataView.getUint8(0);\n this.#assertValidStatusEnum(statusEnum);\n const status = FileTransferStatuses[statusEnum];\n this.#updateStatus(status);\n }\n #updateStatus(status: FileTransferStatus) {\n _console.log({ status });\n this.#status = status;\n this.#dispatchEvent(\"fileTransferStatus\", { fileTransferStatus: status });\n this.#receivedBlocks.length = 0;\n }\n #assertIsIdle() {\n _console.assertWithError(this.#status == \"idle\", \"status is not idle\");\n }\n #assertIsNotIdle() {\n _console.assertWithError(this.#status != \"idle\", \"status is idle\");\n }\n\n // BLOCK\n\n #receivedBlocks: ArrayBuffer[] = [];\n\n async #parseBlock(dataView: DataView) {\n _console.log(\"parseFileBlock\", dataView);\n this.#receivedBlocks.push(dataView.buffer);\n\n const bytesReceived = this.#receivedBlocks.reduce((sum, arrayBuffer) => (sum += arrayBuffer.byteLength), 0);\n const progress = bytesReceived / this.#length;\n\n _console.log(`received ${bytesReceived} of ${this.#length} bytes (${progress * 100}%)`);\n\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n\n if (bytesReceived != this.#length) {\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, bytesReceived, true);\n\n if (this.isServerSide) {\n return;\n }\n await this.sendMessage([{ type: \"fileBytesTransferred\", data: dataView.buffer }]);\n return;\n }\n\n _console.log(\"file transfer complete\");\n\n let fileName = new Date().toLocaleString();\n switch (this.type) {\n case \"tflite\":\n fileName += \".tflite\";\n break;\n }\n\n let file: File | Blob;\n if (typeof File !== \"undefined\") {\n file = new File(this.#receivedBlocks, fileName);\n } else {\n file = new Blob(this.#receivedBlocks);\n }\n\n const arrayBuffer = await file.arrayBuffer();\n const checksum = crc32(arrayBuffer);\n _console.log({ checksum });\n\n if (checksum != this.#checksum) {\n _console.error(`wrong checksum - expected ${this.#checksum}, got ${checksum}`);\n return;\n }\n\n _console.log(\"received file\", file);\n\n this.#dispatchEvent(\"getFileBlock\", { fileTransferBlock: dataView });\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"receiving\" });\n this.#dispatchEvent(\"fileReceived\", { file });\n }\n\n parseMessage(messageType: FileTransferMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"maxFileLength\":\n this.#parseMaxLength(dataView);\n break;\n case \"getFileType\":\n case \"setFileType\":\n this.#parseType(dataView);\n break;\n case \"getFileLength\":\n case \"setFileLength\":\n this.#parseLength(dataView);\n break;\n case \"getFileChecksum\":\n case \"setFileChecksum\":\n this.#parseChecksum(dataView);\n break;\n case \"fileTransferStatus\":\n this.#parseStatus(dataView);\n break;\n case \"getFileBlock\":\n this.#parseBlock(dataView);\n break;\n case \"fileBytesTransferred\":\n this.#parseBytesTransferred(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async send(type: FileType, file: FileLike) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n const fileBuffer = await getFileBuffer(file);\n\n const promises: Promise<any>[] = [];\n\n promises.push(this.#setType(type, false));\n const fileLength = fileBuffer.byteLength;\n promises.push(this.#setLength(fileLength, false));\n const checksum = crc32(fileBuffer);\n promises.push(this.#setChecksum(checksum, false));\n promises.push(this.#setCommand(\"startSend\", false));\n\n this.sendMessage();\n\n await Promise.all(promises);\n\n await this.#send(fileBuffer);\n }\n\n #buffer?: ArrayBuffer;\n #bytesTransferred = 0;\n async #send(buffer: ArrayBuffer) {\n this.#buffer = buffer;\n this.#bytesTransferred = 0;\n return this.#sendBlock();\n }\n\n mtu!: number;\n async #sendBlock(): Promise<void> {\n if (this.status != \"sending\") {\n return;\n }\n if (!this.#buffer) {\n if (!this.isServerSide) {\n _console.error(\"no buffer defined\");\n }\n return;\n }\n\n const buffer = this.#buffer;\n let offset = this.#bytesTransferred;\n\n const slicedBuffer = buffer.slice(offset, offset + (this.mtu - 3 - 3));\n _console.log(\"slicedBuffer\", slicedBuffer);\n const bytesLeft = buffer.byteLength - offset;\n\n const progress = 1 - bytesLeft / buffer.byteLength;\n _console.log(\n `sending bytes ${offset}-${offset + slicedBuffer.byteLength} of ${buffer.byteLength} bytes (${progress * 100}%)`\n );\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n if (slicedBuffer.byteLength == 0) {\n _console.log(\"finished sending buffer\");\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"sending\" });\n } else {\n await this.sendMessage([{ type: \"setFileBlock\", data: slicedBuffer }]);\n this.#bytesTransferred = offset + slicedBuffer.byteLength;\n //return this.#sendBlock(buffer, offset + slicedBuffer.byteLength);\n }\n }\n\n async #parseBytesTransferred(dataView: DataView) {\n _console.log(\"parseBytesTransferred\", dataView);\n const bytesTransferred = dataView.getUint32(0, true);\n _console.log({ bytesTransferred });\n if (this.status != \"sending\") {\n _console.error(`not currently sending file`);\n return;\n }\n if (!this.isServerSide && this.#bytesTransferred != bytesTransferred) {\n _console.error(`bytesTransferred are not equal - got ${bytesTransferred}, expected ${this.#bytesTransferred}`);\n this.cancel();\n return;\n }\n this.#sendBlock();\n }\n\n async receive(type: FileType) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n\n await this.#setType(type);\n await this.#setCommand(\"startReceive\");\n }\n\n async cancel() {\n this.#assertIsNotIdle();\n _console.log(\"cancelling file transfer...\");\n await this.#setCommand(\"cancel\");\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n }\n}\n\nexport default FileTransferManager;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"MathUtils\", { log: true });\n\nexport function getInterpolation(value: number, min: number, max: number, span: number) {\n if (span == undefined) {\n span = max - min;\n }\n return (value - min) / span;\n}\n\nexport const Uint16Max = 2 ** 16;\n\nfunction removeLower2Bytes(number: number) {\n const lower2Bytes = number % Uint16Max;\n return number - lower2Bytes;\n}\n\nconst timestampThreshold = 60_000;\n\nexport function parseTimestamp(dataView: DataView, byteOffset: number) {\n const now = Date.now();\n const nowWithoutLower2Bytes = removeLower2Bytes(now);\n const lower2Bytes = dataView.getUint16(byteOffset, true);\n let timestamp = nowWithoutLower2Bytes + lower2Bytes;\n if (Math.abs(now - timestamp) > timestampThreshold) {\n _console.log(\"correcting timestamp delta\");\n timestamp += Uint16Max * Math.sign(now - timestamp);\n }\n return timestamp;\n}\n\nexport interface Vector2 {\n x: number;\n y: number;\n}\n\nexport interface Vector3 extends Vector2 {\n z: number;\n}\n\nexport interface Quaternion {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\nexport interface Euler {\n heading: number;\n pitch: number;\n roll: number;\n}\n","import { getInterpolation } from \"./MathUtils.ts\";\n\ninterface Range {\n min: number;\n max: number;\n span: number;\n}\n\nconst initialRange: Range = { min: Infinity, max: -Infinity, span: 0 };\n\nclass RangeHelper {\n #range: Range = Object.assign({}, initialRange);\n get min() {\n return this.#range.min;\n }\n get max() {\n return this.#range.max;\n }\n\n set min(newMin) {\n this.#range.min = newMin;\n this.#range.max = Math.max(newMin, this.#range.max);\n this.#updateSpan();\n }\n set max(newMax) {\n this.#range.max = newMax;\n this.#range.min = Math.min(newMax, this.#range.min);\n this.#updateSpan();\n }\n\n #updateSpan() {\n this.#range.span = this.#range.max - this.#range.min;\n }\n\n reset() {\n Object.assign(this.#range, initialRange);\n }\n\n update(value: number) {\n this.#range.min = Math.min(value, this.#range.min);\n this.#range.max = Math.max(value, this.#range.max);\n this.#updateSpan();\n }\n\n getNormalization(value: number, weightByRange: boolean) {\n let normalization = getInterpolation(value, this.#range.min, this.#range.max, this.#range.span);\n if (weightByRange) {\n normalization *= this.#range.span;\n }\n return normalization || 0;\n }\n\n updateAndGetNormalization(value: number, weightByRange: boolean) {\n this.update(value);\n return this.getNormalization(value, weightByRange);\n }\n}\n\nexport default RangeHelper;\n","import RangeHelper from \"./RangeHelper.ts\";\n\nimport { Vector2 } from \"./MathUtils.ts\";\n\nexport type CenterOfPressure = Vector2;\n\nexport interface CenterOfPressureRange {\n x: RangeHelper;\n y: RangeHelper;\n}\n\nclass CenterOfPressureHelper {\n #range: CenterOfPressureRange = {\n x: new RangeHelper(),\n y: new RangeHelper(),\n };\n reset() {\n this.#range.x.reset();\n this.#range.y.reset();\n }\n\n update(centerOfPressure: CenterOfPressure) {\n this.#range.x.update(centerOfPressure.x);\n this.#range.y.update(centerOfPressure.y);\n }\n getNormalization(centerOfPressure: CenterOfPressure): CenterOfPressure {\n return {\n x: this.#range.x.getNormalization(centerOfPressure.x, false),\n y: this.#range.y.getNormalization(centerOfPressure.y, false),\n };\n }\n\n updateAndGetNormalization(centerOfPressure: CenterOfPressure) {\n this.update(centerOfPressure);\n return this.getNormalization(centerOfPressure);\n }\n}\n\nexport default CenterOfPressureHelper;\n","export function createArray(arrayLength: number, objectOrCallback: ((index: number) => any) | object) {\n return new Array(arrayLength).fill(1).map((_, index) => {\n if (typeof objectOrCallback == \"function\") {\n const callback = objectOrCallback;\n return callback(index);\n } else {\n const object = objectOrCallback;\n return Object.assign({}, object);\n }\n });\n}\n\nexport function arrayWithoutDuplicates(array: any[]) {\n return array.filter((value, index) => array.indexOf(value) == index);\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport RangeHelper from \"../utils/RangeHelper.ts\";\nimport { createArray } from \"../utils/ArrayUtils.ts\";\n\nconst _console = createConsole(\"PressureDataManager\", { log: true });\n\nexport const PressureSensorTypes = [\"pressure\"] as const;\nexport type PressureSensorType = (typeof PressureSensorTypes)[number];\n\nexport const ContinuousPressureSensorTypes = PressureSensorTypes;\nexport type ContinuousPressureSensorType = (typeof ContinuousPressureSensorTypes)[number];\n\nimport { Vector2 } from \"../utils/MathUtils.ts\";\nexport type PressureSensorPosition = Vector2;\n\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\n\nexport interface PressureSensorValue {\n position: PressureSensorPosition;\n rawValue: number;\n scaledValue: number;\n normalizedValue: number;\n weightedValue: number;\n}\n\nexport interface PressureData {\n sensors: PressureSensorValue[];\n scaledSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface PressureDataEventMessages {\n pressure: { pressure: PressureData };\n}\n\nexport const DefaultNumberOfPressureSensors = 8;\n\nclass PressureSensorDataManager {\n #positions: PressureSensorPosition[] = [];\n get positions() {\n return this.#positions;\n }\n\n get numberOfSensors() {\n return this.positions.length;\n }\n\n parsePositions(dataView: DataView) {\n const positions: PressureSensorPosition[] = [];\n\n for (\n let pressureSensorIndex = 0, byteOffset = 0;\n byteOffset < dataView.byteLength;\n pressureSensorIndex++, byteOffset += 2\n ) {\n positions.push({\n x: dataView.getUint8(byteOffset) / 2 ** 8,\n y: dataView.getUint8(byteOffset + 1) / 2 ** 8,\n });\n }\n\n _console.log({ positions });\n\n this.#positions = positions;\n\n this.#sensorRangeHelpers = createArray(this.numberOfSensors, () => new RangeHelper());\n\n this.resetRange();\n }\n\n #sensorRangeHelpers!: RangeHelper[];\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetRange() {\n this.#sensorRangeHelpers.forEach((rangeHelper) => rangeHelper.reset());\n this.#centerOfPressureHelper.reset();\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure: PressureData = { sensors: [], scaledSum: 0, normalizedSum: 0 };\n for (let index = 0, byteOffset = 0; byteOffset < dataView.byteLength; index++, byteOffset += 2) {\n const rawValue = dataView.getUint16(byteOffset, true);\n const scaledValue = rawValue * scalar;\n const rangeHelper = this.#sensorRangeHelpers[index];\n const normalizedValue = rangeHelper.updateAndGetNormalization(scaledValue, true);\n const position = this.positions[index];\n pressure.sensors[index] = { rawValue, scaledValue, normalizedValue, position, weightedValue: 0 };\n\n pressure.scaledSum += scaledValue;\n pressure.normalizedSum += normalizedValue / this.numberOfSensors;\n }\n\n if (pressure.scaledSum > 0 && pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n pressure.sensors.forEach((sensor) => {\n sensor.weightedValue = sensor.scaledValue / pressure.scaledSum;\n pressure.center!.x += sensor.position.x * sensor.weightedValue;\n pressure.center!.y += sensor.position.y * sensor.weightedValue;\n });\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ pressure });\n return pressure;\n }\n}\n\nexport default PressureSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nconst _console = createConsole(\"MotionSensorDataManager\", { log: true });\n\nexport const MotionSensorTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n \"orientation\",\n \"activity\",\n \"stepCounter\",\n \"stepDetector\",\n \"deviceOrientation\",\n] as const;\nexport type MotionSensorType = (typeof MotionSensorTypes)[number];\n\nexport const ContinuousMotionTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n] as const;\nexport type ContinuousMotionType = (typeof ContinuousMotionTypes)[number];\n\nimport { Vector3, Quaternion, Euler } from \"../utils/MathUtils.ts\";\nimport { ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nexport const Vector2Size = 2 * 2;\nexport const Vector3Size = 3 * 2;\nexport const QuaternionSize = 4 * 2;\n\nexport const ActivityTypes = [\"still\", \"walking\", \"running\", \"bicycle\", \"vehicle\", \"tilting\"] as const;\nexport type ActivityType = (typeof ActivityTypes)[number];\n\nexport interface Activity {\n still: boolean;\n walking: boolean;\n running: boolean;\n bicycle: boolean;\n vehicle: boolean;\n tilting: boolean;\n}\n\nexport const DeviceOrientations = [\n \"portraitUpright\",\n \"landscapeLeft\",\n \"portraitUpsideDown\",\n \"landscapeRight\",\n \"unknown\",\n] as const;\nexport type DeviceOrientation = (typeof DeviceOrientations)[number];\n\nexport interface MotionSensorDataEventMessages {\n acceleration: { acceleration: Vector3 };\n gravity: { gravity: Vector3 };\n linearAcceleration: { linearAcceleration: Vector3 };\n gyroscope: { gyroscope: Vector3 };\n magnetometer: { magnetometer: Vector3 };\n gameRotation: { gameRotation: Quaternion };\n rotation: { rotation: Quaternion };\n orientation: { orientation: Euler };\n stepDetector: { stepDetector: Object };\n stepCounter: { stepCounter: number };\n activity: { activity: Activity };\n deviceOrientation: { deviceOrientation: DeviceOrientation };\n}\n\nexport type MotionSensorDataEventMessage = ValueOf<MotionSensorDataEventMessages>;\n\nclass MotionSensorDataManager {\n parseVector3(dataView: DataView, scalar: number): Vector3 {\n let [x, y, z] = [dataView.getInt16(0, true), dataView.getInt16(2, true), dataView.getInt16(4, true)].map(\n (value) => value * scalar\n );\n\n const vector: Vector3 = { x, y, z };\n\n _console.log({ vector });\n return vector;\n }\n\n parseQuaternion(dataView: DataView, scalar: number): Quaternion {\n let [x, y, z, w] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n dataView.getInt16(6, true),\n ].map((value) => value * scalar);\n\n const quaternion: Quaternion = { x, y, z, w };\n\n _console.log({ quaternion });\n return quaternion;\n }\n\n parseEuler(dataView: DataView, scalar: number): Euler {\n let [heading, pitch, roll] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n ].map((value) => value * scalar);\n\n pitch *= -1;\n heading *= -1;\n heading += 360;\n\n const euler: Euler = { heading, pitch, roll };\n\n _console.log({ euler });\n return euler;\n }\n\n parseStepCounter(dataView: DataView) {\n _console.log(\"parseStepCounter\", dataView);\n const stepCount = dataView.getUint32(0, true);\n _console.log({ stepCount });\n return stepCount;\n }\n\n parseActivity(dataView: DataView) {\n _console.log(\"parseActivity\", dataView);\n const activity: Partial<Activity> = {};\n\n const activityBitfield = dataView.getUint8(0);\n _console.log(\"activityBitfield\", activityBitfield.toString(2));\n ActivityTypes.forEach((activityType, index) => {\n activity[activityType] = Boolean(activityBitfield & (1 << index));\n });\n\n _console.log(\"activity\", activity);\n\n return activity as Activity;\n }\n\n parseDeviceOrientation(dataView: DataView) {\n _console.log(\"parseDeviceOrientation\", dataView);\n const index = dataView.getUint8(0);\n const deviceOrientation = DeviceOrientations[index];\n _console.assertWithError(deviceOrientation, \"undefined deviceOrientation\");\n _console.log({ deviceOrientation });\n return deviceOrientation;\n }\n}\n\nexport default MotionSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nexport const BarometerSensorTypes = [\"barometer\"] as const;\nexport type BarometerSensorType = (typeof BarometerSensorTypes)[number];\n\nexport const ContinuousBarometerSensorTypes = BarometerSensorTypes;\nexport type ContinuousBarometerSensorType = (typeof ContinuousBarometerSensorTypes)[number];\n\nexport interface BarometerSensorDataEventMessages {\n barometer: {\n barometer: number;\n //altitude: number;\n };\n}\n\nconst _console = createConsole(\"BarometerSensorDataManager\", { log: true });\n\nclass BarometerSensorDataManager {\n #calculcateAltitude(pressure: number) {\n const P0 = 101325; // Standard atmospheric pressure at sea level in Pascals\n const T0 = 288.15; // Standard temperature at sea level in Kelvin\n const L = 0.0065; // Temperature lapse rate in K/m\n const R = 8.3144598; // Universal gas constant in J/(mol·K)\n const g = 9.80665; // Acceleration due to gravity in m/s²\n const M = 0.0289644; // Molar mass of Earth's air in kg/mol\n\n const exponent = (R * L) / (g * M);\n const h = (T0 / L) * (1 - Math.pow(pressure / P0, exponent));\n\n return h;\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure = dataView.getUint32(0, true) * scalar;\n const altitude = this.#calculcateAltitude(pressure);\n _console.log({ pressure, altitude });\n return { pressure };\n }\n}\n\nexport default BarometerSensorDataManager;\n","import { sliceDataView } from \"./ArrayBufferUtils.ts\";\nimport { createConsole } from \"./Console.ts\";\nimport { textDecoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ParseUtils\", { log: true });\n\nexport function parseStringFromDataView(dataView: DataView, byteOffset: number = 0) {\n const stringLength = dataView.getUint8(byteOffset++);\n const string = textDecoder.decode(\n dataView.buffer.slice(dataView.byteOffset + byteOffset, dataView.byteOffset + byteOffset + stringLength)\n );\n byteOffset += stringLength;\n return { string, byteOffset };\n}\n\nexport function parseMessage<MessageType extends string>(\n dataView: DataView,\n messageTypes: readonly MessageType[],\n callback: (messageType: MessageType, dataView: DataView, context?: any) => void,\n context?: any,\n parseMessageLengthAsUint16: boolean = false\n) {\n let byteOffset = 0;\n while (byteOffset < dataView.byteLength) {\n const messageTypeEnum = dataView.getUint8(byteOffset++);\n _console.assertWithError(messageTypeEnum in messageTypes, `invalid messageTypeEnum ${messageTypeEnum}`);\n const messageType = messageTypes[messageTypeEnum];\n\n let messageLength: number;\n if (parseMessageLengthAsUint16) {\n messageLength = dataView.getUint16(byteOffset, true);\n byteOffset += 2;\n } else {\n messageLength = dataView.getUint8(byteOffset++);\n }\n\n _console.log({ messageTypeEnum, messageType, messageLength, dataView, byteOffset });\n\n const _dataView = sliceDataView(dataView, byteOffset, messageLength);\n _console.log({ _dataView });\n\n callback(messageType, _dataView, context);\n\n byteOffset += messageLength;\n }\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport { parseTimestamp } from \"../utils/MathUtils.ts\";\nimport PressureSensorDataManager, { PressureDataEventMessages } from \"./PressureSensorDataManager.ts\";\nimport MotionSensorDataManager, { MotionSensorDataEventMessages } from \"./MotionSensorDataManager.ts\";\nimport BarometerSensorDataManager, { BarometerSensorDataEventMessages } from \"./BarometerSensorDataManager.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport { MotionSensorTypes, ContinuousMotionTypes } from \"./MotionSensorDataManager.ts\";\nimport { PressureSensorTypes, ContinuousPressureSensorTypes } from \"./PressureSensorDataManager.ts\";\nimport { BarometerSensorTypes, ContinuousBarometerSensorTypes } from \"./BarometerSensorDataManager.ts\";\nimport Device from \"../Device.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"SensorDataManager\", { log: true });\n\nexport const SensorTypes = [...PressureSensorTypes, ...MotionSensorTypes, ...BarometerSensorTypes] as const;\nexport type SensorType = (typeof SensorTypes)[number];\n\nexport const ContinuousSensorTypes = [\n ...ContinuousPressureSensorTypes,\n ...ContinuousMotionTypes,\n ...ContinuousBarometerSensorTypes,\n] as const;\nexport type ContinuousSensorType = (typeof ContinuousSensorTypes)[number];\n\nexport const SensorDataMessageTypes = [\"getPressurePositions\", \"getSensorScalars\", \"sensorData\"] as const;\nexport type SensorDataMessageType = (typeof SensorDataMessageTypes)[number];\n\nexport const SensorDataEventTypes = [...SensorDataMessageTypes, ...SensorTypes] as const;\nexport type SensorDataEventType = (typeof SensorDataEventTypes)[number];\n\ninterface BaseSensorDataEventMessage {\n timestamp: number;\n}\n\ntype BaseSensorDataEventMessages = BarometerSensorDataEventMessages &\n MotionSensorDataEventMessages &\n PressureDataEventMessages;\ntype _SensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseSensorDataEventMessages, \"sensorType\">,\n BaseSensorDataEventMessage\n>;\nexport type SensorDataEventMessage = ValueOf<_SensorDataEventMessages>;\ninterface AnySensorDataEventMessages {\n sensorData: SensorDataEventMessage;\n}\nexport type SensorDataEventMessages = _SensorDataEventMessages & AnySensorDataEventMessages;\n\nexport type SensorDataEventDispatcher = EventDispatcher<Device, SensorDataEventType, SensorDataEventMessages>;\n\nclass SensorDataManager {\n pressureSensorDataManager = new PressureSensorDataManager();\n motionSensorDataManager = new MotionSensorDataManager();\n barometerSensorDataManager = new BarometerSensorDataManager();\n\n #scalars: Map<SensorType, number> = new Map();\n\n static AssertValidSensorType(sensorType: SensorType) {\n _console.assertEnumWithError(sensorType, SensorTypes);\n }\n static AssertValidSensorTypeEnum(sensorTypeEnum: number) {\n _console.assertTypeWithError(sensorTypeEnum, \"number\");\n _console.assertWithError(sensorTypeEnum in SensorTypes, `invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n\n eventDispatcher!: SensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n parseMessage(messageType: SensorDataMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorScalars\":\n this.parseScalars(dataView);\n break;\n case \"getPressurePositions\":\n this.pressureSensorDataManager.parsePositions(dataView);\n break;\n case \"sensorData\":\n this.parseData(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n parseScalars(dataView: DataView) {\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 5) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorScalar = dataView.getFloat32(byteOffset + 1, true);\n _console.log({ sensorType, sensorScalar });\n this.#scalars.set(sensorType, sensorScalar);\n }\n }\n\n private parseData(dataView: DataView) {\n _console.log(\"sensorData\", Array.from(new Uint8Array(dataView.buffer)));\n\n let byteOffset = 0;\n const timestamp = parseTimestamp(dataView, byteOffset);\n byteOffset += 2;\n\n const _dataView = new DataView(dataView.buffer, byteOffset);\n\n parseMessage(_dataView, SensorTypes, this.parseDataCallback.bind(this), { timestamp });\n }\n\n private parseDataCallback(sensorType: SensorType, dataView: DataView, { timestamp }: { timestamp: number }) {\n const scalar = this.#scalars.get(sensorType) || 1;\n\n let sensorData = null;\n switch (sensorType) {\n case \"pressure\":\n sensorData = this.pressureSensorDataManager.parseData(dataView, scalar);\n break;\n case \"acceleration\":\n case \"gravity\":\n case \"linearAcceleration\":\n case \"gyroscope\":\n case \"magnetometer\":\n sensorData = this.motionSensorDataManager.parseVector3(dataView, scalar);\n break;\n case \"gameRotation\":\n case \"rotation\":\n sensorData = this.motionSensorDataManager.parseQuaternion(dataView, scalar);\n break;\n case \"orientation\":\n sensorData = this.motionSensorDataManager.parseEuler(dataView, scalar);\n break;\n case \"stepCounter\":\n sensorData = this.motionSensorDataManager.parseStepCounter(dataView);\n break;\n case \"stepDetector\":\n sensorData = {};\n break;\n case \"activity\":\n sensorData = this.motionSensorDataManager.parseActivity(dataView);\n break;\n case \"deviceOrientation\":\n sensorData = this.motionSensorDataManager.parseDeviceOrientation(dataView);\n break;\n case \"barometer\":\n sensorData = this.barometerSensorDataManager.parseData(dataView, scalar);\n break;\n default:\n _console.error(`uncaught sensorType \"${sensorType}\"`);\n }\n\n _console.assertWithError(sensorData != null, `no sensorData defined for sensorType \"${sensorType}\"`);\n\n _console.log({ sensorType, sensorData });\n // @ts-expect-error\n this.dispatchEvent(sensorType, { sensorType, [sensorType]: sensorData, timestamp });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, [sensorType]: sensorData, timestamp });\n }\n}\n\nexport default SensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport SensorDataManager, { SensorTypes, SensorType } from \"./SensorDataManager.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport Device, { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"../../node_modules/auto-bind/index.js\";\n\nconst _console = createConsole(\"SensorConfigurationManager\", { log: true });\n\nexport type SensorConfiguration = { [sensorType in SensorType]?: number };\n\nexport const MaxSensorRate = 2 ** 16 - 1;\nexport const SensorRateStep = 5;\n\nexport const SensorConfigurationMessageTypes = [\"getSensorConfiguration\", \"setSensorConfiguration\"] as const;\nexport type SensorConfigurationMessageType = (typeof SensorConfigurationMessageTypes)[number];\n\nexport const SensorConfigurationEventTypes = SensorConfigurationMessageTypes;\nexport type SensorConfigurationEventType = (typeof SensorConfigurationEventTypes)[number];\n\nexport interface SensorConfigurationEventMessages {\n getSensorConfiguration: { sensorConfiguration: SensorConfiguration };\n}\n\nexport type SensorConfigurationEventDispatcher = EventDispatcher<\n Device,\n SensorConfigurationEventType,\n SensorConfigurationEventMessages\n>;\n\nexport type SendSensorConfigurationMessageCallback = SendMessageCallback<SensorConfigurationMessageType>;\n\nclass SensorConfigurationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendSensorConfigurationMessageCallback;\n\n eventDispatcher!: SensorConfigurationEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #availableSensorTypes!: SensorType[];\n #assertAvailableSensorType(sensorType: SensorType) {\n _console.assertWithError(this.#availableSensorTypes, \"must get initial sensorConfiguration\");\n const isSensorTypeAvailable = this.#availableSensorTypes?.includes(sensorType);\n _console.assert(isSensorTypeAvailable, `unavailable sensor type \"${sensorType}\"`);\n return isSensorTypeAvailable;\n }\n\n #configuration!: SensorConfiguration;\n get configuration() {\n return this.#configuration;\n }\n\n #updateConfiguration(updatedConfiguration: SensorConfiguration) {\n this.#configuration = updatedConfiguration;\n _console.log({ updatedConfiguration: this.#configuration });\n this.#dispatchEvent(\"getSensorConfiguration\", { sensorConfiguration: this.configuration });\n }\n\n #isRedundant(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n return sensorTypes.every((sensorType) => {\n return this.configuration[sensorType] == sensorConfiguration[sensorType];\n });\n }\n\n async setConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n if (clearRest) {\n newSensorConfiguration = Object.assign({ ...this.zeroSensorConfiguration }, newSensorConfiguration);\n }\n _console.log({ newSensorConfiguration });\n if (this.#isRedundant(newSensorConfiguration)) {\n _console.log(\"redundant sensor configuration\");\n return;\n }\n const setSensorConfigurationData = this.#createData(newSensorConfiguration);\n _console.log({ setSensorConfigurationData });\n\n const promise = this.waitForEvent(\"getSensorConfiguration\");\n this.sendMessage([{ type: \"setSensorConfiguration\", data: setSensorConfigurationData.buffer }]);\n await promise;\n }\n\n #parse(dataView: DataView) {\n const parsedSensorConfiguration: SensorConfiguration = {};\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 3) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorRate = dataView.getUint16(byteOffset + 1, true);\n _console.log({ sensorType, sensorRate });\n parsedSensorConfiguration[sensorType] = sensorRate;\n }\n _console.log({ parsedSensorConfiguration });\n this.#availableSensorTypes = Object.keys(parsedSensorConfiguration) as SensorType[];\n return parsedSensorConfiguration;\n }\n\n static #AssertValidSensorRate(sensorRate: number) {\n _console.assertTypeWithError(sensorRate, \"number\");\n _console.assertWithError(sensorRate >= 0, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate < MaxSensorRate, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate % SensorRateStep == 0, `sensorRate must be multiple of ${SensorRateStep}`);\n }\n\n #assertValidSensorRate(sensorRate: number) {\n SensorConfigurationManager.#AssertValidSensorRate(sensorRate);\n }\n\n #createData(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n sensorTypes = sensorTypes.filter((sensorType) => this.#assertAvailableSensorType(sensorType));\n\n const dataView = new DataView(new ArrayBuffer(sensorTypes.length * 3));\n sensorTypes.forEach((sensorType, index) => {\n SensorDataManager.AssertValidSensorType(sensorType);\n const sensorTypeEnum = SensorTypes.indexOf(sensorType);\n dataView.setUint8(index * 3, sensorTypeEnum);\n\n const sensorRate = sensorConfiguration[sensorType]!;\n this.#assertValidSensorRate(sensorRate);\n dataView.setUint16(index * 3 + 1, sensorRate, true);\n });\n _console.log({ sensorConfigurationData: dataView });\n return dataView;\n }\n\n // ZERO\n static #ZeroSensorConfiguration: SensorConfiguration = {};\n static get ZeroSensorConfiguration() {\n return this.#ZeroSensorConfiguration;\n }\n static {\n SensorTypes.forEach((sensorType) => {\n this.#ZeroSensorConfiguration[sensorType] = 0;\n });\n }\n get zeroSensorConfiguration() {\n const zeroSensorConfiguration: SensorConfiguration = {};\n SensorTypes.forEach((sensorType) => {\n zeroSensorConfiguration[sensorType] = 0;\n });\n return zeroSensorConfiguration;\n }\n async clearSensorConfiguration() {\n return this.setConfiguration(this.zeroSensorConfiguration);\n }\n\n // MESSAGE\n parseMessage(messageType: SensorConfigurationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorConfiguration\":\n case \"setSensorConfiguration\":\n const newSensorConfiguration = this.#parse(dataView);\n this.#updateConfiguration(newSensorConfiguration);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default SensorConfigurationManager;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport SensorDataManager, { SensorTypes } from \"./sensor/SensorDataManager.ts\";\nimport { arrayWithoutDuplicates } from \"./utils/ArrayUtils.ts\";\nimport { SensorRateStep } from \"./sensor/SensorConfigurationManager.ts\";\nimport { parseTimestamp } from \"./utils/MathUtils.ts\";\nimport { SensorType } from \"./sensor/SensorDataManager.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"TfliteManager\", { log: true });\n\nexport const TfliteMessageTypes = [\n \"getTfliteName\",\n \"setTfliteName\",\n \"getTfliteTask\",\n \"setTfliteTask\",\n \"getTfliteSampleRate\",\n \"setTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"setTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"setTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"setTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n \"setTfliteInferencingEnabled\",\n \"tfliteInference\",\n] as const;\nexport type TfliteMessageType = (typeof TfliteMessageTypes)[number];\n\nexport const TfliteEventTypes = TfliteMessageTypes;\nexport type TfliteEventType = (typeof TfliteEventTypes)[number];\n\nexport const TfliteTasks = [\"classification\", \"regression\"] as const;\nexport type TfliteTask = (typeof TfliteTasks)[number];\n\nexport interface TfliteEventMessages {\n getTfliteName: { tfliteName: string };\n getTfliteTask: { tfliteTask: TfliteTask };\n getTfliteSampleRate: { tfliteSampleRate: number };\n getTfliteSensorTypes: { tfliteSensorTypes: SensorType[] };\n tfliteIsReady: { tfliteIsReady: boolean };\n getTfliteCaptureDelay: { tfliteCaptureDelay: number };\n getTfliteThreshold: { tfliteThreshold: number };\n getTfliteInferencingEnabled: { tfliteInferencingEnabled: boolean };\n tfliteInference: { tfliteInference: TfliteInference };\n}\n\nexport interface TfliteInference {\n timestamp: number;\n values: number[];\n maxValue?: number;\n maxIndex?: number;\n}\n\nexport type TfliteEventDispatcher = EventDispatcher<Device, TfliteEventType, TfliteEventMessages>;\nexport type SendTfliteMessageCallback = SendMessageCallback<TfliteMessageType>;\n\nexport const TfliteSensorTypes: SensorType[] = [\"pressure\", \"linearAcceleration\", \"gyroscope\", \"magnetometer\"] as const;\nexport type TfliteSensorType = (typeof TfliteSensorTypes)[number];\n\nclass TfliteManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendTfliteMessageCallback;\n\n #assertValidTask(task: TfliteTask) {\n _console.assertEnumWithError(task, TfliteTasks);\n }\n #assertValidTaskEnum(taskEnum: number) {\n _console.assertWithError(taskEnum in TfliteTasks, `invalid taskEnum ${taskEnum}`);\n }\n\n eventDispatcher!: TfliteEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #name!: string;\n get name() {\n return this.#name;\n }\n #parseName(dataView: DataView) {\n _console.log(\"parseName\", dataView);\n const name = textDecoder.decode(dataView.buffer);\n this.#updateName(name);\n }\n #updateName(name: string) {\n _console.log({ name });\n this.#name = name;\n this.#dispatchEvent(\"getTfliteName\", { tfliteName: name });\n }\n async setName(newName: string, sendImmediately?: boolean) {\n _console.assertTypeWithError(newName, \"string\");\n if (this.name == newName) {\n _console.log(`redundant name assignment ${newName}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteName\");\n\n const setNameData = textEncoder.encode(newName);\n this.sendMessage([{ type: \"setTfliteName\", data: setNameData.buffer }], sendImmediately);\n\n await promise;\n }\n\n #task!: TfliteTask;\n get task() {\n return this.#task;\n }\n #parseTask(dataView: DataView) {\n _console.log(\"parseTask\", dataView);\n const taskEnum = dataView.getUint8(0);\n this.#assertValidTaskEnum(taskEnum);\n const task = TfliteTasks[taskEnum];\n this.#updateTask(task);\n }\n #updateTask(task: TfliteTask) {\n _console.log({ task });\n this.#task = task;\n this.#dispatchEvent(\"getTfliteTask\", { tfliteTask: task });\n }\n async setTask(newTask: TfliteTask, sendImmediately?: boolean) {\n this.#assertValidTask(newTask);\n if (this.task == newTask) {\n _console.log(`redundant task assignment ${newTask}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteTask\");\n\n const taskEnum = TfliteTasks.indexOf(newTask);\n this.sendMessage([{ type: \"setTfliteTask\", data: Uint8Array.from([taskEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #sampleRate!: number;\n get sampleRate() {\n return this.#sampleRate;\n }\n #parseSampleRate(dataView: DataView) {\n _console.log(\"parseSampleRate\", dataView);\n const sampleRate = dataView.getUint16(0, true);\n this.#updateSampleRate(sampleRate);\n }\n #updateSampleRate(sampleRate: number) {\n _console.log({ sampleRate });\n this.#sampleRate = sampleRate;\n this.#dispatchEvent(\"getTfliteSampleRate\", { tfliteSampleRate: sampleRate });\n }\n async setSampleRate(newSampleRate: number, sendImmediately?: boolean) {\n _console.assertTypeWithError(newSampleRate, \"number\");\n newSampleRate -= newSampleRate % SensorRateStep;\n _console.assertWithError(\n newSampleRate >= SensorRateStep,\n `sampleRate must be multiple of ${SensorRateStep} greater than 0 (got ${newSampleRate})`\n );\n if (this.#sampleRate == newSampleRate) {\n _console.log(`redundant sampleRate assignment ${newSampleRate}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteSampleRate\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newSampleRate, true);\n this.sendMessage([{ type: \"setTfliteSampleRate\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n static AssertValidSensorType(sensorType: SensorType) {\n SensorDataManager.AssertValidSensorType(sensorType);\n _console.assertWithError(TfliteSensorTypes.includes(sensorType), `invalid tflite sensorType \"${sensorType}\"`);\n }\n\n #sensorTypes: SensorType[] = [];\n get sensorTypes() {\n return this.#sensorTypes.slice();\n }\n #parseSensorTypes(dataView: DataView) {\n _console.log(\"parseSensorTypes\", dataView);\n const sensorTypes: SensorType[] = [];\n for (let index = 0; index < dataView.byteLength; index++) {\n const sensorTypeEnum = dataView.getUint8(index);\n const sensorType = SensorTypes[sensorTypeEnum];\n if (sensorType) {\n sensorTypes.push(sensorType);\n } else {\n _console.error(`invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n }\n this.#updateSensorTypes(sensorTypes);\n }\n #updateSensorTypes(sensorTypes: SensorType[]) {\n _console.log({ sensorTypes });\n this.#sensorTypes = sensorTypes;\n this.#dispatchEvent(\"getTfliteSensorTypes\", { tfliteSensorTypes: sensorTypes });\n }\n async setSensorTypes(newSensorTypes: SensorType[], sendImmediately?: boolean) {\n newSensorTypes.forEach((sensorType) => {\n TfliteManager.AssertValidSensorType(sensorType);\n });\n\n const promise = this.waitForEvent(\"getTfliteSensorTypes\");\n\n newSensorTypes = arrayWithoutDuplicates(newSensorTypes);\n const newSensorTypeEnums = newSensorTypes.map((sensorType) => SensorTypes.indexOf(sensorType)).sort();\n _console.log(newSensorTypes, newSensorTypeEnums);\n this.sendMessage(\n [{ type: \"setTfliteSensorTypes\", data: Uint8Array.from(newSensorTypeEnums).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #isReady!: boolean;\n get isReady() {\n return this.#isReady;\n }\n #parseIsReady(dataView: DataView) {\n _console.log(\"parseIsReady\", dataView);\n const isReady = Boolean(dataView.getUint8(0));\n this.#updateIsReady(isReady);\n }\n #updateIsReady(isReady: boolean) {\n _console.log({ isReady });\n this.#isReady = isReady;\n this.#dispatchEvent(\"tfliteIsReady\", { tfliteIsReady: isReady });\n }\n #assertIsReady() {\n _console.assertWithError(this.isReady, `tflite is not ready`);\n }\n\n #captureDelay!: number;\n get captureDelay() {\n return this.#captureDelay;\n }\n #parseCaptureDelay(dataView: DataView) {\n _console.log(\"parseCaptureDelay\", dataView);\n const captureDelay = dataView.getUint16(0, true);\n this.#updateCaptueDelay(captureDelay);\n }\n #updateCaptueDelay(captureDelay: number) {\n _console.log({ captureDelay });\n this.#captureDelay = captureDelay;\n this.#dispatchEvent(\"getTfliteCaptureDelay\", { tfliteCaptureDelay: captureDelay });\n }\n async setCaptureDelay(newCaptureDelay: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newCaptureDelay, \"number\");\n if (this.#captureDelay == newCaptureDelay) {\n _console.log(`redundant captureDelay assignment ${newCaptureDelay}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteCaptureDelay\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newCaptureDelay, true);\n this.sendMessage([{ type: \"setTfliteCaptureDelay\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #threshold!: number;\n get threshold() {\n return this.#threshold;\n }\n #parseThreshold(dataView: DataView) {\n _console.log(\"parseThreshold\", dataView);\n const threshold = dataView.getFloat32(0, true);\n this.#updateThreshold(threshold);\n }\n #updateThreshold(threshold: number) {\n _console.log({ threshold });\n this.#threshold = threshold;\n this.#dispatchEvent(\"getTfliteThreshold\", { tfliteThreshold: threshold });\n }\n async setThreshold(newThreshold: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newThreshold, \"number\");\n _console.assertWithError(newThreshold >= 0, `threshold must be positive (got ${newThreshold})`);\n if (this.#threshold == newThreshold) {\n _console.log(`redundant threshold assignment ${newThreshold}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteThreshold\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setFloat32(0, newThreshold, true);\n this.sendMessage([{ type: \"setTfliteThreshold\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #inferencingEnabled!: boolean;\n get inferencingEnabled() {\n return this.#inferencingEnabled;\n }\n #parseInferencingEnabled(dataView: DataView) {\n _console.log(\"parseInferencingEnabled\", dataView);\n const inferencingEnabled = Boolean(dataView.getUint8(0));\n this.#updateInferencingEnabled(inferencingEnabled);\n }\n #updateInferencingEnabled(inferencingEnabled: boolean) {\n _console.log({ inferencingEnabled });\n this.#inferencingEnabled = inferencingEnabled;\n this.#dispatchEvent(\"getTfliteInferencingEnabled\", { tfliteInferencingEnabled: inferencingEnabled });\n }\n async setInferencingEnabled(newInferencingEnabled: boolean, sendImmediately: boolean = true) {\n _console.assertTypeWithError(newInferencingEnabled, \"boolean\");\n if (!newInferencingEnabled && !this.isReady) {\n return;\n }\n this.#assertIsReady();\n if (this.#inferencingEnabled == newInferencingEnabled) {\n _console.log(`redundant inferencingEnabled assignment ${newInferencingEnabled}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteInferencingEnabled\");\n\n this.sendMessage(\n [\n {\n type: \"setTfliteInferencingEnabled\",\n data: Uint8Array.from([Number(newInferencingEnabled)]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n async toggleInferencingEnabled() {\n return this.setInferencingEnabled(!this.inferencingEnabled);\n }\n\n async enableInferencing() {\n if (this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(true);\n }\n async disableInferencing() {\n if (!this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(false);\n }\n\n #parseInference(dataView: DataView) {\n _console.log(\"parseInference\", dataView);\n\n const timestamp = parseTimestamp(dataView, 0);\n _console.log({ timestamp });\n\n const values: number[] = [];\n for (let index = 0, byteOffset = 2; byteOffset < dataView.byteLength; index++, byteOffset += 4) {\n const value = dataView.getFloat32(byteOffset, true);\n values.push(value);\n }\n _console.log(\"values\", values);\n\n const inference: TfliteInference = {\n timestamp,\n values,\n };\n\n if (this.task == \"classification\") {\n let maxValue = 0;\n let maxIndex = 0;\n values.forEach((value, index) => {\n if (value > maxValue) {\n maxValue = value;\n maxIndex = index;\n }\n });\n _console.log({ maxIndex, maxValue });\n inference.maxIndex = maxIndex;\n inference.maxValue = maxValue;\n }\n\n this.#dispatchEvent(\"tfliteInference\", { tfliteInference: inference });\n }\n\n parseMessage(messageType: TfliteMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getTfliteName\":\n case \"setTfliteName\":\n this.#parseName(dataView);\n break;\n case \"getTfliteTask\":\n case \"setTfliteTask\":\n this.#parseTask(dataView);\n break;\n case \"getTfliteSampleRate\":\n case \"setTfliteSampleRate\":\n this.#parseSampleRate(dataView);\n break;\n case \"getTfliteSensorTypes\":\n case \"setTfliteSensorTypes\":\n this.#parseSensorTypes(dataView);\n break;\n case \"tfliteIsReady\":\n this.#parseIsReady(dataView);\n break;\n case \"getTfliteCaptureDelay\":\n case \"setTfliteCaptureDelay\":\n this.#parseCaptureDelay(dataView);\n break;\n case \"getTfliteThreshold\":\n case \"setTfliteThreshold\":\n this.#parseThreshold(dataView);\n break;\n case \"getTfliteInferencingEnabled\":\n case \"setTfliteInferencingEnabled\":\n this.#parseInferencingEnabled(dataView);\n break;\n case \"tfliteInference\":\n this.#parseInference(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default TfliteManager;\n","import Device from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder } from \"./utils/Text.ts\";\n\nconst _console = createConsole(\"DeviceInformationManager\", { log: true });\n\nexport interface PnpId {\n source: \"Bluetooth\" | \"USB\";\n vendorId: number;\n productId: number;\n productVersion: number;\n}\n\nexport interface DeviceInformation {\n manufacturerName: string;\n modelNumber: string;\n softwareRevision: string;\n hardwareRevision: string;\n firmwareRevision: string;\n pnpId: PnpId;\n serialNumber: string;\n}\n\nexport const DeviceInformationMessageTypes = [\n \"manufacturerName\",\n \"modelNumber\",\n \"softwareRevision\",\n \"hardwareRevision\",\n \"firmwareRevision\",\n \"pnpId\",\n \"serialNumber\",\n] as const;\nexport type DeviceInformationMessageType = (typeof DeviceInformationMessageTypes)[number];\n\nexport const DeviceInformationEventTypes = [...DeviceInformationMessageTypes, \"deviceInformation\"] as const;\nexport type DeviceInformationEventType = (typeof DeviceInformationEventTypes)[number];\n\nexport interface DeviceInformationEventMessages {\n manufacturerName: { manufacturerName: string };\n modelNumber: { modelNumber: string };\n softwareRevision: { softwareRevision: string };\n hardwareRevision: { hardwareRevision: string };\n firmwareRevision: { firmwareRevision: string };\n pnpId: { pnpId: PnpId };\n serialNumber: { serialNumber: string };\n deviceInformation: { deviceInformation: DeviceInformation };\n}\n\nexport type DeviceInformationEventDispatcher = EventDispatcher<\n Device,\n DeviceInformationEventType,\n DeviceInformationEventMessages\n>;\n\nclass DeviceInformationManager {\n eventDispatcher!: DeviceInformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #information: Partial<DeviceInformation> = {};\n get information() {\n return this.#information as DeviceInformation;\n }\n clear() {\n this.#information = {};\n }\n get #isComplete() {\n return DeviceInformationMessageTypes.every((key) => key in this.#information);\n }\n\n #update(partialDeviceInformation: Partial<DeviceInformation>) {\n _console.log({ partialDeviceInformation });\n const deviceInformationNames = Object.keys(partialDeviceInformation) as (keyof DeviceInformation)[];\n deviceInformationNames.forEach((deviceInformationName) => {\n // @ts-expect-error\n this.#dispatchEvent(deviceInformationName, {\n [deviceInformationName]: partialDeviceInformation[deviceInformationName],\n });\n });\n\n Object.assign(this.#information, partialDeviceInformation);\n _console.log({ deviceInformation: this.#information });\n if (this.#isComplete) {\n _console.log(\"completed deviceInformation\");\n this.#dispatchEvent(\"deviceInformation\", { deviceInformation: this.information });\n }\n }\n\n parseMessage(messageType: DeviceInformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"manufacturerName\":\n const manufacturerName = textDecoder.decode(dataView.buffer);\n _console.log({ manufacturerName });\n this.#update({ manufacturerName });\n break;\n case \"modelNumber\":\n const modelNumber = textDecoder.decode(dataView.buffer);\n _console.log({ modelNumber });\n this.#update({ modelNumber });\n break;\n case \"softwareRevision\":\n const softwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ softwareRevision });\n this.#update({ softwareRevision });\n break;\n case \"hardwareRevision\":\n const hardwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ hardwareRevision });\n this.#update({ hardwareRevision });\n break;\n case \"firmwareRevision\":\n const firmwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ firmwareRevision });\n this.#update({ firmwareRevision });\n break;\n case \"pnpId\":\n const pnpId: PnpId = {\n source: dataView.getUint8(0) === 1 ? \"Bluetooth\" : \"USB\",\n productId: dataView.getUint16(3, true),\n productVersion: dataView.getUint16(5, true),\n vendorId: 0,\n };\n if (pnpId.source == \"Bluetooth\") {\n pnpId.vendorId = dataView.getUint16(1, true);\n } else {\n // no need to implement\n }\n _console.log({ pnpId });\n this.#update({ pnpId });\n break;\n case \"serialNumber\":\n const serialNumber = textDecoder.decode(dataView.buffer);\n _console.log({ serialNumber });\n // will only be used for node\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default DeviceInformationManager;\n","import Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { Uint16Max } from \"./utils/MathUtils.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"InformationManager\", { log: true });\n\nexport const DeviceTypes = [\"leftInsole\", \"rightInsole\"] as const;\nexport type DeviceType = (typeof DeviceTypes)[number];\n\nexport const InsoleSides = [\"left\", \"right\"] as const;\nexport type InsoleSide = (typeof InsoleSides)[number];\n\nexport const MinNameLength = 2;\nexport const MaxNameLength = 30;\n\nexport const InformationMessageTypes = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getMtu\",\n \"getId\",\n \"getName\",\n \"setName\",\n \"getType\",\n \"setType\",\n \"getCurrentTime\",\n \"setCurrentTime\",\n] as const;\nexport type InformationMessageType = (typeof InformationMessageTypes)[number];\n\nexport const InformationEventTypes = InformationMessageTypes;\nexport type InformationEventType = (typeof InformationEventTypes)[number];\n\nexport interface InformationEventMessages {\n isCharging: { isCharging: boolean };\n getBatteryCurrent: { batteryCurrent: number };\n getMtu: { mtu: number };\n getId: { id: string };\n getName: { name: string };\n getType: { type: DeviceType };\n getCurrentTime: { currentTime: number };\n}\n\nexport type InformationEventDispatcher = EventDispatcher<Device, InformationEventType, InformationEventMessages>;\nexport type SendInformationMessageCallback = SendMessageCallback<InformationMessageType>;\n\nclass InformationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendInformationMessageCallback;\n\n eventDispatcher!: InformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #isCharging = false;\n get isCharging() {\n return this.#isCharging;\n }\n #updateIsCharging(updatedIsCharging: boolean) {\n _console.assertTypeWithError(updatedIsCharging, \"boolean\");\n this.#isCharging = updatedIsCharging;\n _console.log({ isCharging: this.#isCharging });\n this.#dispatchEvent(\"isCharging\", { isCharging: this.#isCharging });\n }\n\n #batteryCurrent!: number;\n get batteryCurrent() {\n return this.#batteryCurrent;\n }\n async getBatteryCurrent() {\n _console.log(\"getting battery current...\");\n const promise = this.waitForEvent(\"getBatteryCurrent\");\n this.sendMessage([{ type: \"getBatteryCurrent\" }]);\n await promise;\n }\n #updateBatteryCurrent(updatedBatteryCurrent: number) {\n _console.assertTypeWithError(updatedBatteryCurrent, \"number\");\n this.#batteryCurrent = updatedBatteryCurrent;\n _console.log({ batteryCurrent: this.#batteryCurrent });\n this.#dispatchEvent(\"getBatteryCurrent\", { batteryCurrent: this.#batteryCurrent });\n }\n\n #id!: string;\n get id() {\n return this.#id;\n }\n #updateId(updatedId: string) {\n _console.assertTypeWithError(updatedId, \"string\");\n this.#id = updatedId;\n _console.log({ id: this.#id });\n this.#dispatchEvent(\"getId\", { id: this.#id });\n }\n\n #name = \"\";\n get name() {\n return this.#name;\n }\n\n updateName(updatedName: string) {\n _console.assertTypeWithError(updatedName, \"string\");\n this.#name = updatedName;\n _console.log({ updatedName: this.#name });\n this.#dispatchEvent(\"getName\", { name: this.#name });\n }\n async setName(newName: string) {\n _console.assertTypeWithError(newName, \"string\");\n _console.assertWithError(\n newName.length >= MinNameLength,\n `name must be greater than ${MinNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n _console.assertWithError(\n newName.length < MaxNameLength,\n `name must be less than ${MaxNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n const setNameData = textEncoder.encode(newName);\n _console.log({ setNameData });\n\n const promise = this.waitForEvent(\"getName\");\n this.sendMessage([{ type: \"setName\", data: setNameData.buffer }]);\n await promise;\n }\n\n // TYPE\n #type!: DeviceType;\n get type() {\n return this.#type;\n }\n get typeEnum() {\n return DeviceTypes.indexOf(this.type);\n }\n #assertValidDeviceType(type: DeviceType) {\n _console.assertEnumWithError(type, DeviceTypes);\n }\n #assertValidDeviceTypeEnum(typeEnum: number) {\n _console.assertTypeWithError(typeEnum, \"number\");\n _console.assertWithError(typeEnum in DeviceTypes, `invalid typeEnum ${typeEnum}`);\n }\n updateType(updatedType: DeviceType) {\n this.#assertValidDeviceType(updatedType);\n if (updatedType == this.type) {\n _console.log(\"redundant type assignment\");\n return;\n }\n this.#type = updatedType;\n _console.log({ updatedType: this.#type });\n\n this.#dispatchEvent(\"getType\", { type: this.#type });\n }\n async #setTypeEnum(newTypeEnum: number) {\n this.#assertValidDeviceTypeEnum(newTypeEnum);\n const setTypeData = Uint8Array.from([newTypeEnum]);\n _console.log({ setTypeData });\n const promise = this.waitForEvent(\"getType\");\n this.sendMessage([{ type: \"setType\", data: setTypeData.buffer }]);\n await promise;\n }\n async setType(newType: DeviceType) {\n this.#assertValidDeviceType(newType);\n const newTypeEnum = DeviceTypes.indexOf(newType);\n this.#setTypeEnum(newTypeEnum);\n }\n\n get isInsole() {\n switch (this.type) {\n case \"leftInsole\":\n case \"rightInsole\":\n return true;\n default:\n // for future non-insole device types\n return false;\n }\n }\n\n get insoleSide(): InsoleSide {\n switch (this.type) {\n case \"leftInsole\":\n return \"left\";\n case \"rightInsole\":\n return \"right\";\n }\n }\n\n #mtu = 0;\n get mtu() {\n return this.#mtu;\n }\n #updateMtu(newMtu: number) {\n _console.assertTypeWithError(newMtu, \"number\");\n if (this.#mtu == newMtu) {\n _console.log(\"redundant mtu assignment\", newMtu);\n return;\n }\n this.#mtu = newMtu;\n\n this.#dispatchEvent(\"getMtu\", { mtu: this.#mtu });\n }\n\n #isCurrentTimeSet = false;\n get isCurrentTimeSet() {\n return this.#isCurrentTimeSet;\n }\n\n #onCurrentTime(currentTime: number) {\n _console.log({ currentTime });\n this.#isCurrentTimeSet = currentTime != 0 || Math.abs(Date.now() - currentTime) < Uint16Max;\n if (!this.#isCurrentTimeSet) {\n this.#setCurrentTime(false);\n }\n }\n async #setCurrentTime(sendImmediately?: boolean) {\n _console.log(\"setting current time...\");\n const dataView = new DataView(new ArrayBuffer(8));\n dataView.setBigUint64(0, BigInt(Date.now()), true);\n const promise = this.waitForEvent(\"getCurrentTime\");\n this.sendMessage([{ type: \"setCurrentTime\", data: dataView.buffer }], sendImmediately);\n await promise;\n }\n\n // MESSAGE\n parseMessage(messageType: InformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"isCharging\":\n const isCharging = Boolean(dataView.getUint8(0));\n _console.log({ isCharging });\n this.#updateIsCharging(isCharging);\n break;\n case \"getBatteryCurrent\":\n const batteryCurrent = dataView.getFloat32(0, true);\n _console.log({ batteryCurrent });\n this.#updateBatteryCurrent(batteryCurrent);\n break;\n case \"getId\":\n const id = textDecoder.decode(dataView.buffer);\n _console.log({ id });\n this.#updateId(id);\n break;\n case \"getName\":\n case \"setName\":\n const name = textDecoder.decode(dataView.buffer);\n _console.log({ name });\n this.updateName(name);\n break;\n case \"getType\":\n case \"setType\":\n const typeEnum = dataView.getUint8(0);\n const type = DeviceTypes[typeEnum];\n _console.log({ typeEnum, type });\n this.updateType(type);\n break;\n case \"getMtu\":\n const mtu = dataView.getUint16(0, true);\n _console.log({ mtu });\n this.#updateMtu(mtu);\n break;\n case \"getCurrentTime\":\n case \"setCurrentTime\":\n const currentTime = Number(dataView.getBigUint64(0, true));\n this.#onCurrentTime(currentTime);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n this.#isCurrentTimeSet = false;\n }\n}\n\nexport default InformationManager;\n","export const VibrationWaveformEffects = [\n \"none\",\n \"strongClick100\",\n \"strongClick60\",\n \"strongClick30\",\n \"sharpClick100\",\n \"sharpClick60\",\n \"sharpClick30\",\n \"softBump100\",\n \"softBump60\",\n \"softBump30\",\n \"doubleClick100\",\n \"doubleClick60\",\n \"tripleClick100\",\n \"softFuzz60\",\n \"strongBuzz100\",\n \"alert750ms\",\n \"alert1000ms\",\n \"strongClick1_100\",\n \"strongClick2_80\",\n \"strongClick3_60\",\n \"strongClick4_30\",\n \"mediumClick100\",\n \"mediumClick80\",\n \"mediumClick60\",\n \"sharpTick100\",\n \"sharpTick80\",\n \"sharpTick60\",\n \"shortDoubleClickStrong100\",\n \"shortDoubleClickStrong80\",\n \"shortDoubleClickStrong60\",\n \"shortDoubleClickStrong30\",\n \"shortDoubleClickMedium100\",\n \"shortDoubleClickMedium80\",\n \"shortDoubleClickMedium60\",\n \"shortDoubleSharpTick100\",\n \"shortDoubleSharpTick80\",\n \"shortDoubleSharpTick60\",\n \"longDoubleSharpClickStrong100\",\n \"longDoubleSharpClickStrong80\",\n \"longDoubleSharpClickStrong60\",\n \"longDoubleSharpClickStrong30\",\n \"longDoubleSharpClickMedium100\",\n \"longDoubleSharpClickMedium80\",\n \"longDoubleSharpClickMedium60\",\n \"longDoubleSharpTick100\",\n \"longDoubleSharpTick80\",\n \"longDoubleSharpTick60\",\n \"buzz100\",\n \"buzz80\",\n \"buzz60\",\n \"buzz40\",\n \"buzz20\",\n \"pulsingStrong100\",\n \"pulsingStrong60\",\n \"pulsingMedium100\",\n \"pulsingMedium60\",\n \"pulsingSharp100\",\n \"pulsingSharp60\",\n \"transitionClick100\",\n \"transitionClick80\",\n \"transitionClick60\",\n \"transitionClick40\",\n \"transitionClick20\",\n \"transitionClick10\",\n \"transitionHum100\",\n \"transitionHum80\",\n \"transitionHum60\",\n \"transitionHum40\",\n \"transitionHum20\",\n \"transitionHum10\",\n \"transitionRampDownLongSmooth2_100\",\n \"transitionRampDownLongSmooth1_100\",\n \"transitionRampDownMediumSmooth1_100\",\n \"transitionRampDownMediumSmooth2_100\",\n \"transitionRampDownShortSmooth1_100\",\n \"transitionRampDownShortSmooth2_100\",\n \"transitionRampDownLongSharp1_100\",\n \"transitionRampDownLongSharp2_100\",\n \"transitionRampDownMediumSharp1_100\",\n \"transitionRampDownMediumSharp2_100\",\n \"transitionRampDownShortSharp1_100\",\n \"transitionRampDownShortSharp2_100\",\n \"transitionRampUpLongSmooth1_100\",\n \"transitionRampUpLongSmooth2_100\",\n \"transitionRampUpMediumSmooth1_100\",\n \"transitionRampUpMediumSmooth2_100\",\n \"transitionRampUpShortSmooth1_100\",\n \"transitionRampUpShortSmooth2_100\",\n \"transitionRampUpLongSharp1_100\",\n \"transitionRampUpLongSharp2_100\",\n \"transitionRampUpMediumSharp1_100\",\n \"transitionRampUpMediumSharp2_100\",\n \"transitionRampUpShortSharp1_100\",\n \"transitionRampUpShortSharp2_100\",\n \"transitionRampDownLongSmooth1_50\",\n \"transitionRampDownLongSmooth2_50\",\n \"transitionRampDownMediumSmooth1_50\",\n \"transitionRampDownMediumSmooth2_50\",\n \"transitionRampDownShortSmooth1_50\",\n \"transitionRampDownShortSmooth2_50\",\n \"transitionRampDownLongSharp1_50\",\n \"transitionRampDownLongSharp2_50\",\n \"transitionRampDownMediumSharp1_50\",\n \"transitionRampDownMediumSharp2_50\",\n \"transitionRampDownShortSharp1_50\",\n \"transitionRampDownShortSharp2_50\",\n \"transitionRampUpLongSmooth1_50\",\n \"transitionRampUpLongSmooth2_50\",\n \"transitionRampUpMediumSmooth1_50\",\n \"transitionRampUpMediumSmooth2_50\",\n \"transitionRampUpShortSmooth1_50\",\n \"transitionRampUpShortSmooth2_50\",\n \"transitionRampUpLongSharp1_50\",\n \"transitionRampUpLongSharp2_50\",\n \"transitionRampUpMediumSharp1_50\",\n \"transitionRampUpMediumSharp2_50\",\n \"transitionRampUpShortSharp1_50\",\n \"transitionRampUpShortSharp2_50\",\n \"longBuzz100\",\n \"smoothHum50\",\n \"smoothHum40\",\n \"smoothHum30\",\n \"smoothHum20\",\n \"smoothHum10\",\n] as const;\n\nexport type VibrationWaveformEffect = (typeof VibrationWaveformEffects)[number];\n","import { createConsole } from \"../utils/Console.ts\";\nimport { VibrationWaveformEffect, VibrationWaveformEffects } from \"./VibrationWaveformEffects.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"VibrationManager\");\n\nexport const VibrationLocations = [\"front\", \"rear\"] as const;\nexport type VibrationLocation = (typeof VibrationLocations)[number];\n\nexport const VibrationTypes = [\"waveformEffect\", \"waveform\"] as const;\nexport type VibrationType = (typeof VibrationTypes)[number];\n\nexport interface VibrationWaveformEffectSegment {\n effect?: VibrationWaveformEffect;\n delay?: number;\n loopCount?: number;\n}\n\nexport interface VibrationWaveformSegment {\n duration: number;\n amplitude: number;\n}\n\nexport const VibrationMessageTypes = [\"triggerVibration\"] as const;\nexport type VibrationMessageType = (typeof VibrationMessageTypes)[number];\n\nexport const MaxNumberOfVibrationWaveformEffectSegments = 8;\nexport const MaxVibrationWaveformSegmentDuration = 2550;\nexport const MaxVibrationWaveformEffectSegmentDelay = 1270;\nexport const MaxVibrationWaveformEffectSegmentLoopCount = 3;\nexport const MaxNumberOfVibrationWaveformSegments = 20;\nexport const MaxVibrationWaveformEffectSequenceLoopCount = 6;\n\ninterface BaseVibrationConfiguration {\n type: VibrationType;\n locations?: VibrationLocation[];\n}\n\nexport interface VibrationWaveformEffectConfiguration extends BaseVibrationConfiguration {\n type: \"waveformEffect\";\n segments: VibrationWaveformEffectSegment[];\n loopCount?: number;\n}\n\nexport interface VibrationWaveformConfiguration extends BaseVibrationConfiguration {\n type: \"waveform\";\n segments: VibrationWaveformSegment[];\n}\n\nexport type VibrationConfiguration = VibrationWaveformEffectConfiguration | VibrationWaveformConfiguration;\n\nexport type SendVibrationMessageCallback = SendMessageCallback<VibrationMessageType>;\n\nclass VibrationManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendVibrationMessageCallback;\n\n #verifyLocation(location: VibrationLocation) {\n _console.assertTypeWithError(location, \"string\");\n _console.assertWithError(VibrationLocations.includes(location), `invalid location \"${location}\"`);\n }\n #verifyLocations(locations: VibrationLocation[]) {\n this.#assertNonEmptyArray(locations);\n locations.forEach((location) => {\n this.#verifyLocation(location);\n });\n }\n #createLocationsBitmask(locations: VibrationLocation[]) {\n this.#verifyLocations(locations);\n\n let locationsBitmask = 0;\n locations.forEach((location) => {\n const locationIndex = VibrationLocations.indexOf(location);\n locationsBitmask |= 1 << locationIndex;\n });\n _console.log({ locationsBitmask });\n _console.assertWithError(locationsBitmask > 0, `locationsBitmask must not be zero`);\n return locationsBitmask;\n }\n\n #assertNonEmptyArray(array: any[]) {\n _console.assertWithError(Array.isArray(array), \"passed non-array\");\n _console.assertWithError(array.length > 0, \"passed empty array\");\n }\n\n #verifyWaveformEffect(waveformEffect: VibrationWaveformEffect) {\n _console.assertWithError(\n VibrationWaveformEffects.includes(waveformEffect),\n `invalid waveformEffect \"${waveformEffect}\"`\n );\n }\n\n #verifyWaveformEffectSegment(waveformEffectSegment: VibrationWaveformEffectSegment) {\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n this.#verifyWaveformEffect(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n _console.assertWithError(delay >= 0, `delay must be 0ms or greater (got ${delay})`);\n _console.assertWithError(\n delay <= MaxVibrationWaveformEffectSegmentDelay,\n `delay must be ${MaxVibrationWaveformEffectSegmentDelay}ms or less (got ${delay})`\n );\n } else {\n throw Error(\"no effect or delay found in waveformEffectSegment\");\n }\n\n if (waveformEffectSegment.loopCount != undefined) {\n const { loopCount } = waveformEffectSegment;\n this.#verifyWaveformEffectSegmentLoopCount(loopCount);\n }\n }\n\n #verifyWaveformEffectSegmentLoopCount(waveformEffectSegmentLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSegmentLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSegmentLoopCount >= 0,\n `waveformEffectSegmentLoopCount must be 0 or greater (got ${waveformEffectSegmentLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSegmentLoopCount <= MaxVibrationWaveformEffectSegmentLoopCount,\n `waveformEffectSegmentLoopCount must be ${MaxVibrationWaveformEffectSegmentLoopCount} or fewer (got ${waveformEffectSegmentLoopCount})`\n );\n }\n\n #verifyWaveformEffectSegments(waveformEffectSegments: VibrationWaveformEffectSegment[]) {\n this.#assertNonEmptyArray(waveformEffectSegments);\n _console.assertWithError(\n waveformEffectSegments.length <= MaxNumberOfVibrationWaveformEffectSegments,\n `must have ${MaxNumberOfVibrationWaveformEffectSegments} waveformEffectSegments or fewer (got ${waveformEffectSegments.length})`\n );\n waveformEffectSegments.forEach((waveformEffectSegment) => {\n this.#verifyWaveformEffectSegment(waveformEffectSegment);\n });\n }\n\n #verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSequenceLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSequenceLoopCount >= 0,\n `waveformEffectSequenceLoopCount must be 0 or greater (got ${waveformEffectSequenceLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSequenceLoopCount <= MaxVibrationWaveformEffectSequenceLoopCount,\n `waveformEffectSequenceLoopCount must be ${MaxVibrationWaveformEffectSequenceLoopCount} or fewer (got ${waveformEffectSequenceLoopCount})`\n );\n }\n\n #verifyWaveformSegment(waveformSegment: VibrationWaveformSegment) {\n _console.assertTypeWithError(waveformSegment.amplitude, \"number\");\n _console.assertWithError(\n waveformSegment.amplitude >= 0,\n `amplitude must be 0 or greater (got ${waveformSegment.amplitude})`\n );\n _console.assertWithError(\n waveformSegment.amplitude <= 1,\n `amplitude must be 1 or less (got ${waveformSegment.amplitude})`\n );\n\n _console.assertTypeWithError(waveformSegment.duration, \"number\");\n _console.assertWithError(\n waveformSegment.duration > 0,\n `duration must be greater than 0ms (got ${waveformSegment.duration}ms)`\n );\n _console.assertWithError(\n waveformSegment.duration <= MaxVibrationWaveformSegmentDuration,\n `duration must be ${MaxVibrationWaveformSegmentDuration}ms or less (got ${waveformSegment.duration}ms)`\n );\n }\n\n #verifyWaveformSegments(waveformSegments: VibrationWaveformSegment[]) {\n this.#assertNonEmptyArray(waveformSegments);\n _console.assertWithError(\n waveformSegments.length <= MaxNumberOfVibrationWaveformSegments,\n `must have ${MaxNumberOfVibrationWaveformSegments} waveformSegments or fewer (got ${waveformSegments.length})`\n );\n waveformSegments.forEach((waveformSegment) => {\n this.#verifyWaveformSegment(waveformSegment);\n });\n }\n\n #createWaveformEffectsData(\n locations: VibrationLocation[],\n waveformEffectSegments: VibrationWaveformEffectSegment[],\n waveformEffectSequenceLoopCount: number = 0\n ) {\n this.#verifyWaveformEffectSegments(waveformEffectSegments);\n this.#verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount);\n\n let dataArray = [];\n let byteOffset = 0;\n\n const hasAtLeast1WaveformEffectWithANonzeroLoopCount = waveformEffectSegments.some((waveformEffectSegment) => {\n const { loopCount } = waveformEffectSegment;\n return loopCount != undefined && loopCount > 0;\n });\n\n const includeAllWaveformEffectSegments =\n hasAtLeast1WaveformEffectWithANonzeroLoopCount || waveformEffectSequenceLoopCount != 0;\n\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegments && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegment = waveformEffectSegments[index] || { effect: \"none\" };\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n dataArray[byteOffset++] = VibrationWaveformEffects.indexOf(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n dataArray[byteOffset++] = (1 << 7) | Math.floor(delay / 10); // set most significant bit to 1\n } else {\n throw Error(\"invalid waveformEffectSegment\");\n }\n }\n\n const includeAllWaveformEffectSegmentLoopCounts = waveformEffectSequenceLoopCount != 0;\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegmentLoopCounts && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegmentLoopCount = waveformEffectSegments[index]?.loopCount || 0;\n if (index == 0 || index == 4) {\n dataArray[byteOffset] = 0;\n }\n const bitOffset = 2 * (index % 4);\n dataArray[byteOffset] |= waveformEffectSegmentLoopCount << bitOffset;\n if (index == 3 || index == 7) {\n byteOffset++;\n }\n }\n\n if (waveformEffectSequenceLoopCount != 0) {\n dataArray[byteOffset++] = waveformEffectSequenceLoopCount;\n }\n const dataView = new DataView(Uint8Array.from(dataArray).buffer);\n _console.log({ dataArray, dataView });\n return this.#createData(locations, \"waveformEffect\", dataView);\n }\n #createWaveformData(locations: VibrationLocation[], waveformSegments: VibrationWaveformSegment[]) {\n this.#verifyWaveformSegments(waveformSegments);\n const dataView = new DataView(new ArrayBuffer(waveformSegments.length * 2));\n waveformSegments.forEach((waveformSegment, index) => {\n dataView.setUint8(index * 2, Math.floor(waveformSegment.amplitude * 127));\n dataView.setUint8(index * 2 + 1, Math.floor(waveformSegment.duration / 10));\n });\n _console.log({ dataView });\n return this.#createData(locations, \"waveform\", dataView);\n }\n\n #verifyVibrationType(vibrationType: VibrationType) {\n _console.assertTypeWithError(vibrationType, \"string\");\n _console.assertWithError(VibrationTypes.includes(vibrationType), `invalid vibrationType \"${vibrationType}\"`);\n }\n\n #createData(locations: VibrationLocation[], vibrationType: VibrationType, dataView: DataView) {\n _console.assertWithError(dataView?.byteLength > 0, \"no data received\");\n const locationsBitmask = this.#createLocationsBitmask(locations);\n this.#verifyVibrationType(vibrationType);\n const vibrationTypeIndex = VibrationTypes.indexOf(vibrationType);\n _console.log({ locationsBitmask, vibrationTypeIndex, dataView });\n const data = concatenateArrayBuffers(locationsBitmask, vibrationTypeIndex, dataView.byteLength, dataView);\n _console.log({ data });\n return data;\n }\n\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately: boolean = true) {\n let triggerVibrationData!: ArrayBuffer;\n vibrationConfigurations.forEach((vibrationConfiguration) => {\n const { type } = vibrationConfiguration;\n\n let { locations } = vibrationConfiguration;\n locations = locations || VibrationLocations.slice();\n\n let arrayBuffer: ArrayBuffer;\n\n switch (type) {\n case \"waveformEffect\":\n {\n const { segments, loopCount } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformEffectsData(locations, segments, loopCount);\n }\n break;\n case \"waveform\":\n {\n const { segments } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformData(locations, segments);\n }\n break;\n default:\n throw Error(`invalid vibration type \"${type}\"`);\n }\n _console.log({ type, arrayBuffer });\n triggerVibrationData = concatenateArrayBuffers(triggerVibrationData, arrayBuffer);\n });\n await this.sendMessage([{ type: \"triggerVibration\", data: triggerVibrationData }], sendImmediately);\n }\n}\n\nexport default VibrationManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport Timer from \"../utils/Timer.ts\";\n\nimport { FileTransferMessageTypes } from \"../FileTransferManager.ts\";\nimport { TfliteMessageTypes } from \"../TfliteManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { InformationMessageTypes } from \"../InformationManager.ts\";\nimport { VibrationMessageTypes } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfigurationMessageTypes } from \"../sensor/SensorConfigurationManager.ts\";\nimport { SensorDataMessageTypes } from \"../sensor/SensorDataManager.ts\";\n\nconst _console = createConsole(\"BaseConnectionManager\", { log: true });\n\nexport const ConnectionTypes = [\"webBluetooth\", \"noble\", \"client\"] as const;\nexport type ConnectionType = (typeof ConnectionTypes)[number];\n\nexport const ConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ConnectionStatus = (typeof ConnectionStatuses)[number];\n\nexport const ConnectionEventTypes = [...ConnectionStatuses, \"connectionStatus\", \"isConnected\"] as const;\nexport type ConnectionEventType = (typeof ConnectionEventTypes)[number];\n\nexport interface ConnectionStatusEventMessages {\n notConnected: any;\n connecting: any;\n connected: any;\n disconnecting: any;\n connectionStatus: { connectionStatus: ConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport interface TxMessage {\n type: TxRxMessageType;\n data?: ArrayBuffer;\n}\n\nexport const TxRxMessageTypes = [\n ...InformationMessageTypes,\n ...SensorConfigurationMessageTypes,\n ...SensorDataMessageTypes,\n ...VibrationMessageTypes,\n ...TfliteMessageTypes,\n ...FileTransferMessageTypes,\n] as const;\nexport type TxRxMessageType = (typeof TxRxMessageTypes)[number];\n\nexport const SMPMessageTypes = [\"smp\"] as const;\nexport type SMPMessageType = (typeof SMPMessageTypes)[number];\n\nexport const BatteryLevelMessageTypes = [\"batteryLevel\"] as const;\nexport type BatteryLevelMessageType = (typeof BatteryLevelMessageTypes)[number];\n\nexport const MetaConnectionMessageTypes = [\"rx\", \"tx\"] as const;\nexport type MetaConnectionMessageType = (typeof MetaConnectionMessageTypes)[number];\n\nexport const ConnectionMessageTypes = [\n ...BatteryLevelMessageTypes,\n ...DeviceInformationMessageTypes,\n ...MetaConnectionMessageTypes,\n ...TxRxMessageTypes,\n ...SMPMessageTypes,\n] as const;\nexport type ConnectionMessageType = (typeof ConnectionMessageTypes)[number];\n\nexport type ConnectionStatusCallback = (status: ConnectionStatus) => void;\nexport type MessageReceivedCallback = (messageType: ConnectionMessageType, dataView: DataView) => void;\nexport type MessagesReceivedCallback = () => void;\n\nabstract class BaseConnectionManager {\n static #AssertValidTxRxMessageType(messageType: TxRxMessageType) {\n _console.assertEnumWithError(messageType, TxRxMessageTypes);\n }\n\n abstract get bluetoothId(): string;\n\n // CALLBACKS\n onStatusUpdated?: ConnectionStatusCallback;\n onMessageReceived?: MessageReceivedCallback;\n onMessagesReceived?: MessagesReceivedCallback;\n\n protected get baseConstructor() {\n return this.constructor as typeof BaseConnectionManager;\n }\n static get isSupported() {\n return false;\n }\n get isSupported() {\n return this.baseConstructor.isSupported;\n }\n\n static type: ConnectionType;\n get type(): ConnectionType {\n return this.baseConstructor.type;\n }\n\n /** @throws {Error} if not supported */\n #assertIsSupported() {\n _console.assertWithError(this.isSupported, `${this.constructor.name} is not supported`);\n }\n\n constructor() {\n this.#assertIsSupported();\n }\n\n #status: ConnectionStatus = \"notConnected\";\n get status() {\n return this.#status;\n }\n protected set status(newConnectionStatus) {\n _console.assertEnumWithError(newConnectionStatus, ConnectionStatuses);\n if (this.#status == newConnectionStatus) {\n _console.log(`tried to assign same connection status \"${newConnectionStatus}\"`);\n return;\n }\n _console.log(`new connection status \"${newConnectionStatus}\"`);\n this.#status = newConnectionStatus;\n this.onStatusUpdated!(this.status);\n\n if (this.isConnected) {\n this.#timer.start();\n } else {\n this.#timer.stop();\n }\n\n if (this.#status == \"notConnected\") {\n this.mtu = undefined;\n }\n }\n\n get isConnected() {\n return this.status == \"connected\";\n }\n\n /** @throws {Error} if connected */\n #assertIsNotConnected() {\n _console.assertWithError(!this.isConnected, \"device is already connected\");\n }\n /** @throws {Error} if connecting */\n #assertIsNotConnecting() {\n _console.assertWithError(this.status != \"connecting\", \"device is already connecting\");\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"device is not connected\");\n }\n /** @throws {Error} if disconnecting */\n #assertIsNotDisconnecting() {\n _console.assertWithError(this.status != \"disconnecting\", \"device is already disconnecting\");\n }\n /** @throws {Error} if not connected or is disconnecting */\n #assertIsConnectedAndNotDisconnecting() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n }\n\n async connect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n this.status = \"connecting\";\n }\n get canReconnect() {\n return false;\n }\n async reconnect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n _console.assert(this.canReconnect, \"unable to reconnect\");\n }\n async disconnect() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n this.status = \"disconnecting\";\n _console.log(\"disconnecting from device...\");\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n this.#assertIsConnectedAndNotDisconnecting();\n _console.log(\"sending smp message\", data);\n }\n\n #pendingMessages: TxMessage[] = [];\n #isSendingMessages = false;\n async sendTxMessages(messages: TxMessage[] | undefined, sendImmediately: boolean = true) {\n this.#assertIsConnectedAndNotDisconnecting();\n\n if (messages) {\n this.#pendingMessages.push(...messages);\n _console.log(`appended ${messages.length} messages`);\n }\n\n if (!sendImmediately) {\n _console.log(\"not sending immediately - waiting until later\");\n return;\n }\n\n if (this.#isSendingMessages) {\n console.log(\"already sending messages - waiting until later\");\n return;\n }\n this.#isSendingMessages = true;\n\n _console.log(\"sendTxMessages\", this.#pendingMessages.slice());\n\n const arrayBuffers = this.#pendingMessages.map((message) => {\n BaseConnectionManager.#AssertValidTxRxMessageType(message.type);\n const messageTypeEnum = TxRxMessageTypes.indexOf(message.type);\n const dataLength = new DataView(new ArrayBuffer(2));\n dataLength.setUint16(0, message.data?.byteLength || 0, true);\n return concatenateArrayBuffers(messageTypeEnum, dataLength, message.data);\n });\n this.#pendingMessages.length = 0;\n\n if (this.mtu) {\n while (arrayBuffers.length > 0) {\n let arrayBufferByteLength = 0;\n let arrayBufferCount = 0;\n arrayBuffers.some((arrayBuffer) => {\n if (arrayBufferByteLength + arrayBuffer.byteLength > this.mtu! - 3) {\n return true;\n }\n arrayBufferCount++;\n arrayBufferByteLength += arrayBuffer.byteLength;\n });\n const arrayBuffersToSend = arrayBuffers.splice(0, arrayBufferCount);\n _console.log({ arrayBufferCount, arrayBuffersToSend });\n\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffersToSend);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n } else {\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffers);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n\n this.#isSendingMessages = false;\n }\n\n mtu?: number;\n\n async sendTxData(data: ArrayBuffer) {\n _console.log(\"sendTxData\", data);\n }\n\n parseRxMessage(dataView: DataView) {\n parseMessage(dataView, TxRxMessageTypes, this.#onRxMessage.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onRxMessage(messageType: TxRxMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n this.onMessageReceived!(messageType, dataView);\n }\n\n #timer = new Timer(this.#checkConnection.bind(this), 5000);\n #checkConnection() {\n //console.log(\"checking connection...\");\n if (!this.isConnected) {\n _console.log(\"timer detected disconnection\");\n this.status = \"notConnected\";\n }\n }\n\n clear() {\n this.#isSendingMessages = false;\n this.#pendingMessages.length = 0;\n }\n}\n\nexport default BaseConnectionManager;\n","export function spacesToPascalCase(string: string) {\n return string\n .replace(/(?:^\\w|\\b\\w)/g, function (match) {\n return match.toUpperCase();\n })\n .replace(/\\s+/g, \"\");\n}\n\nexport function capitalizeFirstCharacter(string: string) {\n return string[0].toUpperCase() + string.slice(1);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { spacesToPascalCase } from \"./stringUtils.ts\";\n\nconst _console = createConsole(\"EventUtils\", { log: false });\n\ntype BoundEventListeners = { [eventType: string]: EventListener };\nexport type BoundGenericEventListeners = { [eventType: string]: Function };\n\nexport function bindEventListeners(\n eventTypes: readonly string[],\n boundEventListeners: BoundGenericEventListeners,\n target: any\n) {\n _console.log(\"bindEventListeners\", { eventTypes, boundEventListeners, target });\n eventTypes.forEach((eventType) => {\n const _eventType = `_on${spacesToPascalCase(eventType)}`;\n _console.assertWithError(target[_eventType], `no event \"${_eventType}\" found in target`);\n _console.log(`binding eventType \"${eventType}\" as ${_eventType} from target`, target);\n const boundEvent = target[_eventType].bind(target);\n target[_eventType] = boundEvent;\n boundEventListeners[eventType] = boundEvent;\n });\n}\n\nexport function addEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let addEventListener = target.addEventListener || target.addListener || target.on || target.AddEventListener;\n _console.assertWithError(addEventListener, \"no add listener function found for target\");\n addEventListener = addEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n addEventListener(eventType, eventListener);\n });\n}\n\nexport function removeEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let removeEventListener = target.removeEventListener || target.removeListener || target.RemoveEventListener;\n _console.assertWithError(removeEventListener, \"no remove listener function found for target\");\n removeEventListener = removeEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n removeEventListener(eventType, eventListener);\n });\n}\n","import { isInBrowser, isInNode } from \"../../utils/environment.ts\";\nimport { createConsole } from \"../../utils/Console.ts\";\n\nconst _console = createConsole(\"bluetoothUUIDs\", { log: false });\n\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nvar BluetoothUUID = webbluetooth.BluetoothUUID;\n/** NODE_END */\n/** BROWSER_START */\nif (isInBrowser) {\n var BluetoothUUID = window.BluetoothUUID;\n}\n/** BROWSER_END */\n\nfunction generateBluetoothUUID(value: string): BluetoothServiceUUID {\n _console.assertTypeWithError(value, \"string\");\n _console.assertWithError(value.length == 4, \"value must be 4 characters long\");\n return `ea6da725-${value}-4f9b-893d-c3913e33b39f`;\n}\n\nfunction stringToCharacteristicUUID(identifier: string): BluetoothCharacteristicUUID {\n return BluetoothUUID?.getCharacteristic?.(identifier);\n}\n\nfunction stringToServiceUUID(identifier: string): BluetoothServiceUUID {\n return BluetoothUUID?.getService?.(identifier);\n}\n\nexport type BluetoothServiceName = \"deviceInformation\" | \"battery\" | \"main\" | \"smp\";\nimport { DeviceInformationMessageType } from \"../../DeviceInformationManager.ts\";\nexport type BluetoothCharacteristicName = DeviceInformationMessageType | \"batteryLevel\" | \"rx\" | \"tx\" | \"smp\";\n\ninterface BluetoothCharacteristicInformation {\n uuid: BluetoothCharacteristicUUID;\n}\ninterface BluetoothServiceInformation {\n uuid: BluetoothServiceUUID;\n characteristics: { [characteristicName in BluetoothCharacteristicName]?: BluetoothCharacteristicInformation };\n}\ninterface BluetoothServicesInformation {\n services: { [serviceName in BluetoothServiceName]: BluetoothServiceInformation };\n}\nconst bluetoothUUIDs: BluetoothServicesInformation = Object.freeze({\n services: {\n deviceInformation: {\n uuid: stringToServiceUUID(\"device_information\"),\n characteristics: {\n manufacturerName: {\n uuid: stringToCharacteristicUUID(\"manufacturer_name_string\"),\n },\n modelNumber: {\n uuid: stringToCharacteristicUUID(\"model_number_string\"),\n },\n hardwareRevision: {\n uuid: stringToCharacteristicUUID(\"hardware_revision_string\"),\n },\n firmwareRevision: {\n uuid: stringToCharacteristicUUID(\"firmware_revision_string\"),\n },\n softwareRevision: {\n uuid: stringToCharacteristicUUID(\"software_revision_string\"),\n },\n pnpId: {\n uuid: stringToCharacteristicUUID(\"pnp_id\"),\n },\n serialNumber: {\n uuid: stringToCharacteristicUUID(\"serial_number_string\"),\n },\n },\n },\n battery: {\n uuid: stringToServiceUUID(\"battery_service\"),\n characteristics: {\n batteryLevel: {\n uuid: stringToCharacteristicUUID(\"battery_level\"),\n },\n },\n },\n main: {\n uuid: generateBluetoothUUID(\"0000\"),\n characteristics: {\n rx: { uuid: generateBluetoothUUID(\"1000\") },\n tx: { uuid: generateBluetoothUUID(\"1001\") },\n },\n },\n smp: {\n uuid: \"8d53dc1d-1db7-4cd3-868b-8a527460aa84\",\n characteristics: {\n smp: { uuid: \"da2e7828-fbce-4e01-ae9e-261174997c48\" },\n },\n },\n },\n});\n\nexport const serviceUUIDs = [bluetoothUUIDs.services.main.uuid];\nexport const optionalServiceUUIDs = [\n bluetoothUUIDs.services.deviceInformation.uuid,\n bluetoothUUIDs.services.battery.uuid,\n bluetoothUUIDs.services.smp.uuid,\n];\nexport const allServiceUUIDs = [...serviceUUIDs, ...optionalServiceUUIDs];\n\nexport function getServiceNameFromUUID(serviceUUID: BluetoothServiceUUID): BluetoothServiceName | undefined {\n serviceUUID = serviceUUID.toString().toLowerCase();\n const serviceNames = Object.keys(bluetoothUUIDs.services) as BluetoothServiceName[];\n return serviceNames.find((serviceName) => {\n const serviceInfo = bluetoothUUIDs.services[serviceName];\n let serviceInfoUUID = serviceInfo.uuid.toString();\n if (serviceUUID.length == 4) {\n serviceInfoUUID = serviceInfoUUID.slice(4, 8);\n }\n if (!serviceUUID.includes(\"-\")) {\n serviceInfoUUID = serviceInfoUUID.replaceAll(\"-\", \"\");\n }\n return serviceUUID == serviceInfoUUID;\n });\n}\n\nexport const characteristicUUIDs: BluetoothCharacteristicUUID[] = [];\nexport const allCharacteristicUUIDs: BluetoothCharacteristicUUID[] = [];\n\nexport const characteristicNames: BluetoothCharacteristicName[] = [];\nexport const allCharacteristicNames: BluetoothCharacteristicName[] = [];\n\nObject.values(bluetoothUUIDs.services).forEach((serviceInfo) => {\n if (!serviceInfo.characteristics) {\n return;\n }\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicNames.forEach((characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[characteristicName]!;\n if (serviceUUIDs.includes(serviceInfo.uuid)) {\n characteristicUUIDs.push(characteristicInfo.uuid);\n characteristicNames.push(characteristicName);\n }\n allCharacteristicUUIDs.push(characteristicInfo.uuid);\n allCharacteristicNames.push(characteristicName);\n });\n}, []);\n\n//_console.log({ characteristicUUIDs, allCharacteristicUUIDs });\n\nexport function getCharacteristicNameFromUUID(\n characteristicUUID: BluetoothCharacteristicUUID\n): BluetoothCharacteristicName | undefined {\n //_console.log({ characteristicUUID });\n characteristicUUID = characteristicUUID.toString().toLowerCase();\n var characteristicName: BluetoothCharacteristicName | undefined;\n Object.values(bluetoothUUIDs.services).some((serviceInfo) => {\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicName = characteristicNames.find((_characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[_characteristicName]!;\n let characteristicInfoUUID = characteristicInfo.uuid.toString();\n if (characteristicUUID.length == 4) {\n characteristicInfoUUID = characteristicInfoUUID.slice(4, 8);\n }\n if (!characteristicUUID.includes(\"-\")) {\n characteristicInfoUUID = characteristicInfoUUID.replaceAll(\"-\", \"\");\n }\n return characteristicUUID == characteristicInfoUUID;\n });\n return characteristicName;\n });\n return characteristicName;\n}\n\nexport function getCharacteristicProperties(\n characteristicName: BluetoothCharacteristicName\n): BluetoothCharacteristicProperties {\n const properties = {\n broadcast: false,\n read: true,\n writeWithoutResponse: false,\n write: false,\n notify: false,\n indicate: false,\n authenticatedSignedWrites: false,\n reliableWrite: false,\n writableAuxiliaries: false,\n };\n\n // read\n switch (characteristicName) {\n case \"rx\":\n case \"tx\":\n case \"smp\":\n properties.read = false;\n break;\n }\n\n // notify\n switch (characteristicName) {\n case \"batteryLevel\":\n case \"rx\":\n case \"smp\":\n properties.notify = true;\n break;\n }\n\n // write without response\n switch (characteristicName) {\n case \"smp\":\n properties.writeWithoutResponse = true;\n break;\n }\n\n // write\n switch (characteristicName) {\n case \"tx\":\n properties.write = true;\n break;\n }\n\n return properties;\n}\n\nexport const serviceDataUUID = \"0000\";\n","import { createConsole } from \"../../utils/Console.ts\";\nimport BaseConnectionManager from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"BluetoothConnectionManager\", { log: true });\n\nimport { BluetoothCharacteristicName } from \"./bluetoothUUIDs.ts\";\n\nabstract class BluetoothConnectionManager extends BaseConnectionManager {\n isInRange = true;\n\n protected onCharacteristicValueChanged(characteristicName: BluetoothCharacteristicName, dataView: DataView) {\n if (characteristicName == \"rx\") {\n this.parseRxMessage(dataView);\n } else {\n this.onMessageReceived?.(characteristicName, dataView);\n }\n }\n\n protected async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n _console.log(\"writeCharacteristic\", ...arguments);\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n await this.writeCharacteristic(\"smp\", data);\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n await this.writeCharacteristic(\"tx\", data);\n }\n}\n\nexport default BluetoothConnectionManager;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { isInNode, isInBrowser, isInBluefy, isInWebBLE } from \"../../utils/environment.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport {\n serviceUUIDs,\n optionalServiceUUIDs,\n getServiceNameFromUUID,\n getCharacteristicNameFromUUID,\n getCharacteristicProperties,\n} from \"./bluetoothUUIDs.ts\";\nimport BluetoothConnectionManager from \"./BluetoothConnectionManager.ts\";\nimport { BluetoothCharacteristicName, BluetoothServiceName } from \"./bluetoothUUIDs.ts\";\nimport { ConnectionType } from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"WebBluetoothConnectionManager\", { log: true });\n\ntype WebBluetoothInterface = webbluetooth.Bluetooth | Bluetooth;\n\ninterface BluetoothService extends BluetoothRemoteGATTService {\n name?: BluetoothServiceName;\n}\ninterface BluetoothCharacteristic extends BluetoothRemoteGATTCharacteristic {\n name?: BluetoothCharacteristicName;\n}\n\nvar bluetooth: WebBluetoothInterface | undefined;\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nif (isInNode) {\n bluetooth = webbluetooth.bluetooth;\n}\n/** NODE_END */\n\n/** BROWSER_START */\nif (isInBrowser) {\n bluetooth = window.navigator.bluetooth;\n}\n/** BROWSER_END */\n\nclass WebBluetoothConnectionManager extends BluetoothConnectionManager {\n get bluetoothId() {\n return this.device!.id;\n }\n\n #boundBluetoothCharacteristicEventListeners: { [eventType: string]: EventListener } = {\n characteristicvaluechanged: this.#onCharacteristicvaluechanged.bind(this),\n };\n #boundBluetoothDeviceEventListeners: { [eventType: string]: EventListener } = {\n gattserverdisconnected: this.#onGattserverdisconnected.bind(this),\n };\n\n static get isSupported() {\n return Boolean(bluetooth);\n }\n static get type(): ConnectionType {\n return \"webBluetooth\";\n }\n\n #device!: BluetoothDevice | undefined;\n get device() {\n return this.#device;\n }\n set device(newDevice) {\n if (this.#device == newDevice) {\n _console.log(\"tried to assign the same BluetoothDevice\");\n return;\n }\n if (this.#device) {\n removeEventListeners(this.#device, this.#boundBluetoothDeviceEventListeners);\n }\n if (newDevice) {\n addEventListeners(newDevice, this.#boundBluetoothDeviceEventListeners);\n }\n this.#device = newDevice;\n }\n\n get server(): BluetoothRemoteGATTServer | undefined {\n return this.#device?.gatt;\n }\n get isConnected() {\n return this.server?.connected || false;\n }\n\n #services: Map<BluetoothServiceName, BluetoothService> = new Map();\n #characteristics: Map<BluetoothCharacteristicName, BluetoothCharacteristic> = new Map();\n\n async connect() {\n await super.connect();\n\n try {\n const device = await bluetooth!.requestDevice({\n filters: [{ services: serviceUUIDs }],\n optionalServices: isInBrowser ? optionalServiceUUIDs : [],\n });\n\n _console.log(\"got BluetoothDevice\");\n this.device = device;\n\n _console.log(\"connecting to device...\");\n const server = await this.server!.connect();\n _console.log(`connected to device? ${server.connected}`);\n\n await this.#getServicesAndCharacteristics();\n\n _console.log(\"fully connected\");\n\n this.status = \"connected\";\n } catch (error) {\n _console.error(error);\n this.status = \"notConnected\";\n this.server?.disconnect();\n this.#removeEventListeners();\n }\n }\n async #getServicesAndCharacteristics() {\n this.#removeEventListeners();\n\n _console.log(\"getting services...\");\n const services = await this.server!.getPrimaryServices();\n _console.log(\"got services\", services.length);\n //const service = await this.server!.getPrimaryService(\"8d53dc1d-1db7-4cd3-868b-8a527460aa84\");\n\n _console.log(\"getting characteristics...\");\n for (const serviceIndex in services) {\n const service = services[serviceIndex] as BluetoothService;\n _console.log({ service });\n const serviceName = getServiceNameFromUUID(service.uuid)!;\n _console.assertWithError(serviceName, `no name found for service uuid \"${service.uuid}\"`);\n _console.log(`got \"${serviceName}\" service`);\n service.name = serviceName;\n this.#services.set(serviceName, service);\n _console.log(`getting characteristics for \"${serviceName}\" service`);\n const characteristics = await service.getCharacteristics();\n _console.log(`got characteristics for \"${serviceName}\" service`);\n for (const characteristicIndex in characteristics) {\n const characteristic = characteristics[characteristicIndex] as BluetoothCharacteristic;\n _console.log({ characteristic });\n const characteristicName = getCharacteristicNameFromUUID(characteristic.uuid)!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic uuid \"${characteristic.uuid}\" in \"${serviceName}\" service`\n );\n _console.log(`got \"${characteristicName}\" characteristic in \"${serviceName}\" service`);\n characteristic.name = characteristicName;\n this.#characteristics.set(characteristicName, characteristic);\n addEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`starting notifications for \"${characteristicName}\" characteristic`);\n await characteristic.startNotifications();\n }\n if (characteristicProperties.read) {\n _console.log(`reading \"${characteristicName}\" characteristic...`);\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n }\n }\n async #removeEventListeners() {\n if (this.device) {\n removeEventListeners(this.device, this.#boundBluetoothDeviceEventListeners);\n }\n\n const promises = Array.from(this.#characteristics.keys()).map((characteristicName) => {\n const characteristic = this.#characteristics.get(characteristicName)!;\n removeEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`stopping notifications for \"${characteristicName}\" characteristic`);\n return characteristic.stopNotifications();\n }\n });\n\n return Promise.allSettled(promises);\n }\n async disconnect() {\n await this.#removeEventListeners();\n await super.disconnect();\n this.server?.disconnect();\n this.status = \"notConnected\";\n }\n\n #onCharacteristicvaluechanged(event: Event) {\n _console.log(\"oncharacteristicvaluechanged\");\n\n const characteristic = event.target as BluetoothCharacteristic;\n this.#onCharacteristicValueChanged(characteristic);\n }\n\n #onCharacteristicValueChanged(characteristic: BluetoothCharacteristic) {\n _console.log(\"onCharacteristicValue\");\n\n const characteristicName = characteristic.name!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic with uuid \"${characteristic.uuid}\"`\n );\n\n _console.log(`oncharacteristicvaluechanged for \"${characteristicName}\" characteristic`);\n const dataView = characteristic.value!;\n _console.assertWithError(dataView, `no data found for \"${characteristicName}\" characteristic`);\n _console.log(`data for \"${characteristicName}\" characteristic`, Array.from(new Uint8Array(dataView.buffer)));\n\n try {\n this.onCharacteristicValueChanged(characteristicName, dataView);\n } catch (error) {\n _console.error(error);\n }\n }\n\n async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n super.writeCharacteristic(characteristicName, data);\n\n const characteristic = this.#characteristics.get(characteristicName)!;\n _console.assertWithError(characteristic, `${characteristicName} characteristic not found`);\n _console.log(\"writing characteristic\", characteristic, data);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.writeWithoutResponse) {\n _console.log(\"writing without response\");\n await characteristic.writeValueWithoutResponse(data);\n } else {\n _console.log(\"writing with response\");\n await characteristic.writeValueWithResponse(data);\n }\n _console.log(\"wrote characteristic\");\n\n if (characteristicProperties.read && !characteristicProperties.notify) {\n _console.log(\"reading value after write...\");\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n\n #onGattserverdisconnected() {\n _console.log(\"gattserverdisconnected\");\n this.status = \"notConnected\";\n }\n\n get canReconnect() {\n return Boolean(this.server && !this.server.connected && this.isInRange);\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.status = \"connecting\";\n try {\n await this.server!.connect();\n } catch (error) {\n _console.error(error);\n this.isInRange = false;\n }\n\n if (this.isConnected) {\n _console.log(\"successfully reconnected!\");\n await this.#getServicesAndCharacteristics();\n this.status = \"connected\";\n } else {\n _console.log(\"unable to reconnect\");\n this.status = \"notConnected\";\n }\n }\n}\n\nexport default WebBluetoothConnectionManager;\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nconst POW_2_24 = 5.960464477539063e-8;\nconst POW_2_32 = 4294967296;\nconst POW_2_53 = 9007199254740992;\n\nexport function encode(value) {\n let data = new ArrayBuffer(256);\n let dataView = new DataView(data);\n let lastLength;\n let offset = 0;\n\n function prepareWrite(length) {\n let newByteLength = data.byteLength;\n const requiredLength = offset + length;\n while (newByteLength < requiredLength) {\n newByteLength <<= 1;\n }\n if (newByteLength !== data.byteLength) {\n const oldDataView = dataView;\n data = new ArrayBuffer(newByteLength);\n dataView = new DataView(data);\n const uint32count = (offset + 3) >> 2;\n for (let i = 0; i < uint32count; ++i) {\n dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));\n }\n }\n\n lastLength = length;\n return dataView;\n }\n function commitWrite() {\n offset += lastLength;\n }\n function writeFloat64(value) {\n commitWrite(prepareWrite(8).setFloat64(offset, value));\n }\n function writeUint8(value) {\n commitWrite(prepareWrite(1).setUint8(offset, value));\n }\n function writeUint8Array(value) {\n const dataView = prepareWrite(value.length);\n for (let i = 0; i < value.length; ++i) {\n dataView.setUint8(offset + i, value[i]);\n }\n commitWrite();\n }\n function writeUint16(value) {\n commitWrite(prepareWrite(2).setUint16(offset, value));\n }\n function writeUint32(value) {\n commitWrite(prepareWrite(4).setUint32(offset, value));\n }\n function writeUint64(value) {\n const low = value % POW_2_32;\n const high = (value - low) / POW_2_32;\n const dataView = prepareWrite(8);\n dataView.setUint32(offset, high);\n dataView.setUint32(offset + 4, low);\n commitWrite();\n }\n function writeTypeAndLength(type, length) {\n if (length < 24) {\n writeUint8((type << 5) | length);\n } else if (length < 0x100) {\n writeUint8((type << 5) | 24);\n writeUint8(length);\n } else if (length < 0x10000) {\n writeUint8((type << 5) | 25);\n writeUint16(length);\n } else if (length < 0x100000000) {\n writeUint8((type << 5) | 26);\n writeUint32(length);\n } else {\n writeUint8((type << 5) | 27);\n writeUint64(length);\n }\n }\n\n function encodeItem(value) {\n let i;\n const utf8data = [];\n let length;\n\n if (value === false) {\n return writeUint8(0xf4);\n }\n if (value === true) {\n return writeUint8(0xf5);\n }\n if (value === null) {\n return writeUint8(0xf6);\n }\n if (value === undefined) {\n return writeUint8(0xf7);\n }\n\n switch (typeof value) {\n case \"number\":\n if (Math.floor(value) === value) {\n if (value >= 0 && value <= POW_2_53) {\n return writeTypeAndLength(0, value);\n }\n if (-POW_2_53 <= value && value < 0) {\n return writeTypeAndLength(1, -(value + 1));\n }\n }\n writeUint8(0xfb);\n return writeFloat64(value);\n\n case \"string\":\n for (i = 0; i < value.length; ++i) {\n let charCode = value.charCodeAt(i);\n if (charCode < 0x80) {\n utf8data.push(charCode);\n } else if (charCode < 0x800) {\n utf8data.push(0xc0 | (charCode >> 6));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else if (charCode < 0xd800) {\n utf8data.push(0xe0 | (charCode >> 12));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else {\n charCode = (charCode & 0x3ff) << 10;\n charCode |= value.charCodeAt(++i) & 0x3ff;\n charCode += 0x10000;\n\n utf8data.push(0xf0 | (charCode >> 18));\n utf8data.push(0x80 | ((charCode >> 12) & 0x3f));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n }\n }\n\n writeTypeAndLength(3, utf8data.length);\n return writeUint8Array(utf8data);\n\n default:\n if (Array.isArray(value)) {\n length = value.length;\n writeTypeAndLength(4, length);\n for (i = 0; i < length; ++i) {\n encodeItem(value[i]);\n }\n } else if (value instanceof Uint8Array) {\n writeTypeAndLength(2, value.length);\n writeUint8Array(value);\n } else {\n const keys = Object.keys(value);\n length = keys.length;\n writeTypeAndLength(5, length);\n for (i = 0; i < length; ++i) {\n const key = keys[i];\n encodeItem(key);\n encodeItem(value[key]);\n }\n }\n }\n }\n\n encodeItem(value);\n\n if (\"slice\" in data) {\n return data.slice(0, offset);\n }\n\n const ret = new ArrayBuffer(offset);\n const retView = new DataView(ret);\n for (let i = 0; i < offset; ++i) {\n retView.setUint8(i, dataView.getUint8(i));\n }\n return ret;\n}\n\nexport function decode(data, tagger, simpleValue) {\n const dataView = new DataView(data);\n let offset = 0;\n\n if (typeof tagger !== \"function\") {\n tagger = function (value) {\n return value;\n };\n }\n if (typeof simpleValue !== \"function\") {\n simpleValue = function () {\n return undefined;\n };\n }\n\n function commitRead(length, value) {\n offset += length;\n return value;\n }\n function readArrayBuffer(length) {\n return commitRead(length, new Uint8Array(data, offset, length));\n }\n function readFloat16() {\n const tempArrayBuffer = new ArrayBuffer(4);\n const tempDataView = new DataView(tempArrayBuffer);\n const value = readUint16();\n\n const sign = value & 0x8000;\n let exponent = value & 0x7c00;\n const fraction = value & 0x03ff;\n\n if (exponent === 0x7c00) {\n exponent = 0xff << 10;\n } else if (exponent !== 0) {\n exponent += (127 - 15) << 10;\n } else if (fraction !== 0) {\n return (sign ? -1 : 1) * fraction * POW_2_24;\n }\n\n tempDataView.setUint32(0, (sign << 16) | (exponent << 13) | (fraction << 13));\n return tempDataView.getFloat32(0);\n }\n function readFloat32() {\n return commitRead(4, dataView.getFloat32(offset));\n }\n function readFloat64() {\n return commitRead(8, dataView.getFloat64(offset));\n }\n function readUint8() {\n return commitRead(1, dataView.getUint8(offset));\n }\n function readUint16() {\n return commitRead(2, dataView.getUint16(offset));\n }\n function readUint32() {\n return commitRead(4, dataView.getUint32(offset));\n }\n function readUint64() {\n return readUint32() * POW_2_32 + readUint32();\n }\n function readBreak() {\n if (dataView.getUint8(offset) !== 0xff) {\n return false;\n }\n offset += 1;\n return true;\n }\n function readLength(additionalInformation) {\n if (additionalInformation < 24) {\n return additionalInformation;\n }\n if (additionalInformation === 24) {\n return readUint8();\n }\n if (additionalInformation === 25) {\n return readUint16();\n }\n if (additionalInformation === 26) {\n return readUint32();\n }\n if (additionalInformation === 27) {\n return readUint64();\n }\n if (additionalInformation === 31) {\n return -1;\n }\n throw new Error(\"Invalid length encoding\");\n }\n function readIndefiniteStringLength(majorType) {\n const initialByte = readUint8();\n if (initialByte === 0xff) {\n return -1;\n }\n const length = readLength(initialByte & 0x1f);\n if (length < 0 || initialByte >> 5 !== majorType) {\n throw new Error(\"Invalid indefinite length element\");\n }\n return length;\n }\n\n function appendUtf16Data(utf16data, length) {\n for (let i = 0; i < length; ++i) {\n let value = readUint8();\n if (value & 0x80) {\n if (value < 0xe0) {\n value = ((value & 0x1f) << 6) | (readUint8() & 0x3f);\n length -= 1;\n } else if (value < 0xf0) {\n value = ((value & 0x0f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 2;\n } else {\n value =\n ((value & 0x0f) << 18) | ((readUint8() & 0x3f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 3;\n }\n }\n\n if (value < 0x10000) {\n utf16data.push(value);\n } else {\n value -= 0x10000;\n utf16data.push(0xd800 | (value >> 10));\n utf16data.push(0xdc00 | (value & 0x3ff));\n }\n }\n }\n\n function decodeItem() {\n const initialByte = readUint8();\n const majorType = initialByte >> 5;\n const additionalInformation = initialByte & 0x1f;\n let i;\n let length;\n\n if (majorType === 7) {\n switch (additionalInformation) {\n case 25:\n return readFloat16();\n case 26:\n return readFloat32();\n case 27:\n return readFloat64();\n }\n }\n\n length = readLength(additionalInformation);\n if (length < 0 && (majorType < 2 || majorType > 6)) {\n throw new Error(\"Invalid length\");\n }\n\n const utf16data = [];\n let retArray;\n const retObject = {};\n\n switch (majorType) {\n case 0:\n return length;\n case 1:\n return -1 - length;\n case 2:\n if (length < 0) {\n const elements = [];\n let fullArrayLength = 0;\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n fullArrayLength += length;\n elements.push(readArrayBuffer(length));\n }\n const fullArray = new Uint8Array(fullArrayLength);\n let fullArrayOffset = 0;\n for (i = 0; i < elements.length; ++i) {\n fullArray.set(elements[i], fullArrayOffset);\n fullArrayOffset += elements[i].length;\n }\n return fullArray;\n }\n return readArrayBuffer(length);\n case 3:\n if (length < 0) {\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n appendUtf16Data(utf16data, length);\n }\n } else {\n appendUtf16Data(utf16data, length);\n }\n return String.fromCharCode.apply(null, utf16data);\n case 4:\n if (length < 0) {\n retArray = [];\n while (!readBreak()) {\n retArray.push(decodeItem());\n }\n } else {\n retArray = new Array(length);\n for (i = 0; i < length; ++i) {\n retArray[i] = decodeItem();\n }\n }\n return retArray;\n case 5:\n for (i = 0; i < length || (length < 0 && !readBreak()); ++i) {\n const key = decodeItem();\n retObject[key] = decodeItem();\n }\n return retObject;\n case 6:\n return tagger(decodeItem(), length);\n case 7:\n switch (length) {\n case 20:\n return false;\n case 21:\n return true;\n case 22:\n return null;\n case 23:\n return undefined;\n default:\n return simpleValue(length);\n }\n }\n }\n\n const ret = decodeItem();\n if (offset !== data.byteLength) {\n throw new Error(\"Remaining bytes\");\n }\n return ret;\n}\n\nexport const CBOR = {\n encode,\n decode,\n};\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2023 Laird Connectivity\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * @file mcumgr\n * @brief Provides MCU manager operation functions for the Xbit USB Shell.\n * This file is inspired by the MIT licensed mcumgr file originally\n * authored by Andras Barthazi (https://github.com/boogie/mcumgr-web),\n * updated to also support file upload/download over SMP.\n */\n\nimport { CBOR } from \"./cbor.js\";\nimport { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"mcumgr\", { log: true });\n\nexport const constants = {\n // Opcodes\n MGMT_OP_READ: 0,\n MGMT_OP_READ_RSP: 1,\n MGMT_OP_WRITE: 2,\n MGMT_OP_WRITE_RSP: 3,\n\n // Groups\n MGMT_GROUP_ID_OS: 0,\n MGMT_GROUP_ID_IMAGE: 1,\n MGMT_GROUP_ID_STAT: 2,\n MGMT_GROUP_ID_CONFIG: 3,\n MGMT_GROUP_ID_LOG: 4,\n MGMT_GROUP_ID_CRASH: 5,\n MGMT_GROUP_ID_SPLIT: 6,\n MGMT_GROUP_ID_RUN: 7,\n MGMT_GROUP_ID_FS: 8,\n MGMT_GROUP_ID_SHELL: 9,\n\n // OS group\n OS_MGMT_ID_ECHO: 0,\n OS_MGMT_ID_CONS_ECHO_CTRL: 1,\n OS_MGMT_ID_TASKSTAT: 2,\n OS_MGMT_ID_MPSTAT: 3,\n OS_MGMT_ID_DATETIME_STR: 4,\n OS_MGMT_ID_RESET: 5,\n\n // Image group\n IMG_MGMT_ID_STATE: 0,\n IMG_MGMT_ID_UPLOAD: 1,\n IMG_MGMT_ID_FILE: 2,\n IMG_MGMT_ID_CORELIST: 3,\n IMG_MGMT_ID_CORELOAD: 4,\n IMG_MGMT_ID_ERASE: 5,\n\n // Filesystem group\n FS_MGMT_ID_FILE: 0,\n};\n\nexport class MCUManager {\n constructor() {\n this._mtu = 256;\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n onMessage(callback) {\n this._messageCallback = callback;\n return this;\n }\n\n onImageUploadNext(callback) {\n this._imageUploadNextCallback = callback;\n return this;\n }\n\n onImageUploadProgress(callback) {\n this._imageUploadProgressCallback = callback;\n return this;\n }\n\n onImageUploadFinished(callback) {\n this._imageUploadFinishedCallback = callback;\n return this;\n }\n\n onFileUploadNext(callback) {\n this._fileUploadNextCallback = callback;\n return this;\n }\n\n onFileUploadProgress(callback) {\n this._fileUploadProgressCallback = callback;\n return this;\n }\n\n onFileUploadFinished(callback) {\n this._fileUploadFinishedCallback = callback;\n return this;\n }\n\n onFileDownloadNext(callback) {\n this._fileDownloadNextCallback = callback;\n return this;\n }\n\n onFileDownloadProgress(callback) {\n this._fileDownloadProgressCallback = callback;\n return this;\n }\n\n onFileDownloadFinished(callback) {\n this._fileDownloadFinishedCallback = callback;\n return this;\n }\n\n _getMessage(op, group, id, data) {\n const _flags = 0;\n let encodedData = [];\n if (typeof data !== \"undefined\") {\n encodedData = [...new Uint8Array(CBOR.encode(data))];\n }\n const lengthLo = encodedData.length & 255;\n const lengthHi = encodedData.length >> 8;\n const groupLo = group & 255;\n const groupHi = group >> 8;\n const message = [op, _flags, lengthHi, lengthLo, groupHi, groupLo, this._seq, id, ...encodedData];\n this._seq = (this._seq + 1) % 256;\n\n return message;\n }\n\n _notification(buffer) {\n _console.log(\"mcumgr - message received\");\n const message = new Uint8Array(buffer);\n this._buffer = new Uint8Array([...this._buffer, ...message]);\n const messageLength = this._buffer[2] * 256 + this._buffer[3];\n if (this._buffer.length < messageLength + 8) return;\n this._processMessage(this._buffer.slice(0, messageLength + 8));\n this._buffer = this._buffer.slice(messageLength + 8);\n }\n\n _processMessage(message) {\n const [op, , lengthHi, lengthLo, groupHi, groupLo, , id] = message;\n const data = CBOR.decode(message.slice(8).buffer);\n const length = lengthHi * 256 + lengthLo;\n const group = groupHi * 256 + groupLo;\n\n _console.log(\"mcumgr - Process Message - Group: \" + group + \", Id: \" + id + \", Off: \" + data.off);\n if (group === constants.MGMT_GROUP_ID_IMAGE && id === constants.IMG_MGMT_ID_UPLOAD && data.off) {\n this._uploadOffset = data.off;\n this._uploadNext();\n return;\n }\n if (\n op === constants.MGMT_OP_WRITE_RSP &&\n group === constants.MGMT_GROUP_ID_FS &&\n id === constants.FS_MGMT_ID_FILE &&\n data.off\n ) {\n this._uploadFileOffset = data.off;\n this._uploadFileNext();\n return;\n }\n if (op === constants.MGMT_OP_READ_RSP && group === constants.MGMT_GROUP_ID_FS && id === constants.FS_MGMT_ID_FILE) {\n this._downloadFileOffset += data.data.length;\n if (data.len != undefined) {\n this._downloadFileLength = data.len;\n }\n _console.log(\"downloaded \" + this._downloadFileOffset + \" bytes of \" + this._downloadFileLength);\n if (this._downloadFileLength > 0) {\n this._fileDownloadProgressCallback({\n percentage: Math.floor((this._downloadFileOffset / this._downloadFileLength) * 100),\n });\n }\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n this._downloadFileNext();\n return;\n }\n\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n }\n\n cmdReset() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_RESET);\n }\n\n smpEcho(message) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_ECHO, {\n d: message,\n });\n }\n\n cmdImageState() {\n return this._getMessage(constants.MGMT_OP_READ, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE);\n }\n\n cmdImageErase() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_ERASE, {});\n }\n\n cmdImageTest(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: false,\n });\n }\n\n cmdImageConfirm(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: true,\n });\n }\n\n _hash(image) {\n return crypto.subtle.digest(\"SHA-256\", image);\n }\n\n async _uploadNext() {\n if (!this._uploadImage) {\n return;\n }\n\n if (this._uploadOffset >= this._uploadImage.byteLength) {\n this._uploadIsInProgress = false;\n this._imageUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadOffset };\n if (this._uploadOffset === 0) {\n message.len = this._uploadImage.byteLength;\n message.sha = new Uint8Array(await this._hash(this._uploadImage));\n }\n this._imageUploadProgressCallback({\n percentage: Math.floor((this._uploadOffset / this._uploadImage.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead - 3 - 5;\n\n message.data = new Uint8Array(this._uploadImage.slice(this._uploadOffset, this._uploadOffset + length));\n\n this._uploadOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_IMAGE,\n constants.IMG_MGMT_ID_UPLOAD,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._imageUploadNextCallback({ packet });\n }\n async reset() {\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n async cmdUpload(image, slot = 0) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n\n this._uploadOffset = 0;\n this._uploadImage = image;\n this._uploadSlot = slot;\n\n this._uploadNext();\n }\n\n async cmdUploadFile(filebuf, destFilename) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n this._uploadFileOffset = 0;\n this._uploadFile = filebuf;\n this._uploadFilename = destFilename;\n\n this._uploadFileNext();\n }\n\n async _uploadFileNext() {\n _console.log(\"uploadFileNext - offset: \" + this._uploadFileOffset + \", length: \" + this._uploadFile.byteLength);\n\n if (this._uploadFileOffset >= this._uploadFile.byteLength) {\n this._uploadIsInProgress = false;\n this._fileUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadFileOffset };\n if (this._uploadFileOffset === 0) {\n message.len = this._uploadFile.byteLength;\n }\n message.name = this._uploadFilename;\n this._fileUploadProgressCallback({\n percentage: Math.floor((this._uploadFileOffset / this._uploadFile.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead;\n\n message.data = new Uint8Array(this._uploadFile.slice(this._uploadFileOffset, this._uploadFileOffset + length));\n\n this._uploadFileOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._fileUploadNextCallback({ packet });\n }\n\n async cmdDownloadFile(filename, destFilename) {\n if (this._downloadIsInProgress) {\n _console.error(\"Download is already in progress.\");\n return;\n }\n this._downloadIsInProgress = true;\n this._downloadFileOffset = 0;\n this._downloadFileLength = 0;\n this._downloadRemoteFilename = filename;\n this._downloadLocalFilename = destFilename;\n\n this._downloadFileNext();\n }\n\n async _downloadFileNext() {\n if (this._downloadFileLength > 0) {\n if (this._downloadFileOffset >= this._downloadFileLength) {\n this._downloadIsInProgress = false;\n this._fileDownloadFinishedCallback();\n return;\n }\n }\n\n const message = { off: this._downloadFileOffset };\n if (this._downloadFileOffset === 0) {\n message.name = this._downloadRemoteFilename;\n }\n\n const packet = this._getMessage(\n constants.MGMT_OP_READ,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n _console.log(\"mcumgr - _downloadNext: Message Length: \" + packet.length);\n this._fileDownloadNextCallback({ packet });\n }\n\n async imageInfo(image) {\n const info = {};\n const view = new Uint8Array(image);\n\n // check header length\n if (view.length < 32) {\n throw new Error(\"Invalid image (too short file)\");\n }\n\n // check MAGIC bytes 0x96f3b83d\n if (view[0] !== 0x3d || view[1] !== 0xb8 || view[2] !== 0xf3 || view[3] !== 0x96) {\n throw new Error(\"Invalid image (wrong magic bytes)\");\n }\n\n // check load address is 0x00000000\n if (view[4] !== 0x00 || view[5] !== 0x00 || view[6] !== 0x00 || view[7] !== 0x00) {\n throw new Error(\"Invalid image (wrong load address)\");\n }\n\n const headerSize = view[8] + view[9] * 2 ** 8;\n\n // check protected TLV area size is 0\n if (view[10] !== 0x00 || view[11] !== 0x00) {\n throw new Error(\"Invalid image (wrong protected TLV area size)\");\n }\n\n const imageSize = view[12] + view[13] * 2 ** 8 + view[14] * 2 ** 16 + view[15] * 2 ** 24;\n info.imageSize = imageSize;\n\n // check image size is correct\n if (view.length < imageSize + headerSize) {\n throw new Error(\"Invalid image (wrong image size)\");\n }\n\n // check flags is 0x00000000\n if (view[16] !== 0x00 || view[17] !== 0x00 || view[18] !== 0x00 || view[19] !== 0x00) {\n throw new Error(\"Invalid image (wrong flags)\");\n }\n\n const version = `${view[20]}.${view[21]}.${view[22] + view[23] * 2 ** 8}`;\n info.version = version;\n\n info.hash = [...new Uint8Array(await this._hash(image.slice(0, imageSize + 32)))]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return info;\n }\n}\n","import Device, { SendSmpMessageCallback } from \"./Device.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { MCUManager, constants } from \"./utils/mcumgr.js\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FirmwareManager\", { log: true });\n\nexport const FirmwareMessageTypes = [\"smp\"] as const;\nexport type FirmwareMessageType = (typeof FirmwareMessageTypes)[number];\n\nexport const FirmwareEventTypes = [\n ...FirmwareMessageTypes,\n \"firmwareImages\",\n \"firmwareUploadProgress\",\n \"firmwareStatus\",\n \"firmwareUploadComplete\",\n] as const;\nexport type FirmwareEventType = (typeof FirmwareEventTypes)[number];\n\nexport const FirmwareStatuses = [\"idle\", \"uploading\", \"uploaded\", \"pending\", \"testing\", \"erasing\"] as const;\nexport type FirmwareStatus = (typeof FirmwareStatuses)[number];\n\nexport interface FirmwareImage {\n slot: number;\n active: boolean;\n confirmed: boolean;\n pending: boolean;\n permanent: boolean;\n bootable: boolean;\n version: string;\n hash?: Uint8Array;\n empty?: boolean;\n}\n\nexport interface FirmwareEventMessages {\n smp: { dataView: DataView };\n firmwareImages: { firmwareImages: FirmwareImage[] };\n firmwareUploadProgress: { progress: number };\n firmwareStatus: { firmwareStatus: FirmwareStatus };\n //firmwareUploadComplete: {};\n}\n\nexport type FirmwareEventDispatcher = EventDispatcher<Device, FirmwareEventType, FirmwareEventMessages>;\n\nclass FirmwareManager {\n sendMessage!: SendSmpMessageCallback;\n\n constructor() {\n this.#assignMcuManagerCallbacks();\n autoBind(this);\n }\n\n eventDispatcher!: FirmwareEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n parseMessage(messageType: FirmwareMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"smp\":\n this.#mcuManager._notification(Array.from(new Uint8Array(dataView.buffer)));\n this.#dispatchEvent(\"smp\", { dataView });\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async uploadFirmware(file: FileLike) {\n _console.log(\"uploadFirmware\", file);\n\n const promise = this.waitForEvent(\"firmwareUploadComplete\");\n\n await this.getImages();\n\n const arrayBuffer = await getFileBuffer(file);\n const imageInfo = await this.#mcuManager.imageInfo(arrayBuffer);\n _console.log({ imageInfo });\n\n this.#mcuManager.cmdUpload(arrayBuffer, 1);\n\n this.#updateStatus(\"uploading\");\n\n await promise;\n }\n\n #status: FirmwareStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #updateStatus(newStatus: FirmwareStatus) {\n _console.assertEnumWithError(newStatus, FirmwareStatuses);\n if (this.#status == newStatus) {\n _console.log(`redundant firmwareStatus assignment \"${newStatus}\"`);\n return;\n }\n\n this.#status = newStatus;\n _console.log({ firmwareStatus: this.#status });\n this.#dispatchEvent(\"firmwareStatus\", { firmwareStatus: this.#status });\n }\n\n // COMMANDS\n\n #images!: FirmwareImage[];\n get images() {\n return this.#images;\n }\n #assertImages() {\n _console.assertWithError(this.#images, \"didn't get imageState\");\n }\n #assertValidImageIndex(imageIndex: number) {\n _console.assertTypeWithError(imageIndex, \"number\");\n _console.assertWithError(imageIndex == 0 || imageIndex == 1, \"imageIndex must be 0 or 1\");\n }\n async getImages() {\n const promise = this.waitForEvent(\"firmwareImages\");\n\n _console.log(\"getting firmware image state...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageState()).buffer);\n\n await promise;\n }\n\n async testImage(imageIndex: number = 1) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (!this.#images[imageIndex]) {\n _console.log(`image ${imageIndex} not found`);\n return;\n }\n if (this.#images[imageIndex].pending == true) {\n _console.log(`image ${imageIndex} is already pending`);\n return;\n }\n if (this.#images[imageIndex].empty) {\n _console.log(`image ${imageIndex} is empty`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"testing firmware image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageTest(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async eraseImage() {\n this.#assertImages();\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"erasing image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageErase()).buffer);\n\n this.#updateStatus(\"erasing\");\n\n await promise;\n await this.getImages();\n }\n\n async confirmImage(imageIndex: number = 0) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (this.#images[imageIndex].confirmed === true) {\n _console.log(`image ${imageIndex} is already confirmed`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"confirming image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageConfirm(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async echo(string: string) {\n _console.assertTypeWithError(string, \"string\");\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"sending echo...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.smpEcho(string)).buffer);\n\n await promise;\n }\n\n async reset() {\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"resetting...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdReset()).buffer);\n\n await promise;\n }\n\n // MTU\n #mtu!: number;\n get mtu() {\n return this.#mtu;\n }\n set mtu(newMtu: number) {\n this.#mtu = newMtu;\n this.#mcuManager._mtu = newMtu;\n }\n\n // MCUManager\n #mcuManager = new MCUManager();\n\n #assignMcuManagerCallbacks() {\n this.#mcuManager.onMessage(this.#onMcuMessage.bind(this));\n\n this.#mcuManager.onFileDownloadNext(this.#onMcuFileDownloadNext);\n this.#mcuManager.onFileDownloadProgress(this.#onMcuFileDownloadProgress.bind(this));\n this.#mcuManager.onFileDownloadFinished(this.#onMcuFileDownloadFinished.bind(this));\n\n this.#mcuManager.onFileUploadNext(this.#onMcuFileUploadNext.bind(this));\n this.#mcuManager.onFileUploadProgress(this.#onMcuFileUploadProgress.bind(this));\n this.#mcuManager.onFileUploadFinished(this.#onMcuFileUploadFinished.bind(this));\n\n this.#mcuManager.onImageUploadNext(this.#onMcuImageUploadNext.bind(this));\n this.#mcuManager.onImageUploadProgress(this.#onMcuImageUploadProgress.bind(this));\n this.#mcuManager.onImageUploadFinished(this.#onMcuImageUploadFinished.bind(this));\n }\n\n #onMcuMessage({ op, group, id, data, length }: { op: number; group: number; id: number; data: any; length: number }) {\n _console.log(\"onMcuMessage\", ...arguments);\n\n switch (group) {\n case constants.MGMT_GROUP_ID_OS:\n switch (id) {\n case constants.OS_MGMT_ID_ECHO:\n _console.log(`echo \"${data.r}\"`);\n break;\n case constants.OS_MGMT_ID_TASKSTAT:\n _console.table(data.tasks);\n break;\n case constants.OS_MGMT_ID_MPSTAT:\n _console.log(data);\n break;\n }\n break;\n case constants.MGMT_GROUP_ID_IMAGE:\n switch (id) {\n case constants.IMG_MGMT_ID_STATE:\n this.#onMcuImageState(data);\n }\n break;\n default:\n throw Error(`uncaught mcuMessage group ${group}`);\n }\n }\n\n #onMcuFileDownloadNext() {\n _console.log(\"onMcuFileDownloadNext\", ...arguments);\n }\n #onMcuFileDownloadProgress() {\n _console.log(\"onMcuFileDownloadProgress\", ...arguments);\n }\n #onMcuFileDownloadFinished() {\n _console.log(\"onMcuFileDownloadFinished\", ...arguments);\n }\n\n #onMcuFileUploadNext() {\n _console.log(\"onMcuFileUploadNext\");\n }\n #onMcuFileUploadProgress() {\n _console.log(\"onMcuFileUploadProgress\");\n }\n #onMcuFileUploadFinished() {\n _console.log(\"onMcuFileUploadFinished\");\n }\n\n #onMcuImageUploadNext({ packet }: { packet: number[] }) {\n _console.log(\"onMcuImageUploadNext\");\n this.sendMessage(Uint8Array.from(packet).buffer);\n }\n #onMcuImageUploadProgress({ percentage }: { percentage: number }) {\n const progress = percentage / 100;\n _console.log(\"onMcuImageUploadProgress\", ...arguments);\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress });\n }\n async #onMcuImageUploadFinished() {\n _console.log(\"onMcuImageUploadFinished\", ...arguments);\n\n await this.getImages();\n\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress: 100 });\n this.#dispatchEvent(\"firmwareUploadComplete\", {});\n }\n\n #onMcuImageState({ images }: { images?: FirmwareImage[] }) {\n if (images) {\n this.#images = images;\n _console.log(\"images\", this.#images);\n } else {\n _console.log(\"no images found\");\n return;\n }\n\n let newStatus: FirmwareStatus = \"idle\";\n\n if (this.#images.length == 2) {\n if (!this.#images[1].bootable) {\n _console.warn('Slot 1 has a invalid image. Click \"Erase Image\" to erase it or upload a different image');\n } else if (!this.#images[0].confirmed) {\n _console.log(\n 'Slot 0 has a valid image. Click \"Confirm Image\" to confirm it or wait and the device will swap images back.'\n );\n newStatus = \"testing\";\n } else {\n if (this.#images[1].pending) {\n _console.log(\"reset to upload to the new firmware image\");\n newStatus = \"pending\";\n } else {\n _console.log(\"Slot 1 has a valid image. run testImage() to test it or upload a different image.\");\n newStatus = \"uploaded\";\n }\n }\n }\n\n if (this.#images.length == 1) {\n this.#images.push({\n slot: 1,\n empty: true,\n version: \"Empty\",\n pending: false,\n confirmed: false,\n bootable: false,\n active: false,\n permanent: false,\n });\n\n _console.log(\"Select a firmware upload image to upload to slot 1.\");\n }\n\n this.#updateStatus(newStatus);\n this.#dispatchEvent(\"firmwareImages\", { firmwareImages: this.#images });\n }\n}\n\nexport default FirmwareManager;\n","import { ConnectionStatus } from \"./connection/BaseConnectionManager.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport Device, { BoundDeviceEventListeners, DeviceEventMap } from \"./Device.ts\";\nimport { DeviceType } from \"./InformationManager.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInBluefy, isInBrowser } from \"./utils/environment.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport { addEventListeners } from \"./utils/EventUtils.ts\";\n\nconst _console = createConsole(\"DeviceManager\", { log: true });\n\nexport interface LocalStorageDeviceInformation {\n type: DeviceType;\n bluetoothId: string;\n}\n\nexport interface LocalStorageConfiguration {\n devices: LocalStorageDeviceInformation[];\n}\n\nexport const DeviceManagerEventTypes = [\n \"deviceConnected\",\n \"deviceDisconnected\",\n \"deviceIsConnected\",\n \"availableDevices\",\n \"connectedDevices\",\n] as const;\nexport type DeviceManagerEventType = (typeof DeviceManagerEventTypes)[number];\n\ninterface DeviceManagerEventMessage {\n device: Device;\n}\nexport interface DeviceManagerEventMessages {\n deviceConnected: DeviceManagerEventMessage;\n deviceDisconnected: DeviceManagerEventMessage;\n deviceIsConnected: DeviceManagerEventMessage;\n availableDevices: { availableDevices: Device[] };\n connectedDevices: { connectedDevices: Device[] };\n}\n\nexport type DeviceManagerEventDispatcher = EventDispatcher<\n DeviceManager,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEventMap = EventMap<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type DeviceManagerEventListenerMap = EventListenerMap<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEvent = Event<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type BoundDeviceManagerEventListeners = BoundEventListeners<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\n\nclass DeviceManager {\n static readonly shared = new DeviceManager();\n\n constructor() {\n if (DeviceManager.shared && this != DeviceManager.shared) {\n throw Error(\"DeviceManager is a singleton - use DeviceManager.shared\");\n }\n\n if (this.CanUseLocalStorage) {\n this.UseLocalStorage = true;\n }\n }\n\n // DEVICE LISTENERS\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n getType: this.#onDeviceType.bind(this),\n isConnected: this.#OnDeviceIsConnected.bind(this),\n };\n /** @private */\n onDevice(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n }\n\n #onDeviceType(event: DeviceEventMap[\"getType\"]) {\n if (this.#UseLocalStorage) {\n this.#UpdateLocalStorageConfigurationForDevice(event.target);\n }\n }\n\n // CONNECTION STATUS\n /** @private */\n OnDeviceConnectionStatusUpdated(device: Device, connectionStatus: ConnectionStatus) {\n if (connectionStatus == \"notConnected\" && !device.canReconnect && this.#AvailableDevices.includes(device)) {\n const deviceIndex = this.#AvailableDevices.indexOf(device);\n this.AvailableDevices.splice(deviceIndex, 1);\n this.#DispatchAvailableDevices();\n }\n }\n\n // CONNECTED DEVICES\n\n #ConnectedDevices: Device[] = [];\n get ConnectedDevices() {\n return this.#ConnectedDevices;\n }\n\n #UseLocalStorage = false;\n get UseLocalStorage() {\n return this.#UseLocalStorage;\n }\n set UseLocalStorage(newUseLocalStorage) {\n this.#AssertLocalStorage();\n _console.assertTypeWithError(newUseLocalStorage, \"boolean\");\n this.#UseLocalStorage = newUseLocalStorage;\n if (this.#UseLocalStorage && !this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n }\n\n #DefaultLocalStorageConfiguration: LocalStorageConfiguration = {\n devices: [],\n };\n #LocalStorageConfiguration?: LocalStorageConfiguration;\n\n get CanUseLocalStorage() {\n return isInBrowser && window.localStorage;\n }\n\n #AssertLocalStorage() {\n _console.assertWithError(isInBrowser, \"localStorage is only available in the browser\");\n _console.assertWithError(window.localStorage, \"localStorage not found\");\n }\n #LocalStorageKey = \"BS.Device\";\n #SaveToLocalStorage() {\n this.#AssertLocalStorage();\n localStorage.setItem(this.#LocalStorageKey, JSON.stringify(this.#LocalStorageConfiguration));\n }\n async #LoadFromLocalStorage() {\n this.#AssertLocalStorage();\n let localStorageString = localStorage.getItem(this.#LocalStorageKey);\n if (typeof localStorageString != \"string\") {\n _console.log(\"no info found in localStorage\");\n this.#LocalStorageConfiguration = Object.assign({}, this.#DefaultLocalStorageConfiguration);\n this.#SaveToLocalStorage();\n return;\n }\n try {\n const configuration = JSON.parse(localStorageString);\n _console.log({ configuration });\n this.#LocalStorageConfiguration = configuration;\n if (this.CanGetDevices) {\n await this.GetDevices(); // redundant?\n }\n } catch (error) {\n _console.error(error);\n }\n }\n\n #UpdateLocalStorageConfigurationForDevice(device: Device) {\n if (device.connectionType != \"webBluetooth\") {\n _console.log(\"localStorage is only for webBluetooth devices\");\n return;\n }\n this.#AssertLocalStorage();\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex((deviceInformation) => {\n return deviceInformation.bluetoothId == device.bluetoothId;\n });\n if (deviceInformationIndex == -1) {\n return;\n }\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex].type = device.type;\n this.#SaveToLocalStorage();\n }\n\n // AVAILABLE DEVICES\n #AvailableDevices: Device[] = [];\n get AvailableDevices() {\n return this.#AvailableDevices;\n }\n\n get CanGetDevices() {\n return isInBrowser && navigator.bluetooth?.getDevices;\n }\n /**\n * retrieves devices already connected via web bluetooth in other tabs/windows\n *\n * _only available on web-bluetooth enabled browsers_\n */\n async GetDevices(): Promise<Device[] | undefined> {\n if (!isInBrowser) {\n _console.warn(\"GetDevices is only available in the browser\");\n return;\n }\n\n if (!navigator.bluetooth) {\n _console.warn(\"bluetooth is not available in this browser\");\n return;\n }\n\n if (isInBluefy) {\n _console.warn(\"bluefy lists too many devices...\");\n return;\n }\n\n if (!navigator.bluetooth.getDevices) {\n _console.warn(\"bluetooth.getDevices() is not available in this browser\");\n return;\n }\n\n if (!this.CanGetDevices) {\n _console.log(\"CanGetDevices is false\");\n return;\n }\n\n if (!this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n\n const configuration = this.#LocalStorageConfiguration!;\n if (!configuration.devices || configuration.devices.length == 0) {\n _console.log(\"no devices found in configuration\");\n return;\n }\n\n const bluetoothDevices = await navigator.bluetooth.getDevices();\n\n _console.log({ bluetoothDevices });\n\n bluetoothDevices.forEach((bluetoothDevice) => {\n if (!bluetoothDevice.gatt) {\n return;\n }\n let deviceInformation = configuration.devices.find(\n (deviceInformation) => bluetoothDevice.id == deviceInformation.bluetoothId\n );\n if (!deviceInformation) {\n return;\n }\n\n let existingConnectedDevice = this.ConnectedDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n\n const existingAvailableDevice = this.AvailableDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n if (existingAvailableDevice) {\n if (\n existingConnectedDevice &&\n existingConnectedDevice?.bluetoothId == existingAvailableDevice.bluetoothId &&\n existingConnectedDevice != existingAvailableDevice\n ) {\n this.AvailableDevices[this.#AvailableDevices.indexOf(existingAvailableDevice)] = existingConnectedDevice;\n }\n return;\n }\n\n if (existingConnectedDevice) {\n this.AvailableDevices.push(existingConnectedDevice);\n return;\n }\n\n const device = new Device();\n const connectionManager = new WebBluetoothConnectionManager();\n connectionManager.device = bluetoothDevice;\n if (bluetoothDevice.name) {\n device._informationManager.updateName(bluetoothDevice.name);\n }\n device._informationManager.updateType(deviceInformation.type);\n device.connectionManager = connectionManager;\n this.AvailableDevices.push(device);\n });\n this.#DispatchAvailableDevices();\n return this.AvailableDevices;\n }\n\n // STATIC EVENTLISTENERS\n\n #EventDispatcher: DeviceManagerEventDispatcher = new EventDispatcher(this as DeviceManager, DeviceManagerEventTypes);\n\n get AddEventListener() {\n return this.#EventDispatcher.addEventListener;\n }\n get #DispatchEvent() {\n return this.#EventDispatcher.dispatchEvent;\n }\n get RemoveEventListener() {\n return this.#EventDispatcher.removeEventListener;\n }\n get RemoveEventListeners() {\n return this.#EventDispatcher.removeEventListeners;\n }\n get RemoveAllEventListeners() {\n return this.#EventDispatcher.removeAllEventListeners;\n }\n\n #OnDeviceIsConnected(event: DeviceEventMap[\"isConnected\"]) {\n const { target: device } = event;\n if (device.isConnected) {\n if (!this.#ConnectedDevices.includes(device)) {\n _console.log(\"adding device\", device);\n this.#ConnectedDevices.push(device);\n if (this.UseLocalStorage && device.connectionType == \"webBluetooth\") {\n const deviceInformation: LocalStorageDeviceInformation = {\n type: device.type,\n bluetoothId: device.bluetoothId!,\n };\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex(\n (_deviceInformation) => _deviceInformation.bluetoothId == deviceInformation.bluetoothId\n );\n if (deviceInformationIndex == -1) {\n this.#LocalStorageConfiguration!.devices.push(deviceInformation);\n } else {\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex] = deviceInformation;\n }\n this.#SaveToLocalStorage();\n }\n this.#DispatchEvent(\"deviceConnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already included\");\n }\n } else {\n if (this.#ConnectedDevices.includes(device)) {\n _console.log(\"removing device\", device);\n this.#ConnectedDevices.splice(this.#ConnectedDevices.indexOf(device), 1);\n this.#DispatchEvent(\"deviceDisconnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already not included\");\n }\n }\n if (this.CanGetDevices) {\n this.GetDevices();\n }\n if (device.isConnected && !this.AvailableDevices.includes(device)) {\n const existingAvailableDevice = this.AvailableDevices.find(\n (_device) => _device.bluetoothId == device.bluetoothId\n );\n _console.log({ existingAvailableDevice });\n if (existingAvailableDevice) {\n this.AvailableDevices[this.AvailableDevices.indexOf(existingAvailableDevice)] = device;\n } else {\n this.AvailableDevices.push(device);\n }\n this.#DispatchAvailableDevices();\n }\n }\n\n #DispatchAvailableDevices() {\n _console.log({ AvailableDevices: this.AvailableDevices });\n this.#DispatchEvent(\"availableDevices\", { availableDevices: this.AvailableDevices });\n }\n #DispatchConnectedDevices() {\n _console.log({ ConnectedDevices: this.ConnectedDevices });\n this.#DispatchEvent(\"connectedDevices\", { connectedDevices: this.ConnectedDevices });\n }\n}\n\nexport default DeviceManager.shared;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport BaseConnectionManager, {\n TxMessage,\n TxRxMessageType,\n ConnectionStatus,\n ConnectionMessageType,\n MetaConnectionMessageTypes,\n BatteryLevelMessageTypes,\n ConnectionEventTypes,\n ConnectionStatusEventMessages,\n} from \"./connection/BaseConnectionManager.ts\";\nimport { isInBrowser, isInNode } from \"./utils/environment.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport SensorConfigurationManager, {\n SendSensorConfigurationMessageCallback,\n SensorConfiguration,\n SensorConfigurationEventDispatcher,\n SensorConfigurationEventMessages,\n SensorConfigurationEventTypes,\n SensorConfigurationMessageType,\n SensorConfigurationMessageTypes,\n} from \"./sensor/SensorConfigurationManager.ts\";\nimport SensorDataManager, {\n SensorDataEventMessages,\n SensorDataEventTypes,\n SensorDataMessageType,\n SensorDataMessageTypes,\n SensorType,\n ContinuousSensorTypes,\n SensorDataEventDispatcher,\n} from \"./sensor/SensorDataManager.ts\";\nimport VibrationManager, {\n SendVibrationMessageCallback,\n VibrationConfiguration,\n} from \"./vibration/VibrationManager.ts\";\nimport FileTransferManager, {\n FileTransferEventTypes,\n FileTransferEventMessages,\n FileTransferEventDispatcher,\n SendFileTransferMessageCallback,\n FileTransferMessageTypes,\n FileTransferMessageType,\n FileType,\n} from \"./FileTransferManager.ts\";\nimport TfliteManager, {\n TfliteEventTypes,\n TfliteEventMessages,\n TfliteEventDispatcher,\n SendTfliteMessageCallback,\n TfliteMessageTypes,\n TfliteMessageType,\n TfliteSensorTypes,\n} from \"./TfliteManager.ts\";\nimport FirmwareManager, {\n FirmwareEventDispatcher,\n FirmwareEventMessages,\n FirmwareEventTypes,\n FirmwareMessageType,\n FirmwareMessageTypes,\n} from \"./FirmwareManager.ts\";\nimport DeviceInformationManager, {\n DeviceInformationEventDispatcher,\n DeviceInformationEventTypes,\n DeviceInformationMessageType,\n DeviceInformationMessageTypes,\n DeviceInformationEventMessages,\n} from \"./DeviceInformationManager.ts\";\nimport InformationManager, {\n DeviceType,\n InformationEventDispatcher,\n InformationEventTypes,\n InformationMessageType,\n InformationMessageTypes,\n InformationEventMessages,\n SendInformationMessageCallback,\n} from \"./InformationManager.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport DeviceManager from \"./DeviceManager.ts\";\n\nconst _console = createConsole(\"Device\", { log: true });\n\nexport const DeviceEventTypes = [\n \"connectionMessage\",\n ...ConnectionEventTypes,\n ...MetaConnectionMessageTypes,\n ...BatteryLevelMessageTypes,\n ...InformationEventTypes,\n ...DeviceInformationEventTypes,\n ...SensorConfigurationEventTypes,\n ...SensorDataEventTypes,\n ...FileTransferEventTypes,\n ...TfliteEventTypes,\n ...FirmwareEventTypes,\n] as const;\nexport type DeviceEventType = (typeof DeviceEventTypes)[number];\n\nexport interface DeviceEventMessages\n extends ConnectionStatusEventMessages,\n DeviceInformationEventMessages,\n InformationEventMessages,\n SensorDataEventMessages,\n SensorConfigurationEventMessages,\n TfliteEventMessages,\n FileTransferEventMessages,\n FirmwareEventMessages {\n batteryLevel: { batteryLevel: number };\n connectionMessage: { messageType: ConnectionMessageType; dataView: DataView };\n}\n\nexport type SendMessageCallback<MessageType extends string> = (\n messages?: { type: MessageType; data?: ArrayBuffer }[],\n sendImmediately?: boolean\n) => Promise<void>;\n\nexport type SendSmpMessageCallback = (data: ArrayBuffer) => Promise<void>;\n\nexport type DeviceEventDispatcher = EventDispatcher<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEvent = Event<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventMap = EventMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventListenerMap = EventListenerMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type BoundDeviceEventListeners = BoundEventListeners<Device, DeviceEventType, DeviceEventMessages>;\n\nexport const RequiredInformationConnectionMessages: TxRxMessageType[] = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getId\",\n \"getMtu\",\n\n \"getName\",\n \"getType\",\n \"getCurrentTime\",\n \"getSensorConfiguration\",\n \"getSensorScalars\",\n \"getPressurePositions\",\n\n \"maxFileLength\",\n \"getFileLength\",\n \"getFileChecksum\",\n \"getFileType\",\n \"fileTransferStatus\",\n\n \"getTfliteName\",\n \"getTfliteTask\",\n \"getTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n];\n\nclass Device {\n get bluetoothId() {\n return this.#connectionManager?.bluetoothId;\n }\n\n constructor() {\n this.#deviceInformationManager.eventDispatcher = this.#eventDispatcher as DeviceInformationEventDispatcher;\n\n this._informationManager.sendMessage = this.sendTxMessages as SendInformationMessageCallback;\n this._informationManager.eventDispatcher = this.#eventDispatcher as InformationEventDispatcher;\n\n this.#sensorConfigurationManager.sendMessage = this.sendTxMessages as SendSensorConfigurationMessageCallback;\n this.#sensorConfigurationManager.eventDispatcher = this.#eventDispatcher as SensorConfigurationEventDispatcher;\n\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as SensorDataEventDispatcher;\n\n this.#vibrationManager.sendMessage = this.sendTxMessages as SendVibrationMessageCallback;\n\n this.#tfliteManager.sendMessage = this.sendTxMessages as SendTfliteMessageCallback;\n this.#tfliteManager.eventDispatcher = this.#eventDispatcher as TfliteEventDispatcher;\n\n this.#fileTransferManager.sendMessage = this.sendTxMessages as SendFileTransferMessageCallback;\n this.#fileTransferManager.eventDispatcher = this.#eventDispatcher as FileTransferEventDispatcher;\n\n this.#firmwareManager.sendMessage = this.sendSmpMessage as SendSmpMessageCallback;\n this.#firmwareManager.eventDispatcher = this.#eventDispatcher as FirmwareEventDispatcher;\n\n this.addEventListener(\"getMtu\", () => {\n this.#firmwareManager.mtu = this.mtu;\n this.#fileTransferManager.mtu = this.mtu;\n this.connectionManager!.mtu = this.mtu;\n });\n DeviceManager.onDevice(this);\n if (isInBrowser) {\n window.addEventListener(\"beforeunload\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n if (isInNode) {\n /** can add more node leave handlers https://gist.github.com/hyrious/30a878f6e6a057f09db87638567cb11a */\n process.on(\"exit\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n }\n\n static #DefaultConnectionManager(): BaseConnectionManager {\n return new WebBluetoothConnectionManager();\n }\n\n #eventDispatcher: DeviceEventDispatcher = new EventDispatcher(this as Device, DeviceEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // CONNECTION MANAGER\n\n #connectionManager?: BaseConnectionManager;\n get connectionManager() {\n return this.#connectionManager;\n }\n set connectionManager(newConnectionManager) {\n if (this.connectionManager == newConnectionManager) {\n _console.log(\"same connectionManager is already assigned\");\n return;\n }\n\n if (this.connectionManager) {\n this.connectionManager.onStatusUpdated = undefined;\n this.connectionManager.onMessageReceived = undefined;\n this.connectionManager.onMessagesReceived = undefined;\n }\n if (newConnectionManager) {\n newConnectionManager.onStatusUpdated = this.#onConnectionStatusUpdated.bind(this);\n newConnectionManager.onMessageReceived = this.#onConnectionMessageReceived.bind(this);\n newConnectionManager.onMessagesReceived = this.#onConnectionMessagesReceived.bind(this);\n }\n\n this.#connectionManager = newConnectionManager;\n _console.log(\"assigned new connectionManager\", this.#connectionManager);\n }\n async #sendTxMessages(messages?: TxMessage[], sendImmediately?: boolean) {\n await this.#connectionManager?.sendTxMessages(messages, sendImmediately);\n }\n private sendTxMessages = this.#sendTxMessages.bind(this);\n\n async connect() {\n if (!this.connectionManager) {\n this.connectionManager = Device.#DefaultConnectionManager();\n }\n this.#clear();\n return this.connectionManager.connect();\n }\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n get #hasRequiredInformation() {\n return RequiredInformationConnectionMessages.every((messageType) => {\n return this.latestConnectionMessage.has(messageType);\n });\n }\n #requestRequiredInformation() {\n const messages: TxMessage[] = RequiredInformationConnectionMessages.map((messageType) => ({\n type: messageType,\n }));\n this.#sendTxMessages(messages);\n }\n\n get canReconnect() {\n return this.connectionManager?.canReconnect;\n }\n #assertCanReconnect() {\n _console.assertWithError(this.canReconnect, \"cannot reconnect to device\");\n }\n async reconnect() {\n this.#assertCanReconnect();\n this.#clear();\n return this.connectionManager?.reconnect();\n }\n\n static async Connect() {\n const device = new Device();\n await device.connect();\n return device;\n }\n\n static #ReconnectOnDisconnection = false;\n static get ReconnectOnDisconnection() {\n return this.#ReconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#ReconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n #reconnectOnDisconnection = Device.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this.#reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n #reconnectIntervalId?: NodeJS.Timeout | number;\n\n get connectionType() {\n return this.connectionManager?.type;\n }\n async disconnect() {\n this.#assertIsConnected();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.addEventListener(\n \"isConnected\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n\n return this.connectionManager!.disconnect();\n }\n\n toggleConnection() {\n if (this.isConnected) {\n this.disconnect();\n } else if (this.canReconnect) {\n this.reconnect();\n } else {\n this.connect();\n }\n }\n\n get connectionStatus(): ConnectionStatus {\n switch (this.#connectionManager?.status) {\n case \"connected\":\n return this.isConnected ? \"connected\" : \"connecting\";\n case \"notConnected\":\n case \"connecting\":\n case \"disconnecting\":\n return this.#connectionManager.status;\n default:\n return \"notConnected\";\n }\n }\n get isConnectionBusy() {\n return this.connectionStatus == \"connecting\" || this.connectionStatus == \"disconnecting\";\n }\n\n #onConnectionStatusUpdated(connectionStatus: ConnectionStatus) {\n _console.log({ connectionStatus });\n\n if (connectionStatus == \"notConnected\") {\n //this.#clear();\n\n if (this.canReconnect && this.reconnectOnDisconnection) {\n _console.log(\"starting reconnect interval...\");\n this.#reconnectIntervalId = setInterval(() => {\n _console.log(\"attempting reconnect...\");\n this.reconnect();\n }, 1000);\n }\n } else {\n if (this.#reconnectIntervalId != undefined) {\n _console.log(\"clearing reconnect interval\");\n clearInterval(this.#reconnectIntervalId);\n this.#reconnectIntervalId = undefined;\n }\n }\n\n this.#checkConnection();\n\n if (connectionStatus == \"connected\" && !this.#isConnected) {\n this.#requestRequiredInformation();\n }\n\n DeviceManager.OnDeviceConnectionStatusUpdated(this, connectionStatus);\n }\n\n #dispatchConnectionEvents(includeIsConnected: boolean = false) {\n this.#dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.#dispatchEvent(this.connectionStatus, {});\n if (includeIsConnected) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n }\n #checkConnection() {\n this.#isConnected =\n Boolean(this.connectionManager?.isConnected) &&\n this.#hasRequiredInformation &&\n this._informationManager.isCurrentTimeSet;\n\n switch (this.connectionStatus) {\n case \"connected\":\n if (this.#isConnected) {\n this.#dispatchConnectionEvents(true);\n }\n break;\n case \"notConnected\":\n this.#dispatchConnectionEvents(true);\n break;\n default:\n this.#dispatchConnectionEvents(false);\n break;\n }\n }\n\n #clear() {\n this.connectionManager?.clear();\n this.latestConnectionMessage.clear();\n this._informationManager.clear();\n this.#deviceInformationManager.clear();\n }\n\n #onConnectionMessageReceived(messageType: ConnectionMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n switch (messageType) {\n case \"batteryLevel\":\n const batteryLevel = dataView.getUint8(0);\n _console.log(\"received battery level\", { batteryLevel });\n this.#updateBatteryLevel(batteryLevel);\n break;\n\n default:\n if (FileTransferMessageTypes.includes(messageType as FileTransferMessageType)) {\n this.#fileTransferManager.parseMessage(messageType as FileTransferMessageType, dataView);\n } else if (TfliteMessageTypes.includes(messageType as TfliteMessageType)) {\n this.#tfliteManager.parseMessage(messageType as TfliteMessageType, dataView);\n } else if (SensorDataMessageTypes.includes(messageType as SensorDataMessageType)) {\n this.#sensorDataManager.parseMessage(messageType as SensorDataMessageType, dataView);\n } else if (FirmwareMessageTypes.includes(messageType as FirmwareMessageType)) {\n this.#firmwareManager.parseMessage(messageType as FirmwareMessageType, dataView);\n } else if (DeviceInformationMessageTypes.includes(messageType as DeviceInformationMessageType)) {\n this.#deviceInformationManager.parseMessage(messageType as DeviceInformationMessageType, dataView);\n } else if (InformationMessageTypes.includes(messageType as InformationMessageType)) {\n this._informationManager.parseMessage(messageType as InformationMessageType, dataView);\n } else if (SensorConfigurationMessageTypes.includes(messageType as SensorConfigurationMessageType)) {\n this.#sensorConfigurationManager.parseMessage(messageType as SensorConfigurationMessageType, dataView);\n } else {\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n this.latestConnectionMessage.set(messageType, dataView);\n this.#dispatchEvent(\"connectionMessage\", { messageType, dataView });\n }\n #onConnectionMessagesReceived() {\n if (!this.isConnected && this.#hasRequiredInformation) {\n this.#checkConnection();\n }\n if (this.connectionStatus == \"notConnected\") {\n return;\n }\n this.#sendTxMessages();\n }\n\n latestConnectionMessage: Map<ConnectionMessageType, DataView> = new Map();\n\n // DEVICE INFORMATION\n #deviceInformationManager = new DeviceInformationManager();\n get deviceInformation() {\n return this.#deviceInformationManager.information;\n }\n\n // BATTERY LEVEL\n #batteryLevel = 0;\n get batteryLevel() {\n return this.#batteryLevel;\n }\n #updateBatteryLevel(updatedBatteryLevel: number) {\n _console.assertTypeWithError(updatedBatteryLevel, \"number\");\n if (this.#batteryLevel == updatedBatteryLevel) {\n _console.log(`duplicate batteryLevel assignment ${updatedBatteryLevel}`);\n return;\n }\n this.#batteryLevel = updatedBatteryLevel;\n _console.log({ updatedBatteryLevel: this.#batteryLevel });\n this.#dispatchEvent(\"batteryLevel\", { batteryLevel: this.#batteryLevel });\n }\n\n // INFORMATION\n /** @private */\n _informationManager = new InformationManager();\n\n get id() {\n return this._informationManager.id;\n }\n\n get isCharging() {\n return this._informationManager.isCharging;\n }\n get batteryCurrent() {\n return this._informationManager.batteryCurrent;\n }\n get getBatteryCurrent() {\n return this._informationManager.getBatteryCurrent;\n }\n\n get name() {\n return this._informationManager.name;\n }\n get setName() {\n return this._informationManager.setName;\n }\n\n get type() {\n return this._informationManager.type;\n }\n get setType() {\n return this._informationManager.setType;\n }\n\n get isInsole() {\n return this._informationManager.isInsole;\n }\n get insoleSide() {\n return this._informationManager.insoleSide;\n }\n\n get mtu() {\n return this._informationManager.mtu;\n }\n\n // SENSOR TYPES\n get sensorTypes() {\n return Object.keys(this.sensorConfiguration) as SensorType[];\n }\n get continuousSensorTypes() {\n return ContinuousSensorTypes.filter((sensorType) => this.sensorTypes.includes(sensorType));\n }\n\n // SENSOR CONFIGURATION\n\n #sensorConfigurationManager = new SensorConfigurationManager();\n\n get sensorConfiguration() {\n return this.#sensorConfigurationManager.configuration;\n }\n\n async setSensorConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n await this.#sensorConfigurationManager.setConfiguration(newSensorConfiguration, clearRest);\n }\n\n async clearSensorConfiguration() {\n return this.#sensorConfigurationManager.clearSensorConfiguration();\n }\n\n static #ClearSensorConfigurationOnLeave = true;\n static get ClearSensorConfigurationOnLeave() {\n return this.#ClearSensorConfigurationOnLeave;\n }\n static set ClearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#ClearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n #clearSensorConfigurationOnLeave = Device.ClearSensorConfigurationOnLeave;\n get clearSensorConfigurationOnLeave() {\n return this.#clearSensorConfigurationOnLeave;\n }\n set clearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#clearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n // PRESSURE\n get numberOfPressureSensors() {\n return this.#sensorDataManager.pressureSensorDataManager.numberOfSensors;\n }\n\n // SENSOR DATA\n #sensorDataManager = new SensorDataManager();\n resetPressureRange() {\n this.#sensorDataManager.pressureSensorDataManager.resetRange();\n }\n\n // VIBRATION\n #vibrationManager = new VibrationManager();\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n this.#vibrationManager.triggerVibration(vibrationConfigurations, sendImmediately);\n }\n\n // FILE TRANSFER\n #fileTransferManager = new FileTransferManager();\n\n get maxFileLength() {\n return this.#fileTransferManager.maxLength;\n }\n\n async sendFile(fileType: FileType, file: FileLike) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.send(fileType, file);\n await promise;\n }\n async receiveFile(fileType: FileType) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.receive(fileType);\n await promise;\n }\n\n get fileTransferStatus() {\n return this.#fileTransferManager.status;\n }\n\n cancelFileTransfer() {\n this.#fileTransferManager.cancel();\n }\n\n // TFLITE\n #tfliteManager = new TfliteManager();\n\n get tfliteName() {\n return this.#tfliteManager.name;\n }\n get setTfliteName() {\n return this.#tfliteManager.setName;\n }\n\n // TFLITE MODEL CONFIG\n get tfliteTask() {\n return this.#tfliteManager.task;\n }\n get setTfliteTask() {\n return this.#tfliteManager.setTask;\n }\n get tfliteSampleRate() {\n return this.#tfliteManager.sampleRate;\n }\n get setTfliteSampleRate() {\n return this.#tfliteManager.setSampleRate;\n }\n get tfliteSensorTypes() {\n return this.#tfliteManager.sensorTypes;\n }\n get allowedTfliteSensorTypes() {\n return this.sensorTypes.filter((sensorType) => TfliteSensorTypes.includes(sensorType));\n }\n get setTfliteSensorTypes() {\n return this.#tfliteManager.setSensorTypes;\n }\n get tfliteIsReady() {\n return this.#tfliteManager.isReady;\n }\n\n // TFLITE INFERENCING\n\n get tfliteInferencingEnabled() {\n return this.#tfliteManager.inferencingEnabled;\n }\n get setTfliteInferencingEnabled() {\n return this.#tfliteManager.setInferencingEnabled;\n }\n async enableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(true);\n }\n async disableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(false);\n }\n get toggleTfliteInferencing() {\n return this.#tfliteManager.toggleInferencingEnabled;\n }\n\n // TFLITE INFERENCE CONFIG\n\n get tfliteCaptureDelay() {\n return this.#tfliteManager.captureDelay;\n }\n get setTfliteCaptureDelay() {\n return this.#tfliteManager.setCaptureDelay;\n }\n get tfliteThreshold() {\n return this.#tfliteManager.threshold;\n }\n get setTfliteThreshold() {\n return this.#tfliteManager.setThreshold;\n }\n\n // FIRMWARE MANAGER\n\n #firmwareManager = new FirmwareManager();\n\n #sendSmpMessage(data: ArrayBuffer) {\n return this.#connectionManager!.sendSmpMessage(data);\n }\n private sendSmpMessage = this.#sendSmpMessage.bind(this);\n\n get uploadFirmware() {\n return this.#firmwareManager.uploadFirmware;\n }\n async reset() {\n await this.#firmwareManager.reset();\n return this.#connectionManager!.disconnect();\n }\n get firmwareStatus() {\n return this.#firmwareManager.status;\n }\n get getFirmwareImages() {\n return this.#firmwareManager.getImages;\n }\n get firmwareImages() {\n return this.#firmwareManager.images;\n }\n get eraseFirmwareImage() {\n return this.#firmwareManager.eraseImage;\n }\n get confirmFirmwareImage() {\n return this.#firmwareManager.confirmImage;\n }\n get testFirmwareImage() {\n return this.#firmwareManager.testImage;\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n\n this.#fileTransferManager.isServerSide = this.isServerSide;\n }\n}\n\nexport default Device;\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport { PressureData } from \"../sensor/PressureSensorDataManager.ts\";\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { DeviceEventMap } from \"../Device.ts\";\n\nconst _console = createConsole(\"DevicePairPressureSensorDataManager\", { log: true });\n\nexport type DevicePairRawPressureData = { [insoleSide in InsoleSide]: PressureData };\n\nexport interface DevicePairPressureData {\n rawSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface DevicePairPressureDataEventMessage {\n pressure: DevicePairPressureData;\n}\n\nexport interface DevicePairPressureDataEventMessages {\n pressure: DevicePairPressureDataEventMessage;\n}\n\nclass DevicePairPressureSensorDataManager {\n #rawPressure: Partial<DevicePairRawPressureData> = {};\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetPressureRange() {\n this.#centerOfPressureHelper.reset();\n }\n\n onDevicePressureData(event: DeviceEventMap[\"pressure\"]) {\n const { pressure } = event.message;\n const insoleSide = event.target.insoleSide;\n _console.log({ pressure, insoleSide });\n this.#rawPressure[insoleSide] = pressure;\n if (this.#hasAllPressureData) {\n return this.#updatePressureData();\n } else {\n _console.log(\"doesn't have all pressure data yet...\");\n }\n }\n\n get #hasAllPressureData() {\n return InsoleSides.every((side) => side in this.#rawPressure);\n }\n\n #updatePressureData() {\n const pressure: DevicePairPressureData = { rawSum: 0, normalizedSum: 0 };\n\n InsoleSides.forEach((side) => {\n pressure.rawSum += this.#rawPressure[side]!.scaledSum;\n pressure.normalizedSum += this.#rawPressure[side]!.normalizedSum;\n });\n\n if (pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n InsoleSides.forEach((side) => {\n const sidePressure = this.#rawPressure[side]!;\n const normalizedPressureSumWeight = sidePressure.normalizedSum / pressure.normalizedSum;\n if (normalizedPressureSumWeight > 0) {\n if (sidePressure.normalizedCenter?.y != undefined) {\n pressure.center!.y += sidePressure.normalizedCenter.y * normalizedPressureSumWeight;\n }\n if (side == \"right\") {\n pressure.center!.x = normalizedPressureSumWeight;\n }\n }\n });\n\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ devicePairPressure: pressure });\n\n return pressure;\n }\n}\n\nexport default DevicePairPressureSensorDataManager;\n","import DevicePairPressureSensorDataManager, {\n DevicePairPressureDataEventMessages,\n} from \"./DevicePairPressureSensorDataManager.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { InsoleSide } from \"../InformationManager.ts\";\nimport { SensorType } from \"../sensor/SensorDataManager.ts\";\nimport { DeviceEventMap, SpecificDeviceEvent } from \"../Device.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport DevicePair from \"./DevicePair.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"DevicePairSensorDataManager\", { log: true });\n\nexport const DevicePairSensorTypes = [\"pressure\", \"sensorData\"] as const;\nexport type DevicePairSensorType = (typeof DevicePairSensorTypes)[number];\n\nexport const DevicePairSensorDataEventTypes = DevicePairSensorTypes;\nexport type DevicePairSensorDataEventType = (typeof DevicePairSensorDataEventTypes)[number];\n\nexport type DevicePairSensorDataTimestamps = { [insoleSide in InsoleSide]: number };\n\ninterface BaseDevicePairSensorDataEventMessage {\n timestamps: DevicePairSensorDataTimestamps;\n}\n\ntype BaseDevicePairSensorDataEventMessages = DevicePairPressureDataEventMessages;\ntype _DevicePairSensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseDevicePairSensorDataEventMessages, \"sensorType\">,\n BaseDevicePairSensorDataEventMessage\n>;\n\nexport type DevicePairSensorDataEventMessage = ValueOf<_DevicePairSensorDataEventMessages>;\ninterface AnyDevicePairSensorDataEventMessages {\n sensorData: DevicePairSensorDataEventMessage;\n}\nexport type DevicePairSensorDataEventMessages = _DevicePairSensorDataEventMessages &\n AnyDevicePairSensorDataEventMessages;\n\nexport type DevicePairSensorDataEventDispatcher = EventDispatcher<\n DevicePair,\n DevicePairSensorDataEventType,\n DevicePairSensorDataEventMessages\n>;\n\nclass DevicePairSensorDataManager {\n eventDispatcher!: DevicePairSensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #timestamps: { [sensorType in SensorType]?: Partial<DevicePairSensorDataTimestamps> } = {};\n\n pressureSensorDataManager = new DevicePairPressureSensorDataManager();\n resetPressureRange() {\n this.pressureSensorDataManager.resetPressureRange();\n }\n\n onDeviceSensorData(event: DeviceEventMap[\"sensorData\"]) {\n const { timestamp, sensorType } = event.message;\n\n _console.log({ sensorType, timestamp, event });\n\n if (!this.#timestamps[sensorType]) {\n this.#timestamps[sensorType] = {};\n }\n this.#timestamps[sensorType]![event.target.insoleSide] = timestamp;\n\n let value;\n switch (sensorType) {\n case \"pressure\":\n value = this.pressureSensorDataManager.onDevicePressureData(event as unknown as DeviceEventMap[\"pressure\"]);\n break;\n default:\n _console.log(`uncaught sensorType \"${sensorType}\"`);\n break;\n }\n\n if (value) {\n const timestamps = Object.assign({}, this.#timestamps[sensorType]) as DevicePairSensorDataTimestamps;\n // @ts-expect-error\n this.dispatchEvent(sensorType as DevicePairSensorDataEventType, { sensorType, timestamps, [sensorType]: value });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, timestamps, [sensorType]: value });\n } else {\n _console.log(\"no value received\");\n }\n }\n}\n\nexport default DevicePairSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"../utils/EventDispatcher.ts\";\nimport { addEventListeners, removeEventListeners } from \"../utils/EventUtils.ts\";\nimport Device, {\n DeviceEvent,\n DeviceEventType,\n DeviceEventMessages,\n DeviceEventTypes,\n BoundDeviceEventListeners,\n DeviceEventMap,\n} from \"../Device.ts\";\nimport DevicePairSensorDataManager, { DevicePairSensorDataEventDispatcher } from \"./DevicePairSensorDataManager.ts\";\nimport { capitalizeFirstCharacter } from \"../utils/stringUtils.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { VibrationConfiguration } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfiguration } from \"../sensor/SensorConfigurationManager.ts\";\nimport { DevicePairSensorDataEventMessages, DevicePairSensorDataEventTypes } from \"./DevicePairSensorDataManager.ts\";\nimport { AddPrefixToInterfaceKeys, ExtendInterfaceValues, KeyOf } from \"../utils/TypeScriptUtils.ts\";\nimport DeviceManager from \"../DeviceManager.ts\";\n\nconst _console = createConsole(\"DevicePair\", { log: true });\n\ninterface BaseDevicePairDeviceEventMessage {\n device: Device;\n side: InsoleSide;\n}\ntype DevicePairDeviceEventMessages = ExtendInterfaceValues<\n AddPrefixToInterfaceKeys<DeviceEventMessages, \"device\">,\n BaseDevicePairDeviceEventMessage\n>;\ntype DevicePairDeviceEventType = KeyOf<DevicePairDeviceEventMessages>;\nfunction getDevicePairDeviceEventType(deviceEventType: DeviceEventType) {\n return `device${capitalizeFirstCharacter(deviceEventType)}` as DevicePairDeviceEventType;\n}\nconst DevicePairDeviceEventTypes = DeviceEventTypes.map((eventType) =>\n getDevicePairDeviceEventType(eventType)\n) as DevicePairDeviceEventType[];\n\nexport const DevicePairConnectionEventTypes = [\"isConnected\"] as const;\nexport type DevicePairConnectionEventType = (typeof DevicePairConnectionEventTypes)[number];\n\nexport interface DevicePairConnectionEventMessages {\n isConnected: { isConnected: boolean };\n}\n\nexport const DevicePairEventTypes = [\n ...DevicePairConnectionEventTypes,\n ...DevicePairSensorDataEventTypes,\n ...DevicePairDeviceEventTypes,\n] as const;\nexport type DevicePairEventType = (typeof DevicePairEventTypes)[number];\n\nexport type DevicePairEventMessages = DevicePairConnectionEventMessages &\n DevicePairSensorDataEventMessages &\n DevicePairDeviceEventMessages;\n\nexport type DevicePairEventDispatcher = EventDispatcher<DevicePair, DevicePairEventType, DevicePairEventMessages>;\nexport type DevicePairEventMap = EventMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEventListenerMap = EventListenerMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEvent = Event<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type BoundDevicePairEventListeners = BoundEventListeners<DevicePair, DeviceEventType, DevicePairEventMessages>;\n\nclass DevicePair {\n constructor() {\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as DevicePairSensorDataEventDispatcher;\n }\n\n get sides() {\n return InsoleSides;\n }\n\n #eventDispatcher: DevicePairEventDispatcher = new EventDispatcher(this as DevicePair, DevicePairEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // SIDES\n #left?: Device;\n get left() {\n return this.#left;\n }\n\n #right?: Device;\n get right() {\n return this.#right;\n }\n\n get isConnected() {\n return InsoleSides.every((side) => this[side]?.isConnected);\n }\n get isPartiallyConnected() {\n return InsoleSides.some((side) => this[side]?.isConnected);\n }\n get isHalfConnected() {\n return this.isPartiallyConnected && !this.isConnected;\n }\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"devicePair must be connected\");\n }\n\n assignInsole(device: Device) {\n if (!device.isInsole) {\n _console.warn(\"device is not an insole\");\n return;\n }\n const side = device.insoleSide;\n\n const currentDevice = this[side];\n\n if (device == currentDevice) {\n _console.log(\"device already assigned\");\n return;\n }\n\n if (currentDevice) {\n this.#removeDeviceEventListeners(currentDevice);\n }\n this.#addDeviceEventListeners(device);\n\n switch (side) {\n case \"left\":\n this.#left = device;\n break;\n case \"right\":\n this.#right = device;\n break;\n }\n\n _console.log(`assigned ${side} insole`, device);\n\n this.resetPressureRange();\n\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n this.#dispatchEvent(\"deviceIsConnected\", { device, isConnected: device.isConnected, side });\n\n return currentDevice;\n }\n\n #addDeviceEventListeners(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.addEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n #removeDeviceEventListeners(device: Device) {\n removeEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.removeEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n\n #removeInsole(device: Device) {\n const foundDevice = InsoleSides.some((side) => {\n if (this[side] != device) {\n return false;\n }\n\n _console.log(`removing ${side} insole`, device);\n removeEventListeners(device, this.#boundDeviceEventListeners);\n delete this[side];\n\n return true;\n });\n if (foundDevice) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n return foundDevice;\n }\n\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n isConnected: this.#onDeviceIsConnected.bind(this),\n sensorData: this.#onDeviceSensorData.bind(this),\n getType: this.#onDeviceType.bind(this),\n };\n\n #redispatchDeviceEvent(deviceEvent: DeviceEvent) {\n const { type, target: device, message } = deviceEvent;\n this.#dispatchEvent(getDevicePairDeviceEventType(type), {\n ...message,\n device,\n side: device.insoleSide,\n });\n }\n\n #onDeviceIsConnected(deviceEvent: DeviceEventMap[\"isConnected\"]) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n\n #onDeviceType(deviceEvent: DeviceEventMap[\"getType\"]) {\n const { target: device } = deviceEvent;\n if (this[device.insoleSide] == device) {\n return;\n }\n const foundDevice = this.#removeInsole(device);\n if (!foundDevice) {\n return;\n }\n this.assignInsole(device);\n }\n\n // SENSOR CONFIGURATION\n async setSensorConfiguration(sensorConfiguration: SensorConfiguration) {\n for (let i = 0; i < InsoleSides.length; i++) {\n const side = InsoleSides[i];\n if (this[side]?.isConnected) {\n await this[side].setSensorConfiguration(sensorConfiguration);\n }\n }\n }\n\n // SENSOR DATA\n #sensorDataManager = new DevicePairSensorDataManager();\n #onDeviceSensorData(deviceEvent: DeviceEventMap[\"sensorData\"]) {\n if (this.isConnected) {\n this.#sensorDataManager.onDeviceSensorData(deviceEvent);\n }\n }\n resetPressureRange() {\n this.#sensorDataManager.resetPressureRange();\n }\n\n // VIBRATION\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n const promises = InsoleSides.map((side) => {\n return this[side]?.triggerVibration(vibrationConfigurations, sendImmediately);\n }).filter(Boolean);\n return Promise.allSettled(promises);\n }\n\n // SHARED INSTANCE\n static #shared = new DevicePair();\n static get shared() {\n return this.#shared;\n }\n static {\n DeviceManager.AddEventListener(\"deviceConnected\", (event) => {\n const { device } = event.message;\n if (device.isInsole) {\n this.#shared.assignInsole(device);\n }\n });\n }\n}\n\nexport default DevicePair;\n","import { DeviceEventTypes } from \"../Device.ts\";\nimport { ConnectionMessageType, ConnectionMessageTypes } from \"../connection/BaseConnectionManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\n\nconst _console = createConsole(\"ServerUtils\", { log: false });\n\nexport const ServerMessageTypes = [\n \"isScanningAvailable\",\n \"isScanning\",\n \"startScan\",\n \"stopScan\",\n \"discoveredDevice\",\n \"discoveredDevices\",\n \"expiredDiscoveredDevice\",\n \"connectToDevice\",\n \"disconnectFromDevice\",\n \"connectedDevices\",\n \"deviceMessage\",\n] as const;\nexport type ServerMessageType = (typeof ServerMessageTypes)[number];\n\nexport const DeviceMessageTypes = [\"connectionStatus\", \"batteryLevel\", \"deviceInformation\", \"rx\", \"smp\"] as const;\nexport type DeviceMessageType = (typeof DeviceMessageTypes)[number];\n\n// MESSAGING\n\nexport type MessageLike = number | number[] | ArrayBufferLike | DataView | boolean | string | any;\n\nexport interface Message<MessageType extends string> {\n type: MessageType;\n data?: MessageLike | MessageLike[];\n}\n\nexport function createMessage<MessageType extends string>(\n enumeration: readonly MessageType[],\n ...messages: (Message<MessageType> | MessageType)[]\n) {\n _console.log(\"createMessage\", ...messages);\n\n const messageBuffers = messages.map((message) => {\n if (typeof message == \"string\") {\n message = { type: message };\n }\n\n if (message.data != undefined) {\n if (!Array.isArray(message.data)) {\n message.data = [message.data];\n }\n } else {\n message.data = [];\n }\n\n const messageDataArrayBuffer = concatenateArrayBuffers(...message.data);\n const messageDataArrayBufferByteLength = messageDataArrayBuffer.byteLength;\n\n _console.assertEnumWithError(message.type, enumeration);\n const messageTypeEnum = enumeration.indexOf(message.type);\n\n const messageDataLengthDataView = new DataView(new ArrayBuffer(2));\n messageDataLengthDataView.setUint16(0, messageDataArrayBufferByteLength, true);\n\n return concatenateArrayBuffers(messageTypeEnum, messageDataLengthDataView, messageDataArrayBuffer);\n });\n _console.log(\"messageBuffers\", ...messageBuffers);\n return concatenateArrayBuffers(...messageBuffers);\n}\n\nexport type ServerMessage = ServerMessageType | Message<ServerMessageType>;\nexport function createServerMessage(...messages: ServerMessage[]) {\n _console.log(\"createServerMessage\", ...messages);\n return createMessage(ServerMessageTypes, ...messages);\n}\n\nexport type DeviceMessage = DeviceEventType | Message<DeviceEventType>;\nexport function createDeviceMessage(...messages: DeviceMessage[]) {\n _console.log(\"createDeviceMessage\", ...messages);\n return createMessage(DeviceEventTypes, ...messages);\n}\n\nexport type ClientDeviceMessage = ConnectionMessageType | Message<ConnectionMessageType>;\nexport function createClientDeviceMessage(...messages: ClientDeviceMessage[]) {\n _console.log(\"createClientDeviceMessage\", ...messages);\n return createMessage(ConnectionMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const isScanningAvailableRequestMessage = createServerMessage(\"isScanningAvailable\");\nexport const isScanningRequestMessage = createServerMessage(\"isScanning\");\nexport const startScanRequestMessage = createServerMessage(\"startScan\");\nexport const stopScanRequestMessage = createServerMessage(\"stopScan\");\nexport const discoveredDevicesMessage = createServerMessage(\"discoveredDevices\");\n","import { createConsole } from \"../utils/Console.ts\";\nimport { isInBrowser } from \"../utils/environment.ts\";\nimport BaseConnectionManager, { ConnectionType, ConnectionMessageType } from \"./BaseConnectionManager.ts\";\nimport { DeviceEventTypes } from \"../Device.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\nimport { ClientDeviceMessage } from \"../server/ServerUtils.ts\";\n\nconst _console = createConsole(\"ClientConnectionManager\", { log: true });\n\nexport type SendClientMessageCallback = (...messages: ClientDeviceMessage[]) => void;\n\nconst ClientDeviceInformationMessageTypes: ConnectionMessageType[] = [...DeviceInformationMessageTypes, \"batteryLevel\"];\n\nclass ClientConnectionManager extends BaseConnectionManager {\n static get isSupported() {\n return isInBrowser;\n }\n static get type(): ConnectionType {\n return \"client\";\n }\n\n #bluetoothId!: string;\n get bluetoothId() {\n return this.#bluetoothId!;\n }\n set bluetoothId(newBluetoothId) {\n _console.assertTypeWithError(newBluetoothId, \"string\");\n if (this.#bluetoothId == newBluetoothId) {\n _console.log(\"redundant bluetoothId assignment\");\n return;\n }\n this.#bluetoothId = newBluetoothId;\n }\n\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n set isConnected(newIsConnected) {\n _console.assertTypeWithError(newIsConnected, \"boolean\");\n if (this.#isConnected == newIsConnected) {\n _console.log(\"redundant newIsConnected assignment\", newIsConnected);\n return;\n }\n this.#isConnected = newIsConnected;\n\n this.status = this.#isConnected ? \"connected\" : \"notConnected\";\n\n if (this.isConnected) {\n this.#requestDeviceInformation();\n }\n }\n\n async connect() {\n await super.connect();\n this.sendClientConnectMessage();\n }\n async disconnect() {\n await super.disconnect();\n this.sendClientDisconnectMessage();\n }\n\n get canReconnect() {\n return true;\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.connect();\n }\n\n sendClientMessage!: SendClientMessageCallback;\n sendClientConnectMessage!: Function;\n sendClientDisconnectMessage!: Function;\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n this.sendClientMessage({ type: \"smp\", data });\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n this.sendClientMessage({ type: \"tx\", data });\n }\n\n #requestDeviceInformation() {\n this.sendClientMessage(...ClientDeviceInformationMessageTypes);\n }\n\n onClientMessage(dataView: DataView) {\n _console.log({ dataView });\n parseMessage(dataView, DeviceEventTypes, this.#onClientMessageCallback.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onClientMessageCallback(messageType: DeviceEventType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isConnected\":\n const isConnected = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isConnected });\n this.isConnected = isConnected;\n break;\n\n case \"rx\":\n this.parseRxMessage(dataView);\n break;\n\n default:\n this.onMessageReceived!(messageType as ConnectionMessageType, dataView);\n break;\n }\n }\n}\n\nexport default ClientConnectionManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport {\n ServerMessageTypes,\n discoveredDevicesMessage,\n ServerMessage,\n MessageLike,\n ClientDeviceMessage,\n createClientDeviceMessage,\n ServerMessageType,\n} from \"./ServerUtils.ts\";\nimport { parseMessage, parseStringFromDataView } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher, { BoundEventListeners, Event } from \"../utils/EventDispatcher.ts\";\nimport Device from \"../Device.ts\";\nimport { sliceDataView } from \"../utils/ArrayBufferUtils.ts\";\nimport { DiscoveredDevice, DiscoveredDevicesMap, ScannerEventMessages } from \"../scanner/BaseScanner.ts\";\nimport ClientConnectionManager from \"../connection/ClientConnectionManager.ts\";\n\nconst _console = createConsole(\"BaseClient\", { log: true });\n\nexport const ClientConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ClientConnectionStatus = (typeof ClientConnectionStatuses)[number];\n\nexport const ClientEventTypes = [\n ...ClientConnectionStatuses,\n \"connectionStatus\",\n \"isConnected\",\n \"isScanningAvailable\",\n \"isScanning\",\n \"discoveredDevice\",\n \"expiredDiscoveredDevice\",\n] as const;\nexport type ClientEventType = (typeof ClientEventTypes)[number];\n\ninterface ClientConnectionEventMessages {\n connectionStatus: { connectionStatus: ClientConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport type ClientEventMessages = ClientConnectionEventMessages & ScannerEventMessages;\n\nexport type ClientEventDispatcher = EventDispatcher<BaseClient, ClientEventType, ClientEventMessages>;\nexport type ClientEvent = Event<BaseClient, ClientEventType, ClientEventMessages>;\nexport type BoundClientEventListeners = BoundEventListeners<BaseClient, ClientEventType, ClientEventMessages>;\n\nexport type ServerURL = string | URL;\n\ntype DevicesMap = { [deviceId: string]: Device };\n\nabstract class BaseClient {\n protected get baseConstructor() {\n return this.constructor as typeof BaseClient;\n }\n\n #reset() {\n this.#isScanningAvailable = false;\n this.#isScanning = false;\n for (const id in this.#devices) {\n const device = this.#devices[id];\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n //device.removeAllEventListeners();\n }\n //this.#devices = {};\n }\n\n // DEVICES\n #devices: DevicesMap = {};\n get devices(): Readonly<DevicesMap> {\n return this.#devices;\n }\n\n #eventDispatcher: ClientEventDispatcher = new EventDispatcher(this as BaseClient, ClientEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n protected get dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n\n abstract isConnected: boolean;\n protected assertConnection() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n abstract isDisconnected: boolean;\n protected assertDisconnection() {\n _console.assertWithError(this.isDisconnected, \"not disconnected\");\n }\n\n abstract connect(): void;\n abstract disconnect(): void;\n abstract reconnect(): void;\n abstract toggleConnection(url?: ServerURL): void;\n\n static _reconnectOnDisconnection = true;\n static get ReconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n protected _reconnectOnDisconnection = this.baseConstructor.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n abstract sendServerMessage(...messages: ServerMessage[]): void;\n\n // CONNECTION STATUS\n #_connectionStatus: ClientConnectionStatus = \"notConnected\";\n protected get _connectionStatus() {\n return this.#_connectionStatus;\n }\n protected set _connectionStatus(newConnectionStatus) {\n _console.assertTypeWithError(newConnectionStatus, \"string\");\n _console.log({ newConnectionStatus });\n this.#_connectionStatus = newConnectionStatus;\n\n this.dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.dispatchEvent(this.connectionStatus, {});\n\n switch (newConnectionStatus) {\n case \"connected\":\n case \"notConnected\":\n this.dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n if (this.isConnected) {\n this.sendServerMessage(\"isScanningAvailable\", \"discoveredDevices\", \"connectedDevices\");\n } else {\n this.#reset();\n }\n break;\n }\n }\n get connectionStatus() {\n return this._connectionStatus;\n }\n\n protected parseMessage(dataView: DataView) {\n _console.log(\"parseMessage\", { dataView });\n parseMessage(dataView, ServerMessageTypes, this.#parseMessageCallback.bind(this), null, true);\n }\n\n #parseMessageCallback(messageType: ServerMessageType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isScanningAvailable\":\n {\n const isScanningAvailable = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanningAvailable });\n this.#isScanningAvailable = isScanningAvailable;\n }\n break;\n case \"isScanning\":\n {\n const isScanning = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanning });\n this.#isScanning = isScanning;\n }\n break;\n case \"discoveredDevice\":\n {\n const { string: discoveredDeviceString } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ discoveredDeviceString });\n\n const discoveredDevice: DiscoveredDevice = JSON.parse(discoveredDeviceString);\n _console.log({ discoveredDevice });\n\n this.onDiscoveredDevice(discoveredDevice);\n }\n break;\n case \"expiredDiscoveredDevice\":\n {\n const { string: bluetoothId } = parseStringFromDataView(dataView, byteOffset);\n this.#onExpiredDiscoveredDevice(bluetoothId);\n }\n break;\n case \"connectedDevices\":\n {\n if (dataView.byteLength == 0) {\n break;\n }\n const { string: connectedBluetoothDeviceIdStrings } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ connectedBluetoothDeviceIdStrings });\n const connectedBluetoothDeviceIds = JSON.parse(connectedBluetoothDeviceIdStrings).connectedDevices;\n _console.log({ connectedBluetoothDeviceIds });\n this.onConnectedBluetoothDeviceIds(connectedBluetoothDeviceIds);\n }\n break;\n case \"deviceMessage\":\n {\n const { string: bluetoothId, byteOffset: _byteOffset } = parseStringFromDataView(dataView, byteOffset);\n byteOffset = _byteOffset;\n const device = this.#devices[bluetoothId];\n _console.assertWithError(device, `no device found for id ${bluetoothId}`);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n const _dataView = sliceDataView(dataView, byteOffset);\n connectionManager.onClientMessage(_dataView);\n }\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // SCANNING\n #_isScanningAvailable = false;\n get #isScanningAvailable() {\n return this.#_isScanningAvailable;\n }\n set #isScanningAvailable(newIsAvailable) {\n _console.assertTypeWithError(newIsAvailable, \"boolean\");\n this.#_isScanningAvailable = newIsAvailable;\n this.dispatchEvent(\"isScanningAvailable\", { isScanningAvailable: this.isScanningAvailable });\n if (this.isScanningAvailable) {\n this.#requestIsScanning();\n }\n }\n get isScanningAvailable() {\n return this.#isScanningAvailable;\n }\n #assertIsScanningAvailable() {\n this.assertConnection();\n _console.assertWithError(this.isScanningAvailable, \"scanning is not available\");\n }\n protected requestIsScanningAvailable() {\n this.sendServerMessage(\"isScanningAvailable\");\n }\n\n #_isScanning = false;\n get #isScanning() {\n return this.#_isScanning;\n }\n set #isScanning(newIsScanning) {\n _console.assertTypeWithError(newIsScanning, \"boolean\");\n this.#_isScanning = newIsScanning;\n this.dispatchEvent(\"isScanning\", { isScanning: this.isScanning });\n }\n get isScanning() {\n return this.#isScanning;\n }\n #requestIsScanning() {\n this.sendServerMessage(\"isScanning\");\n }\n\n #assertIsScanning() {\n _console.assertWithError(this.isScanning, \"is not scanning\");\n }\n #assertIsNotScanning() {\n _console.assertWithError(!this.isScanning, \"is already scanning\");\n }\n\n startScan() {\n this.#assertIsNotScanning();\n this.sendServerMessage(\"startScan\");\n }\n stopScan() {\n this.#assertIsScanning();\n this.sendServerMessage(\"stopScan\");\n }\n toggleScan() {\n this.#assertIsScanningAvailable();\n\n if (this.isScanning) {\n this.stopScan();\n } else {\n this.startScan();\n }\n }\n\n // PERIPHERALS\n #discoveredDevices: DiscoveredDevicesMap = {};\n get discoveredDevices(): Readonly<DiscoveredDevicesMap> {\n return this.#discoveredDevices;\n }\n\n protected onDiscoveredDevice(discoveredDevice: DiscoveredDevice) {\n _console.log({ discoveredDevice });\n this.#discoveredDevices[discoveredDevice.bluetoothId] = discoveredDevice;\n this.dispatchEvent(\"discoveredDevice\", { discoveredDevice });\n }\n requestDiscoveredDevices() {\n this.sendServerMessage({ type: \"discoveredDevices\" });\n }\n #onExpiredDiscoveredDevice(bluetoothId: string) {\n _console.log({ expiredBluetoothDeviceId: bluetoothId });\n const discoveredDevice = this.#discoveredDevices[bluetoothId];\n if (!discoveredDevice) {\n _console.warn(`no discoveredDevice found with id \"${bluetoothId}\"`);\n return;\n }\n _console.log({ expiredDiscoveredDevice: discoveredDevice });\n delete this.#discoveredDevices[bluetoothId];\n this.dispatchEvent(\"expiredDiscoveredDevice\", { discoveredDevice });\n }\n\n // DEVICE CONNECTION\n connectToDevice(bluetoothId: string) {\n return this.requestConnectionToDevice(bluetoothId);\n }\n protected requestConnectionToDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.#getOrCreateDevice(bluetoothId);\n device.connect();\n return device;\n }\n protected sendConnectToDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"connectToDevice\", data: bluetoothId });\n }\n\n // DEVICE CONNECTION\n createDevice(bluetoothId: string) {\n const device = new Device();\n const clientConnectionManager = new ClientConnectionManager();\n clientConnectionManager.bluetoothId = bluetoothId;\n clientConnectionManager.sendClientMessage = this.sendDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientConnectMessage = this.sendConnectToDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientDisconnectMessage = this.sendDisconnectFromDeviceMessage.bind(this, bluetoothId);\n device.connectionManager = clientConnectionManager;\n return device;\n }\n\n #getOrCreateDevice(bluetoothId: string) {\n let device = this.#devices[bluetoothId];\n if (!device) {\n device = this.createDevice(bluetoothId);\n this.#devices[bluetoothId] = device;\n }\n return device;\n }\n protected onConnectedBluetoothDeviceIds(bluetoothIds: string[]) {\n _console.log({ bluetoothIds });\n bluetoothIds.forEach((bluetoothId) => {\n const device = this.#getOrCreateDevice(bluetoothId);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = true;\n });\n }\n\n disconnectFromDevice(bluetoothId: string) {\n this.requestDisconnectionFromDevice(bluetoothId);\n }\n protected requestDisconnectionFromDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.devices[bluetoothId];\n _console.assertWithError(device, `no device found with id ${bluetoothId}`);\n device.disconnect();\n return device;\n }\n protected sendDisconnectFromDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"disconnectFromDevice\", data: bluetoothId });\n }\n\n protected sendDeviceMessage(bluetoothId: string, ...messages: ClientDeviceMessage[]) {\n this.sendServerMessage({\n type: \"deviceMessage\",\n data: [bluetoothId, createClientDeviceMessage(...messages)],\n });\n }\n}\n\nexport default BaseClient;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createMessage, Message } from \"../ServerUtils.ts\";\n\nconst _console = createConsole(\"WebSocketUtils\", { log: false });\n\nexport const webSocketPingTimeout = 30_000_000;\nexport const webSocketReconnectTimeout = 3_000;\n\nexport const WebSocketMessageTypes = [\"ping\", \"pong\", \"serverMessage\"] as const;\nexport type WebSocketMessageType = (typeof WebSocketMessageTypes)[number];\n\nexport type WebSocketMessage = WebSocketMessageType | Message<WebSocketMessageType>;\nexport function createWebSocketMessage(...messages: WebSocketMessage[]) {\n _console.log(\"createWebSocketMessage\", ...messages);\n return createMessage(WebSocketMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const webSocketPingMessage = createWebSocketMessage(\"ping\");\nexport const webSocketPongMessage = createWebSocketMessage(\"pong\");\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createServerMessage, MessageLike, ServerMessage } from \"../ServerUtils.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport ClientConnectionManager from \"../../connection/ClientConnectionManager.ts\";\nimport BaseClient, { ServerURL } from \"../BaseClient.ts\";\nimport type * as ws from \"ws\";\nimport Timer from \"../../utils/Timer.ts\";\nimport {\n createWebSocketMessage,\n WebSocketMessageType,\n WebSocketMessageTypes,\n webSocketPingTimeout,\n webSocketReconnectTimeout,\n WebSocketMessage,\n} from \"./WebSocketUtils.ts\";\nimport { parseMessage } from \"../../utils/ParseUtils.ts\";\n\nconst _console = createConsole(\"WebSocketClient\", { log: true });\n\nclass WebSocketClient extends BaseClient {\n // WEBSOCKET\n #webSocket?: WebSocket;\n get webSocket() {\n return this.#webSocket;\n }\n set webSocket(newWebSocket) {\n if (this.#webSocket == newWebSocket) {\n _console.log(\"redundant webSocket assignment\");\n return;\n }\n\n _console.log(\"assigning webSocket\", newWebSocket);\n\n if (this.#webSocket) {\n removeEventListeners(this.#webSocket, this.#boundWebSocketEventListeners);\n }\n\n addEventListeners(newWebSocket, this.#boundWebSocketEventListeners);\n this.#webSocket = newWebSocket;\n\n _console.log(\"assigned webSocket\");\n }\n get readyState() {\n return this.webSocket?.readyState;\n }\n get isConnected() {\n return this.readyState == WebSocket.OPEN;\n }\n get isDisconnected() {\n return this.readyState == WebSocket.CLOSED;\n }\n\n connect(url: string | URL = `wss://${location.host}`) {\n if (this.webSocket) {\n this.assertDisconnection();\n }\n this._connectionStatus = \"connecting\";\n this.webSocket = new WebSocket(url);\n }\n\n disconnect() {\n this.assertConnection();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.webSocket!.addEventListener(\n \"close\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n this._connectionStatus = \"disconnecting\";\n this.webSocket!.close();\n }\n\n reconnect() {\n this.assertDisconnection();\n this.connect(this.webSocket!.url);\n }\n\n toggleConnection(url?: ServerURL) {\n if (this.isConnected) {\n this.disconnect();\n } else if (url && this.webSocket?.url == url) {\n this.reconnect();\n } else {\n this.connect(url);\n }\n }\n\n // WEBSOCKET MESSAGING\n sendMessage(message: MessageLike) {\n this.assertConnection();\n this.#webSocket!.send(message);\n }\n\n sendServerMessage(...messages: ServerMessage[]) {\n this.sendMessage(createWebSocketMessage({ type: \"serverMessage\", data: createServerMessage(...messages) }));\n }\n\n #sendWebSocketMessage(...messages: WebSocketMessage[]) {\n this.sendMessage(createWebSocketMessage(...messages));\n }\n\n // WEBSOCKET EVENTS\n #boundWebSocketEventListeners: { [eventType: string]: Function } = {\n open: this.#onWebSocketOpen.bind(this),\n message: this.#onWebSocketMessage.bind(this),\n close: this.#onWebSocketClose.bind(this),\n error: this.#onWebSocketError.bind(this),\n };\n\n #onWebSocketOpen(event: ws.Event) {\n _console.log(\"webSocket.open\", event);\n this.#pingTimer.start();\n this._connectionStatus = \"connected\";\n }\n async #onWebSocketMessage(event: ws.MessageEvent) {\n _console.log(\"webSocket.message\", event);\n this.#pingTimer.restart();\n //@ts-expect-error\n const arrayBuffer = await event.data.arrayBuffer();\n const dataView = new DataView(arrayBuffer);\n this.#parseWebSocketMessage(dataView);\n }\n #onWebSocketClose(event: ws.CloseEvent) {\n _console.log(\"webSocket.close\", event);\n\n this._connectionStatus = \"notConnected\";\n\n Object.entries(this.devices).forEach(([id, device]) => {\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n });\n\n this.#pingTimer.stop();\n if (this.reconnectOnDisconnection) {\n setTimeout(() => {\n this.reconnect();\n }, webSocketReconnectTimeout);\n }\n }\n #onWebSocketError(event: ws.ErrorEvent) {\n _console.error(\"webSocket.error\", event.message);\n }\n\n // PARSING\n #parseWebSocketMessage(dataView: DataView) {\n parseMessage(dataView, WebSocketMessageTypes, this.#onServerMessage.bind(this), null, true);\n }\n\n #onServerMessage(messageType: WebSocketMessageType, dataView: DataView) {\n switch (messageType) {\n case \"ping\":\n this.#pong();\n break;\n case \"pong\":\n break;\n case \"serverMessage\":\n this.parseMessage(dataView);\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // PING\n #pingTimer = new Timer(this.#ping.bind(this), webSocketPingTimeout);\n #ping() {\n this.#sendWebSocketMessage(\"ping\");\n }\n #pong() {\n this.#sendWebSocketMessage(\"pong\");\n }\n}\n\nexport default WebSocketClient;\n"],"names":[],"mappings":"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;AACE;AACF;;AAGA;;;;;;;;;;;;;;AAEI;;;AAWA;;;AAGA;;;;;;;AASE;;;;;;;AAOA;;;;;;;;AASF;;;AAOA;AAEA;AACE;;;AAGC;AAED;AACA;AAEA;;;AAIA;AAEA;AAEA;;;AAEA;AACA;AACA;AACA;;;;AAoEF;AACA;AACA;AACA;;AA+BF;AACE;;;;;AAMA;AACE;AACA;;;AAEA;AACA;;AAEF;;AAGE;AACA;AACA;AACE;;;;;AAWJ;;;AAGA;AACA;AACA;AACA;AACE;;;AAEA;AACA;;AAGF;AACE;AACA;AACA;;;AAEA;AACA;;;AAGL;;AAvJG;AAEA;;;AAKA;AACA;AACE;AACA;;;;AAIA;;;AAGA;;AAEA;AACE;AACA;;;;AAOA;;AAEA;;AAEA;;AAEE;;AAEF;;AAEE;AACA;AACE;;;;;AAKV;AAEE;;;;;AAME;;AAEA;;AAEE;;AAEJ;AAEA;AACF;AASE;AAEA;AACA;AACF;AAGE;AAEA;AACA;;AAMA;;;AAIA;AACE;;;AAEA;;AAEJ;AA4BE;AACA;;;;;;;ACnNJ;AACA;AACA;;AAGA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;AACA;AAEA;AACA;;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;;;AAGA;AACA;;;AAGA;AACA;;AAEA;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;;AAEA;;AAEA;AACA;;;;AAKA;AACA;;AAEA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAIA;;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;;AAGA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAGA;AACA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AAEA;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;;AAEA;AACA;AAEA;AACA;AACA;;;;AAIA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AAEA;AACA;AACA;AACA;;AAGA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;AAMA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;;;;ACtYO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAIA;AACA;;AAGA;AACA;AACA;AACA;;AAEA;AACA;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","x_google_ignoreList":[0,8]}
1
+ {"version":3,"file":"brilliantsole.js","sources":["../node_modules/tslib/tslib.es6.js","../brilliantsole/utils/environment.ts","../brilliantsole/utils/Console.ts","../brilliantsole/utils/EventDispatcher.ts","../brilliantsole/utils/Timer.ts","../brilliantsole/utils/checksum.ts","../brilliantsole/utils/Text.ts","../brilliantsole/utils/ArrayBufferUtils.ts","../node_modules/auto-bind/index.js","../brilliantsole/FileTransferManager.ts","../brilliantsole/utils/MathUtils.ts","../brilliantsole/utils/RangeHelper.ts","../brilliantsole/utils/CenterOfPressureHelper.ts","../brilliantsole/utils/ArrayUtils.ts","../brilliantsole/sensor/PressureSensorDataManager.ts","../brilliantsole/sensor/MotionSensorDataManager.ts","../brilliantsole/sensor/BarometerSensorDataManager.ts","../brilliantsole/utils/ParseUtils.ts","../brilliantsole/CameraManager.ts","../brilliantsole/sensor/SensorDataManager.ts","../brilliantsole/sensor/SensorConfigurationManager.ts","../brilliantsole/TfliteManager.ts","../brilliantsole/DeviceInformationManager.ts","../brilliantsole/InformationManager.ts","../brilliantsole/vibration/VibrationWaveformEffects.ts","../brilliantsole/vibration/VibrationManager.ts","../brilliantsole/WifiManager.ts","../brilliantsole/connection/BaseConnectionManager.ts","../brilliantsole/utils/stringUtils.ts","../brilliantsole/utils/EventUtils.ts","../brilliantsole/connection/bluetooth/bluetoothUUIDs.ts","../brilliantsole/connection/bluetooth/BluetoothConnectionManager.ts","../brilliantsole/connection/bluetooth/WebBluetoothConnectionManager.ts","../brilliantsole/utils/cbor.js","../brilliantsole/utils/mcumgr.js","../brilliantsole/FirmwareManager.ts","../brilliantsole/DeviceManager.ts","../brilliantsole/server/ServerUtils.ts","../brilliantsole/server/websocket/WebSocketUtils.ts","../brilliantsole/connection/websocket/WebSocketConnectionManager.ts","../brilliantsole/Device.ts","../brilliantsole/devicePair/DevicePairPressureSensorDataManager.ts","../brilliantsole/devicePair/DevicePairSensorDataManager.ts","../brilliantsole/devicePair/DevicePair.ts","../brilliantsole/utils/ThrottleUtils.ts","../brilliantsole/connection/ClientConnectionManager.ts","../brilliantsole/server/BaseClient.ts","../brilliantsole/server/websocket/WebSocketClient.ts","../brilliantsole/BS.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","type ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\" | \"__BRILLIANTSOLE__PROD__\";\nconst __BRILLIANTSOLE__ENVIRONMENT__: ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\";\n\n//@ts-expect-error\nconst isInProduction = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__PROD__\";\nconst isInDev = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__DEV__\";\n\n// https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts\nconst isInBrowser = typeof window !== \"undefined\" && typeof window?.document !== \"undefined\";\nconst isInNode = typeof process !== \"undefined\" && process?.versions?.node != null;\n\nconst userAgent = (isInBrowser && navigator.userAgent) || \"\";\n\nlet isBluetoothSupported = false;\nif (isInBrowser) {\n isBluetoothSupported = Boolean(navigator.bluetooth);\n} else if (isInNode) {\n isBluetoothSupported = true;\n}\n\nconst isInBluefy = isInBrowser && /Bluefy/i.test(userAgent);\nconst isInWebBLE = isInBrowser && /WebBLE/i.test(userAgent);\n\nconst isAndroid = isInBrowser && /Android/i.test(userAgent);\nconst isSafari = isInBrowser && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);\n\nconst isIOS = isInBrowser && /iPad|iPhone|iPod/i.test(userAgent);\nconst isMac = isInBrowser && /Macintosh/i.test(userAgent);\n\n// @ts-expect-error\nconst isInLensStudio = !isInBrowser && !isInNode && typeof global !== \"undefined\" && typeof Studio !== \"undefined\";\n\nexport {\n isInDev,\n isInProduction,\n isInBrowser,\n isInNode,\n isAndroid,\n isInBluefy,\n isInWebBLE,\n isSafari,\n isInLensStudio,\n isIOS,\n isMac,\n isBluetoothSupported,\n};\n","import { isInDev, isInLensStudio, isInNode } from \"./environment.ts\";\n\ndeclare var Studio: any | undefined;\n\nexport type LogFunction = (...data: any[]) => void;\nexport type AssertLogFunction = (condition: boolean, ...data: any[]) => void;\n\nexport interface ConsoleLevelFlags {\n log?: boolean;\n warn?: boolean;\n error?: boolean;\n assert?: boolean;\n table?: boolean;\n}\n\ninterface ConsoleLike {\n log?: LogFunction;\n warn?: LogFunction;\n error?: LogFunction;\n assert?: AssertLogFunction;\n table?: LogFunction;\n}\n\nvar __console: ConsoleLike;\nif (isInLensStudio) {\n const log = function (...args: any[]) {\n Studio.log(args.map((value) => new String(value)).join(\",\"));\n };\n __console = {};\n __console.log = log;\n __console.warn = log.bind(__console, \"WARNING\");\n __console.error = log.bind(__console, \"ERROR\");\n} else {\n __console = console;\n}\n\nfunction getCallerFunctionPath(): string {\n const stack = new Error().stack;\n if (!stack) return \"\";\n\n const lines = stack.split(\"\\n\");\n const callerLine = lines[3] || lines[2];\n\n const match = callerLine.match(/at (.*?) \\(/) || callerLine.match(/at (.*)/);\n if (!match) return \"\";\n\n const fullFn = match[1].trim();\n return `[${fullFn}]`;\n}\n\nfunction wrapWithLocation(fn: LogFunction): LogFunction {\n return (...args: any[]) => {\n if (isInNode) {\n const functionPath = getCallerFunctionPath();\n fn(functionPath, ...args);\n } else {\n fn(...args);\n }\n };\n}\n\n// console.assert not supported in WebBLE\nif (!__console.assert) {\n const assert: AssertLogFunction = (condition, ...data) => {\n if (!condition) {\n __console.warn!(...data);\n }\n };\n __console.assert = assert;\n}\n\n// console.table not supported in WebBLE\nif (!__console.table) {\n const table: LogFunction = (...data) => {\n __console.log!(...data);\n };\n __console.table = table;\n}\n\nfunction emptyFunction() {}\n\nconst log: LogFunction = isInNode\n ? wrapWithLocation(__console.log!.bind(__console))\n : __console.log!.bind(__console);\nconst warn: LogFunction = isInNode\n ? wrapWithLocation(__console.warn!.bind(__console))\n : __console.warn!.bind(__console);\nconst error: LogFunction = isInNode\n ? wrapWithLocation(__console.error!.bind(__console))\n : __console.error!.bind(__console);\nconst table: LogFunction = isInNode\n ? wrapWithLocation(__console.table!.bind(__console))\n : __console.table!.bind(__console);\nconst assert: AssertLogFunction = __console.assert.bind(__console);\n\nclass Console {\n static #consoles: { [type: string]: Console } = {};\n\n constructor(type: string) {\n if (Console.#consoles[type]) {\n throw new Error(`\"${type}\" console already exists`);\n }\n Console.#consoles[type] = this;\n }\n\n #levelFlags: ConsoleLevelFlags = {\n log: isInDev,\n warn: isInDev,\n assert: true,\n error: true,\n table: true,\n };\n\n setLevelFlags(levelFlags: ConsoleLevelFlags) {\n Object.assign(this.#levelFlags, levelFlags);\n }\n\n /** @throws {Error} if no console with type \"type\" is found */\n static setLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n if (!this.#consoles[type]) {\n throw new Error(`no console found with type \"${type}\"`);\n }\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n\n static setAllLevelFlags(levelFlags: ConsoleLevelFlags) {\n for (const type in this.#consoles) {\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n }\n\n static create(type: string, levelFlags?: ConsoleLevelFlags): Console {\n const console = this.#consoles[type] || new Console(type);\n if (isInDev && levelFlags) {\n console.setLevelFlags(levelFlags);\n }\n return console;\n }\n\n get log() {\n return this.#levelFlags.log ? log : emptyFunction;\n }\n\n get warn() {\n return this.#levelFlags.warn ? warn : emptyFunction;\n }\n\n get error() {\n return this.#levelFlags.error ? error : emptyFunction;\n }\n\n get assert() {\n return this.#levelFlags.assert ? assert : emptyFunction;\n }\n\n get table() {\n return this.#levelFlags.table ? table : emptyFunction;\n }\n\n /** @throws {Error} if condition is not met */\n assertWithError(condition: any, message: string) {\n if (!Boolean(condition)) {\n throw new Error(message);\n }\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertTypeWithError(value: any, type: string) {\n this.assertWithError(\n typeof value == type,\n `value ${value} of type \"${typeof value}\" not of type \"${type}\"`\n );\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertEnumWithError(value: string, enumeration: readonly string[]) {\n this.assertWithError(\n enumeration.includes(value),\n `invalid enum \"${value}\"`\n );\n }\n\n /** @throws {Error} if value is not within some range */\n assertRangeWithError(name: string, value: number, min: number, max: number) {\n this.assertWithError(\n value >= min && value <= max,\n `${name} ${value} must be within ${min}-${max}`\n );\n }\n}\n\nexport function createConsole(\n type: string,\n levelFlags?: ConsoleLevelFlags\n): Console {\n return Console.create(type, levelFlags);\n}\n\n/** @throws {Error} if no console with type is found */\nexport function setConsoleLevelFlagsForType(\n type: string,\n levelFlags: ConsoleLevelFlags\n) {\n Console.setLevelFlagsForType(type, levelFlags);\n}\n\nexport function setAllConsoleLevelFlags(levelFlags: ConsoleLevelFlags) {\n Console.setAllLevelFlags(levelFlags);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { deepEqual } from \"./ObjectUtils.ts\";\n\nconst _console = createConsole(\"EventDispatcher\", { log: false });\n\nexport type EventMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: { type: T; target: Target; message: EventMessages[T] };\n};\nexport type EventListenerMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n};\n\nexport type Event<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = EventMap<Target, EventType, EventMessages>[keyof EventMessages];\n\ntype SpecificEvent<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>,\n SpecificEventType extends EventType\n> = { type: SpecificEventType; target: Target; message: EventMessages[SpecificEventType] };\n\nexport type BoundEventListeners<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [SpecificEventType in keyof EventMessages]?: (\n // @ts-expect-error\n event: SpecificEvent<Target, EventType, EventMessages, SpecificEventType>\n ) => void;\n};\n\nclass EventDispatcher<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> {\n private listeners: {\n [T in EventType]?: {\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n once?: boolean;\n shouldRemove?: boolean;\n }[];\n } = {};\n\n constructor(private target: Target, private validEventTypes: readonly EventType[]) {\n this.addEventListener = this.addEventListener.bind(this);\n this.removeEventListener = this.removeEventListener.bind(this);\n this.removeEventListeners = this.removeEventListeners.bind(this);\n this.removeAllEventListeners = this.removeAllEventListeners.bind(this);\n this.dispatchEvent = this.dispatchEvent.bind(this);\n this.waitForEvent = this.waitForEvent.bind(this);\n }\n\n private isValidEventType(type: any): type is EventType {\n return this.validEventTypes.includes(type);\n }\n\n private updateEventListeners(type: EventType) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type]!.filter((listenerObj) => {\n if (listenerObj.shouldRemove) {\n _console.log(`removing \"${type}\" eventListener`, listenerObj);\n }\n return !listenerObj.shouldRemove;\n });\n }\n\n addEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void,\n options: { once?: boolean } = { once: false }\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n _console.log(`creating \"${type}\" listeners array`, this.listeners[type]!);\n }\n const alreadyAdded = this.listeners[type].find((listenerObject) => {\n return listenerObject.listener == listener && listenerObject.once == options.once;\n });\n if (alreadyAdded) {\n _console.log(\"already added listener\");\n return;\n }\n _console.log(`adding \"${type}\" listener`, listener, options);\n this.listeners[type]!.push({ listener, once: options.once });\n\n _console.log(`currently have ${this.listeners[type]!.length} \"${type}\" listeners`);\n }\n\n removeEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listener...`, listener);\n this.listeners[type]!.forEach((listenerObj) => {\n const isListenerToRemove = listenerObj.listener === listener;\n if (isListenerToRemove) {\n _console.log(`flagging \"${type}\" listener`, listener);\n listenerObj.shouldRemove = true;\n }\n });\n\n this.updateEventListeners(type);\n }\n\n removeEventListeners<T extends EventType>(type: T): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listeners...`);\n this.listeners[type] = [];\n }\n\n removeAllEventListeners(): void {\n _console.log(`removing listeners...`);\n this.listeners = {};\n }\n\n dispatchEvent<T extends EventType>(type: T, message: EventMessages[T]): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n this.listeners[type]!.forEach((listenerObj) => {\n if (listenerObj.shouldRemove) {\n return;\n }\n\n _console.log(`dispatching \"${type}\" listener`, listenerObj);\n listenerObj.listener({ type, target: this.target, message });\n\n if (listenerObj.once) {\n _console.log(`flagging \"${type}\" listener`, listenerObj);\n listenerObj.shouldRemove = true;\n }\n });\n this.updateEventListeners(type);\n }\n\n waitForEvent<T extends EventType>(type: T): Promise<{ type: T; target: Target; message: EventMessages[T] }> {\n return new Promise((resolve) => {\n const onceListener = (event: { type: T; target: Target; message: EventMessages[T] }) => {\n resolve(event);\n };\n\n this.addEventListener(type, onceListener, { once: true });\n });\n }\n}\n\nexport default EventDispatcher;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"Timer\", { log: false });\n\nexport async function wait(delay: number) {\n _console.log(`waiting for ${delay} ms`);\n return new Promise((resolve: Function) => {\n setTimeout(() => resolve(), delay);\n });\n}\n\nclass Timer {\n #callback!: Function;\n get callback() {\n return this.#callback;\n }\n set callback(newCallback) {\n _console.assertTypeWithError(newCallback, \"function\");\n _console.log({ newCallback });\n this.#callback = newCallback;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n #interval!: number;\n get interval() {\n return this.#interval;\n }\n set interval(newInterval) {\n _console.assertTypeWithError(newInterval, \"number\");\n _console.assertWithError(newInterval > 0, \"interval must be above 0\");\n _console.log({ newInterval });\n this.#interval = newInterval;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n constructor(callback: Function, interval: number) {\n this.interval = interval;\n this.callback = callback;\n }\n\n #intervalId: number | undefined;\n get isRunning() {\n return this.#intervalId != undefined;\n }\n\n start(immediately = false) {\n if (this.isRunning) {\n _console.log(\"interval already running\");\n return;\n }\n _console.log(`starting interval every ${this.#interval}ms`);\n this.#intervalId = setInterval(this.#callback, this.#interval);\n if (immediately) {\n this.#callback();\n }\n }\n stop() {\n if (!this.isRunning) {\n _console.log(\"interval already not running\");\n return;\n }\n _console.log(\"stopping interval\");\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n restart(startImmediately = false) {\n this.stop();\n this.start(startImmediately);\n }\n}\nexport default Timer;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"checksum\", { log: false });\n\n// https://github.com/googlecreativelab/tiny-motion-trainer/blob/5fceb49f018ae0c403bf9f0ccc437309c2acb507/frontend/src/tf4micro-motion-kit/modules/bleFileTransfer#L195\n\n// See http://home.thep.lu.se/~bjorn/crc/ for more information on simple CRC32 calculations.\nexport function crc32ForByte(r: number) {\n for (let j = 0; j < 8; ++j) {\n r = (r & 1 ? 0 : 0xedb88320) ^ (r >>> 1);\n }\n return r ^ 0xff000000;\n}\n\nconst tableSize = 256;\nconst crc32Table = new Uint32Array(tableSize);\nfor (let i = 0; i < tableSize; ++i) {\n crc32Table[i] = crc32ForByte(i);\n}\n\nexport function crc32(dataIterable: ArrayBuffer | number[]) {\n let dataBytes = new Uint8Array(dataIterable);\n let crc = 0;\n for (let i = 0; i < dataBytes.byteLength; ++i) {\n const crcLowByte = crc & 0x000000ff;\n const dataByte = dataBytes[i];\n const tableIndex = crcLowByte ^ dataByte;\n // The last >>> is to convert this into an unsigned 32-bit integer.\n crc = (crc32Table[tableIndex] ^ (crc >>> 8)) >>> 0;\n }\n return crc;\n}\n\n// This is a small test function for the CRC32 implementation, not normally called but left in\n// for debugging purposes. We know the expected CRC32 of [97, 98, 99, 100, 101] is 2240272485,\n// or 0x8587d865, so if anything else is output we know there's an error in the implementation.\nexport function testCrc32() {\n const testArray = [97, 98, 99, 100, 101];\n const testArrayCrc32 = crc32(testArray);\n _console.log(\"CRC32 for [97, 98, 99, 100, 101] is 0x\" + testArrayCrc32.toString(16) + \" (\" + testArrayCrc32 + \")\");\n}\n","var _TextEncoder;\nif (typeof TextEncoder == \"undefined\") {\n _TextEncoder = class {\n encode(string: string) {\n const encoding = Array.from(string).map((char) => char.charCodeAt(0));\n return Uint8Array.from(encoding);\n }\n };\n} else {\n _TextEncoder = TextEncoder;\n}\n\nvar _TextDecoder;\nif (typeof TextDecoder == \"undefined\") {\n _TextDecoder = class {\n decode(data: ArrayBuffer) {\n const byteArray = Array.from(new Uint8Array(data));\n return byteArray\n .map((value) => {\n return String.fromCharCode(value);\n })\n .join(\"\");\n }\n };\n} else {\n _TextDecoder = TextDecoder;\n}\n\nexport const textEncoder = new _TextEncoder();\nexport const textDecoder = new _TextDecoder();\n","import { createConsole } from \"./Console.ts\";\nimport { textEncoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ArrayBufferUtils\", { log: false });\n\nexport function concatenateArrayBuffers(...arrayBuffers: any[]): ArrayBuffer {\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer != undefined || arrayBuffer != null);\n arrayBuffers = arrayBuffers.map((arrayBuffer) => {\n if (typeof arrayBuffer == \"number\") {\n const number = arrayBuffer;\n return Uint8Array.from([Math.floor(number)]);\n } else if (typeof arrayBuffer == \"boolean\") {\n const boolean = arrayBuffer;\n return Uint8Array.from([boolean ? 1 : 0]);\n } else if (typeof arrayBuffer == \"string\") {\n const string = arrayBuffer;\n return stringToArrayBuffer(string);\n } else if (arrayBuffer instanceof Array) {\n const array = arrayBuffer;\n return concatenateArrayBuffers(...array);\n } else if (arrayBuffer instanceof ArrayBuffer) {\n return arrayBuffer;\n } else if (\"buffer\" in arrayBuffer && arrayBuffer.buffer instanceof ArrayBuffer) {\n const bufferContainer = arrayBuffer;\n return bufferContainer.buffer;\n } else if (arrayBuffer instanceof DataView) {\n const dataView = arrayBuffer;\n return dataView.buffer;\n } else if (typeof arrayBuffer == \"object\") {\n const object = arrayBuffer;\n return objectToArrayBuffer(object);\n } else {\n return arrayBuffer;\n }\n });\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer && \"byteLength\" in arrayBuffer);\n const length = arrayBuffers.reduce((length, arrayBuffer) => length + arrayBuffer.byteLength, 0);\n const uint8Array = new Uint8Array(length);\n let byteOffset = 0;\n arrayBuffers.forEach((arrayBuffer) => {\n uint8Array.set(new Uint8Array(arrayBuffer), byteOffset);\n byteOffset += arrayBuffer.byteLength;\n });\n return uint8Array.buffer;\n}\n\nexport function dataToArrayBuffer(data: Buffer) {\n return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n}\n\nexport function stringToArrayBuffer(string: string) {\n const encoding = textEncoder.encode(string);\n return concatenateArrayBuffers(encoding.byteLength, encoding);\n}\n\nexport function objectToArrayBuffer(object: object) {\n return stringToArrayBuffer(JSON.stringify(object));\n}\n\nexport function sliceDataView(dataView: DataView, begin: number, length?: number) {\n let end;\n if (length != undefined) {\n end = dataView.byteOffset + begin + length;\n }\n _console.log({ dataView, begin, end, length });\n return new DataView(dataView.buffer.slice(dataView.byteOffset + begin, end));\n}\n\nexport type FileLike = number[] | ArrayBuffer | DataView | URL | string | File;\n\nexport async function getFileBuffer(file: FileLike) {\n let fileBuffer;\n if (file instanceof Array) {\n fileBuffer = Uint8Array.from(file);\n } else if (file instanceof DataView) {\n fileBuffer = file.buffer;\n } else if (typeof file == \"string\" || file instanceof URL) {\n const response = await fetch(file);\n fileBuffer = await response.arrayBuffer();\n } else if (file instanceof File) {\n fileBuffer = await file.arrayBuffer();\n } else if (file instanceof ArrayBuffer) {\n fileBuffer = file;\n } else {\n throw { error: \"invalid file type\", file };\n }\n return fileBuffer;\n}\n","// Gets all non-builtin properties up the prototype chain.\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nexport default function autoBind(self, {include, exclude} = {}) {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n}\n","import { createConsole } from \"./utils/Console.ts\";\nimport { crc32 } from \"./utils/checksum.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FileTransferManager\", { log: false });\n\nexport const FileTransferMessageTypes = [\n \"getFileTypes\",\n \"maxFileLength\",\n \"getFileType\",\n \"setFileType\",\n \"getFileLength\",\n \"setFileLength\",\n \"getFileChecksum\",\n \"setFileChecksum\",\n \"setFileTransferCommand\",\n \"fileTransferStatus\",\n \"getFileBlock\",\n \"setFileBlock\",\n \"fileBytesTransferred\",\n] as const;\nexport type FileTransferMessageType = (typeof FileTransferMessageTypes)[number];\n\nexport const FileTypes = [\"tflite\", \"wifiServerCert\", \"wifiServerKey\"] as const;\nexport type FileType = (typeof FileTypes)[number];\n\nexport const FileTransferStatuses = [\"idle\", \"sending\", \"receiving\"] as const;\nexport type FileTransferStatus = (typeof FileTransferStatuses)[number];\n\nexport const FileTransferCommands = [\n \"startSend\",\n \"startReceive\",\n \"cancel\",\n] as const;\nexport type FileTransferCommand = (typeof FileTransferCommands)[number];\n\nexport const FileTransferDirections = [\"sending\", \"receiving\"] as const;\nexport type FileTransferDirection = (typeof FileTransferDirections)[number];\n\nexport const FileTransferEventTypes = [\n ...FileTransferMessageTypes,\n \"fileTransferProgress\",\n \"fileTransferComplete\",\n \"fileReceived\",\n] as const;\nexport type FileTransferEventType = (typeof FileTransferEventTypes)[number];\n\nexport const RequiredFileTransferMessageTypes: FileTransferMessageType[] = [\n \"maxFileLength\",\n \"getFileLength\",\n \"getFileChecksum\",\n \"getFileType\",\n \"fileTransferStatus\",\n];\n\nexport interface FileConfiguration {\n file: FileLike;\n type: FileType;\n}\n\nexport interface FileTransferEventMessages {\n getFileTypes: { fileTypes: FileType[] };\n maxFileLength: { maxFileLength: number };\n getFileType: { fileType: FileType };\n getFileLength: { fileLength: number };\n getFileChecksum: { fileChecksum: number };\n fileTransferStatus: { fileTransferStatus: FileTransferStatus };\n getFileBlock: { fileTransferBlock: DataView };\n fileTransferProgress: { progress: number };\n fileTransferComplete: { direction: FileTransferDirection };\n fileReceived: { file: File | Blob };\n}\n\nexport type FileTransferEventDispatcher = EventDispatcher<\n Device,\n FileTransferEventType,\n FileTransferEventMessages\n>;\nexport type SendFileTransferMessageCallback =\n SendMessageCallback<FileTransferMessageType>;\n\nclass FileTransferManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendFileTransferMessageCallback;\n\n eventDispatcher!: FileTransferEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #assertValidType(type: FileType) {\n _console.assertEnumWithError(type, FileTypes);\n }\n #isValidType(type: FileType) {\n return FileTypes.includes(type);\n }\n #assertValidTypeEnum(typeEnum: number) {\n _console.assertWithError(\n typeEnum in FileTypes,\n `invalid typeEnum ${typeEnum}`\n );\n }\n\n #assertValidStatusEnum(statusEnum: number) {\n _console.assertWithError(\n statusEnum in FileTransferStatuses,\n `invalid statusEnum ${statusEnum}`\n );\n }\n #assertValidCommand(command: FileTransferCommand) {\n _console.assertEnumWithError(command, FileTransferCommands);\n }\n\n #fileTypes: FileType[] = [];\n get fileTypes() {\n return this.#fileTypes;\n }\n #parseFileTypes(dataView: DataView) {\n const fileTypes = Array.from(new Uint8Array(dataView.buffer))\n .map((index) => FileTypes[index])\n .filter(Boolean);\n this.#fileTypes = fileTypes;\n _console.log(\"fileTypes\", fileTypes);\n this.#dispatchEvent(\"getFileTypes\", {\n fileTypes: this.#fileTypes,\n });\n }\n\n static #MaxLength = 0; // kB\n static get MaxLength() {\n return this.#MaxLength;\n }\n #maxLength = FileTransferManager.MaxLength;\n /** kB */\n get maxLength() {\n return this.#maxLength;\n }\n #parseMaxLength(dataView: DataView) {\n _console.log(\"parseFileMaxLength\", dataView);\n const maxLength = dataView.getUint32(0, true);\n _console.log(`maxLength: ${maxLength / 1024}kB`);\n this.#updateMaxLength(maxLength);\n }\n #updateMaxLength(maxLength: number) {\n _console.log({ maxLength });\n this.#maxLength = maxLength;\n this.#dispatchEvent(\"maxFileLength\", { maxFileLength: maxLength });\n }\n #assertValidLength(length: number) {\n _console.assertWithError(\n length <= this.maxLength,\n `file length ${length}kB too large - must be ${this.maxLength}kB or less`\n );\n }\n\n #type: FileType | undefined;\n get type() {\n return this.#type;\n }\n #parseType(dataView: DataView) {\n _console.log(\"parseFileType\", dataView);\n const typeEnum = dataView.getUint8(0);\n this.#assertValidTypeEnum(typeEnum);\n const type = FileTypes[typeEnum];\n this.#updateType(type);\n }\n #updateType(type: FileType) {\n _console.log({ fileTransferType: type });\n this.#type = type;\n this.#dispatchEvent(\"getFileType\", { fileType: type });\n }\n async #setType(newType: FileType, sendImmediately?: boolean) {\n this.#assertValidType(newType);\n if (this.type == newType) {\n _console.log(`redundant type assignment ${newType}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileType\");\n\n const typeEnum = FileTypes.indexOf(newType);\n this.sendMessage(\n [{ type: \"setFileType\", data: Uint8Array.from([typeEnum]).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #length = 0;\n get length() {\n return this.#length;\n }\n #parseLength(dataView: DataView) {\n _console.log(\"parseFileLength\", dataView);\n const length = dataView.getUint32(0, true);\n\n this.#updateLength(length);\n }\n #updateLength(length: number) {\n _console.log(`length: ${length / 1024}kB`);\n this.#length = length;\n this.#dispatchEvent(\"getFileLength\", { fileLength: length });\n }\n async #setLength(newLength: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newLength, \"number\");\n this.#assertValidLength(newLength);\n if (this.length == newLength) {\n _console.log(`redundant length assignment ${newLength}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileLength\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newLength, true);\n this.sendMessage(\n [{ type: \"setFileLength\", data: dataView.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #checksum = 0;\n get checksum() {\n return this.#checksum;\n }\n #parseChecksum(dataView: DataView) {\n _console.log(\"checksum\", dataView);\n const checksum = dataView.getUint32(0, true);\n this.#updateChecksum(checksum);\n }\n #updateChecksum(checksum: number) {\n _console.log({ checksum });\n this.#checksum = checksum;\n this.#dispatchEvent(\"getFileChecksum\", { fileChecksum: checksum });\n }\n async #setChecksum(newChecksum: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newChecksum, \"number\");\n if (this.checksum == newChecksum) {\n _console.log(`redundant checksum assignment ${newChecksum}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileChecksum\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newChecksum, true);\n this.sendMessage(\n [{ type: \"setFileChecksum\", data: dataView.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n async #setCommand(command: FileTransferCommand, sendImmediately?: boolean) {\n this.#assertValidCommand(command);\n\n const promise = this.waitForEvent(\"fileTransferStatus\");\n _console.log(`setting command ${command}`);\n const commandEnum = FileTransferCommands.indexOf(command);\n this.sendMessage(\n [\n {\n type: \"setFileTransferCommand\",\n data: Uint8Array.from([commandEnum]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n\n #status: FileTransferStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #parseStatus(dataView: DataView) {\n _console.log(\"parseFileStatus\", dataView);\n const statusEnum = dataView.getUint8(0);\n this.#assertValidStatusEnum(statusEnum);\n const status = FileTransferStatuses[statusEnum];\n this.#updateStatus(status);\n }\n #updateStatus(status: FileTransferStatus) {\n _console.log({ status });\n this.#status = status;\n this.#dispatchEvent(\"fileTransferStatus\", { fileTransferStatus: status });\n this.#receivedBlocks.length = 0;\n this.#isCancelling = false;\n }\n #assertIsIdle() {\n _console.assertWithError(this.#status == \"idle\", \"status is not idle\");\n }\n #assertIsNotIdle() {\n _console.assertWithError(this.#status != \"idle\", \"status is idle\");\n }\n\n // BLOCK\n\n #receivedBlocks: ArrayBuffer[] = [];\n\n async #parseBlock(dataView: DataView) {\n _console.log(\"parseFileBlock\", dataView);\n this.#receivedBlocks.push(dataView.buffer);\n\n const bytesReceived = this.#receivedBlocks.reduce(\n (sum, arrayBuffer) => (sum += arrayBuffer.byteLength),\n 0\n );\n const progress = bytesReceived / this.#length;\n\n _console.log(\n `received ${bytesReceived} of ${this.#length} bytes (${progress * 100}%)`\n );\n\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n\n if (bytesReceived != this.#length) {\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, bytesReceived, true);\n\n if (this.isServerSide) {\n return;\n }\n await this.sendMessage([\n { type: \"fileBytesTransferred\", data: dataView.buffer },\n ]);\n return;\n }\n\n _console.log(\"file transfer complete\");\n\n let fileName = new Date().toLocaleString();\n switch (this.type) {\n case \"tflite\":\n fileName += \".tflite\";\n break;\n case \"wifiServerCert\":\n fileName += \"_server.crt\";\n break;\n case \"wifiServerKey\":\n fileName += \"_server.key\";\n break;\n }\n\n let file: File | Blob;\n if (typeof File !== \"undefined\") {\n file = new File(this.#receivedBlocks, fileName);\n } else {\n file = new Blob(this.#receivedBlocks);\n }\n\n const arrayBuffer = await file.arrayBuffer();\n const checksum = crc32(arrayBuffer);\n _console.log({ checksum });\n\n if (checksum != this.#checksum) {\n _console.error(\n `wrong checksum - expected ${this.#checksum}, got ${checksum}`\n );\n return;\n }\n\n _console.log(\"received file\", file);\n\n this.#dispatchEvent(\"getFileBlock\", { fileTransferBlock: dataView });\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"receiving\" });\n this.#dispatchEvent(\"fileReceived\", { file });\n }\n\n parseMessage(messageType: FileTransferMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getFileTypes\":\n this.#parseFileTypes(dataView);\n break;\n case \"maxFileLength\":\n this.#parseMaxLength(dataView);\n break;\n case \"getFileType\":\n case \"setFileType\":\n this.#parseType(dataView);\n break;\n case \"getFileLength\":\n case \"setFileLength\":\n this.#parseLength(dataView);\n break;\n case \"getFileChecksum\":\n case \"setFileChecksum\":\n this.#parseChecksum(dataView);\n break;\n case \"fileTransferStatus\":\n this.#parseStatus(dataView);\n break;\n case \"getFileBlock\":\n this.#parseBlock(dataView);\n break;\n case \"fileBytesTransferred\":\n this.#parseBytesTransferred(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async send(type: FileType, file: FileLike) {\n if (true) {\n this.#assertIsIdle();\n this.#assertValidType(type);\n } else {\n if (this.status != \"idle\") {\n _console.warn(`cannot send file - status is ${this.status}`);\n return false;\n }\n if (!this.#isValidType(type)) {\n _console.warn(`invalid fileType ${type}`);\n return false;\n }\n }\n\n const fileBuffer = await getFileBuffer(file);\n const fileLength = fileBuffer.byteLength;\n const checksum = crc32(fileBuffer);\n\n if (type != this.type) {\n _console.log(\"different fileTypes - sending\");\n } else if (fileLength != this.length) {\n _console.log(\"different fileLengths - sending\");\n } else if (checksum != this.checksum) {\n _console.log(\"different fileChecksums - sending\");\n } else {\n _console.log(\"already sent file\");\n return false;\n }\n\n const promises: Promise<any>[] = [];\n\n promises.push(this.#setType(type, false));\n promises.push(this.#setLength(fileLength, false));\n promises.push(this.#setChecksum(checksum, false));\n promises.push(this.#setCommand(\"startSend\", false));\n\n this.sendMessage();\n\n await Promise.all(promises);\n\n await this.#send(fileBuffer);\n\n return true;\n }\n\n #buffer?: ArrayBuffer;\n #bytesTransferred = 0;\n async #send(buffer: ArrayBuffer) {\n this.#buffer = buffer;\n this.#bytesTransferred = 0;\n return this.#sendBlock();\n }\n\n mtu!: number;\n async #sendBlock(): Promise<void> {\n if (this.status != \"sending\") {\n return;\n }\n if (this.#isCancelling) {\n _console.error(\"not sending block - busy cancelling\");\n return;\n }\n if (!this.#buffer) {\n if (!this.isServerSide) {\n _console.error(\"no buffer defined\");\n }\n return;\n }\n\n const buffer = this.#buffer;\n let offset = this.#bytesTransferred;\n\n const slicedBuffer = buffer.slice(offset, offset + (this.mtu - 3 - 3));\n _console.log(\"slicedBuffer\", slicedBuffer);\n const bytesLeft = buffer.byteLength - offset;\n\n const progress = 1 - bytesLeft / buffer.byteLength;\n _console.log(\n `sending bytes ${offset}-${offset + slicedBuffer.byteLength} of ${\n buffer.byteLength\n } bytes (${progress * 100}%)`\n );\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n if (slicedBuffer.byteLength == 0) {\n _console.log(\"finished sending buffer\");\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"sending\" });\n } else {\n await this.sendMessage([{ type: \"setFileBlock\", data: slicedBuffer }]);\n this.#bytesTransferred = offset + slicedBuffer.byteLength;\n //return this.#sendBlock(buffer, offset + slicedBuffer.byteLength);\n }\n }\n\n async #parseBytesTransferred(dataView: DataView) {\n _console.log(\"parseBytesTransferred\", dataView);\n const bytesTransferred = dataView.getUint32(0, true);\n _console.log({ bytesTransferred });\n if (this.status != \"sending\") {\n _console.error(`not currently sending file`);\n return;\n }\n if (!this.isServerSide && this.#bytesTransferred != bytesTransferred) {\n _console.error(\n `bytesTransferred are not equal - got ${bytesTransferred}, expected ${\n this.#bytesTransferred\n }`\n );\n this.cancel();\n return;\n }\n this.#sendBlock();\n }\n\n async receive(type: FileType) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n\n await this.#setType(type);\n await this.#setCommand(\"startReceive\");\n }\n\n #isCancelling = false;\n async cancel() {\n this.#assertIsNotIdle();\n _console.log(\"cancelling file transfer...\");\n this.#isCancelling = true;\n await this.#setCommand(\"cancel\");\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n }\n\n requestRequiredInformation() {\n _console.log(\"requesting required fileTransfer information\");\n const messages = RequiredFileTransferMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendMessage(messages, false);\n }\n}\n\nexport default FileTransferManager;\n","import { PressureSensorPosition } from \"../sensor/PressureSensorDataManager.ts\";\nimport { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"MathUtils\", { log: false });\n\nexport function getInterpolation(value: number, min: number, max: number, span: number) {\n if (span == undefined) {\n span = max - min;\n }\n return (value - min) / span;\n}\n\nexport const Uint16Max = 2 ** 16;\n\nfunction removeLower2Bytes(number: number) {\n const lower2Bytes = number % Uint16Max;\n return number - lower2Bytes;\n}\n\nconst timestampThreshold = 60_000;\n\nexport function parseTimestamp(dataView: DataView, byteOffset: number) {\n const now = Date.now();\n const nowWithoutLower2Bytes = removeLower2Bytes(now);\n const lower2Bytes = dataView.getUint16(byteOffset, true);\n let timestamp = nowWithoutLower2Bytes + lower2Bytes;\n if (Math.abs(now - timestamp) > timestampThreshold) {\n _console.log(\"correcting timestamp delta\");\n timestamp += Uint16Max * Math.sign(now - timestamp);\n }\n return timestamp;\n}\n\nexport interface Vector2 {\n x: number;\n y: number;\n}\n\nexport interface Vector3 extends Vector2 {\n z: number;\n}\n\nexport interface Quaternion {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\nexport interface Euler {\n heading: number;\n pitch: number;\n roll: number;\n}\n\nexport function computeVoronoiWeights(points: PressureSensorPosition[], sampleCount = 100000) {\n const n = points.length;\n const counts = new Array(n).fill(0);\n\n for (let i = 0; i < sampleCount; i++) {\n const x = Math.random();\n const y = Math.random();\n\n // Find the closest input point\n let minDist = Infinity;\n let closestIndex = -1;\n\n for (let j = 0; j < n; j++) {\n const { x: px, y: py } = points[j];\n const dist = (px - x) ** 2 + (py - y) ** 2; // Squared Euclidean distance\n if (dist < minDist) {\n minDist = dist;\n closestIndex = j;\n }\n }\n\n // Increment count for the closest point\n counts[closestIndex]++;\n }\n\n // Convert counts to weights (sum to 1)\n return counts.map((c) => c / sampleCount);\n}\n","import { getInterpolation } from \"./MathUtils.ts\";\n\ninterface Range {\n min: number;\n max: number;\n span: number;\n}\n\nconst initialRange: Range = { min: Infinity, max: -Infinity, span: 0 };\n\nclass RangeHelper {\n #range: Range = Object.assign({}, initialRange);\n get min() {\n return this.#range.min;\n }\n get max() {\n return this.#range.max;\n }\n\n set min(newMin) {\n this.#range.min = newMin;\n this.#range.max = Math.max(newMin, this.#range.max);\n this.#updateSpan();\n }\n set max(newMax) {\n this.#range.max = newMax;\n this.#range.min = Math.min(newMax, this.#range.min);\n this.#updateSpan();\n }\n\n #updateSpan() {\n this.#range.span = this.#range.max - this.#range.min;\n }\n\n reset() {\n Object.assign(this.#range, initialRange);\n }\n\n update(value: number) {\n this.#range.min = Math.min(value, this.#range.min);\n this.#range.max = Math.max(value, this.#range.max);\n this.#updateSpan();\n }\n\n getNormalization(value: number, weightByRange: boolean) {\n let normalization = getInterpolation(value, this.#range.min, this.#range.max, this.#range.span);\n if (weightByRange) {\n normalization *= this.#range.span;\n }\n return normalization || 0;\n }\n\n updateAndGetNormalization(value: number, weightByRange: boolean) {\n this.update(value);\n return this.getNormalization(value, weightByRange);\n }\n}\n\nexport default RangeHelper;\n","import RangeHelper from \"./RangeHelper.ts\";\n\nimport { Vector2 } from \"./MathUtils.ts\";\n\nexport type CenterOfPressure = Vector2;\n\nexport interface CenterOfPressureRange {\n x: RangeHelper;\n y: RangeHelper;\n}\n\nclass CenterOfPressureHelper {\n #range: CenterOfPressureRange = {\n x: new RangeHelper(),\n y: new RangeHelper(),\n };\n reset() {\n this.#range.x.reset();\n this.#range.y.reset();\n }\n\n update(centerOfPressure: CenterOfPressure) {\n this.#range.x.update(centerOfPressure.x);\n this.#range.y.update(centerOfPressure.y);\n }\n getNormalization(centerOfPressure: CenterOfPressure, weightByRange: boolean): CenterOfPressure {\n return {\n x: this.#range.x.getNormalization(centerOfPressure.x, weightByRange),\n y: this.#range.y.getNormalization(centerOfPressure.y, weightByRange),\n };\n }\n\n updateAndGetNormalization(centerOfPressure: CenterOfPressure, weightByRange: boolean) {\n this.update(centerOfPressure);\n return this.getNormalization(centerOfPressure, weightByRange);\n }\n}\n\nexport default CenterOfPressureHelper;\n","export function createArray(arrayLength: number, objectOrCallback: ((index: number) => any) | object) {\n return new Array(arrayLength).fill(1).map((_, index) => {\n if (typeof objectOrCallback == \"function\") {\n const callback = objectOrCallback;\n return callback(index);\n } else {\n const object = objectOrCallback;\n return Object.assign({}, object);\n }\n });\n}\n\nexport function arrayWithoutDuplicates(array: any[]) {\n return array.filter((value, index) => array.indexOf(value) == index);\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport RangeHelper from \"../utils/RangeHelper.ts\";\nimport { createArray } from \"../utils/ArrayUtils.ts\";\n\nconst _console = createConsole(\"PressureDataManager\", { log: false });\n\nexport const PressureSensorTypes = [\"pressure\"] as const;\nexport type PressureSensorType = (typeof PressureSensorTypes)[number];\n\nexport const ContinuousPressureSensorTypes = PressureSensorTypes;\nexport type ContinuousPressureSensorType =\n (typeof ContinuousPressureSensorTypes)[number];\n\nimport { computeVoronoiWeights, Vector2 } from \"../utils/MathUtils.ts\";\nexport type PressureSensorPosition = Vector2;\n\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\n\nexport interface PressureSensorValue {\n position: PressureSensorPosition;\n rawValue: number;\n scaledValue: number;\n normalizedValue: number;\n weightedValue: number;\n}\n\nexport interface PressureData {\n sensors: PressureSensorValue[];\n scaledSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface PressureDataEventMessages {\n pressure: { pressure: PressureData };\n}\n\nexport const DefaultNumberOfPressureSensors = 8;\n\nclass PressureSensorDataManager {\n #positions: PressureSensorPosition[] = [];\n get positions() {\n return this.#positions;\n }\n\n get numberOfSensors() {\n return this.positions.length;\n }\n\n parsePositions(dataView: DataView) {\n const positions: PressureSensorPosition[] = [];\n\n for (\n let pressureSensorIndex = 0, byteOffset = 0;\n byteOffset < dataView.byteLength;\n pressureSensorIndex++, byteOffset += 2\n ) {\n positions.push({\n x: dataView.getUint8(byteOffset) / 2 ** 8,\n y: dataView.getUint8(byteOffset + 1) / 2 ** 8,\n });\n }\n\n _console.log({ positions });\n\n this.#positions = positions;\n\n this.#sensorRangeHelpers = createArray(\n this.numberOfSensors,\n () => new RangeHelper()\n );\n\n this.resetRange();\n }\n\n #sensorRangeHelpers!: RangeHelper[];\n #normalizedSumRangeHelper = new RangeHelper();\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetRange() {\n this.#sensorRangeHelpers?.forEach((rangeHelper) => rangeHelper.reset());\n this.#centerOfPressureHelper.reset();\n this.#normalizedSumRangeHelper.reset();\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure: PressureData = {\n sensors: [],\n scaledSum: 0,\n normalizedSum: 0,\n };\n for (\n let index = 0, byteOffset = 0;\n byteOffset < dataView.byteLength;\n index++, byteOffset += 2\n ) {\n const rawValue = dataView.getUint16(byteOffset, true);\n let scaledValue = (rawValue * scalar) / this.numberOfSensors;\n const rangeHelper = this.#sensorRangeHelpers[index];\n const normalizedValue = rangeHelper.updateAndGetNormalization(\n scaledValue,\n false\n );\n //scaledValue -= rangeHelper.min;\n\n const position = this.positions[index];\n pressure.sensors[index] = {\n rawValue,\n scaledValue,\n normalizedValue,\n position,\n weightedValue: 0,\n };\n\n pressure.scaledSum += scaledValue;\n //pressure.normalizedSum += normalizedValue;\n }\n pressure.normalizedSum =\n this.#normalizedSumRangeHelper.updateAndGetNormalization(\n pressure.scaledSum,\n false\n );\n\n if (pressure.scaledSum > 0) {\n pressure.center = { x: 0, y: 0 };\n pressure.sensors.forEach((sensor) => {\n sensor.weightedValue = sensor.scaledValue / pressure.scaledSum;\n pressure.center!.x += sensor.position.x * sensor.weightedValue;\n pressure.center!.y += sensor.position.y * sensor.weightedValue;\n });\n pressure.normalizedCenter =\n this.#centerOfPressureHelper.updateAndGetNormalization(\n pressure.center,\n false\n );\n }\n\n _console.log({ pressure });\n return pressure;\n }\n}\n\nexport default PressureSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nconst _console = createConsole(\"MotionSensorDataManager\", { log: false });\n\nexport const MotionSensorTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n \"orientation\",\n \"activity\",\n \"stepCounter\",\n \"stepDetector\",\n \"deviceOrientation\",\n \"tapDetector\",\n] as const;\nexport type MotionSensorType = (typeof MotionSensorTypes)[number];\n\nexport const ContinuousMotionTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n \"orientation\",\n] as const;\nexport type ContinuousMotionType = (typeof ContinuousMotionTypes)[number];\n\nimport { Vector3, Quaternion, Euler } from \"../utils/MathUtils.ts\";\nimport { ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nexport const Vector2Size = 2 * 2;\nexport const Vector3Size = 3 * 2;\nexport const QuaternionSize = 4 * 2;\n\nexport const ActivityTypes = [\n \"still\",\n \"walking\",\n \"running\",\n \"bicycle\",\n \"vehicle\",\n \"tilting\",\n] as const;\nexport type ActivityType = (typeof ActivityTypes)[number];\n\nexport interface Activity {\n still: boolean;\n walking: boolean;\n running: boolean;\n bicycle: boolean;\n vehicle: boolean;\n tilting: boolean;\n}\n\nexport const DeviceOrientations = [\n \"portraitUpright\",\n \"landscapeLeft\",\n \"portraitUpsideDown\",\n \"landscapeRight\",\n \"unknown\",\n] as const;\nexport type DeviceOrientation = (typeof DeviceOrientations)[number];\n\nexport interface MotionSensorDataEventMessages {\n acceleration: { acceleration: Vector3 };\n gravity: { gravity: Vector3 };\n linearAcceleration: { linearAcceleration: Vector3 };\n gyroscope: { gyroscope: Vector3 };\n magnetometer: { magnetometer: Vector3 };\n gameRotation: { gameRotation: Quaternion };\n rotation: { rotation: Quaternion };\n orientation: { orientation: Euler };\n stepDetector: { stepDetector: Object };\n stepCounter: { stepCounter: number };\n activity: { activity: Activity };\n deviceOrientation: { deviceOrientation: DeviceOrientation };\n tapDetector: { tapDetector: Object };\n}\n\nexport type MotionSensorDataEventMessage =\n ValueOf<MotionSensorDataEventMessages>;\n\nclass MotionSensorDataManager {\n parseVector3(dataView: DataView, scalar: number): Vector3 {\n let [x, y, z] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n ].map((value) => value * scalar);\n\n const vector: Vector3 = { x, y, z };\n\n _console.log({ vector });\n return vector;\n }\n\n parseQuaternion(dataView: DataView, scalar: number): Quaternion {\n let [x, y, z, w] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n dataView.getInt16(6, true),\n ].map((value) => value * scalar);\n\n const quaternion: Quaternion = { x, y, z, w };\n\n _console.log({ quaternion });\n return quaternion;\n }\n\n parseEuler(dataView: DataView, scalar: number): Euler {\n let [heading, pitch, roll] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n ].map((value) => value * scalar);\n\n pitch *= -1;\n heading *= -1;\n if (heading < 0) {\n heading += 360;\n }\n\n const euler: Euler = { heading, pitch, roll };\n\n _console.log({ euler });\n return euler;\n }\n\n parseStepCounter(dataView: DataView) {\n _console.log(\"parseStepCounter\", dataView);\n const stepCount = dataView.getUint32(0, true);\n _console.log({ stepCount });\n return stepCount;\n }\n\n parseActivity(dataView: DataView) {\n _console.log(\"parseActivity\", dataView);\n const activity: Partial<Activity> = {};\n\n const activityBitfield = dataView.getUint8(0);\n _console.log(\"activityBitfield\", activityBitfield.toString(2));\n ActivityTypes.forEach((activityType, index) => {\n activity[activityType] = Boolean(activityBitfield & (1 << index));\n });\n\n _console.log(\"activity\", activity);\n\n return activity as Activity;\n }\n\n parseDeviceOrientation(dataView: DataView) {\n _console.log(\"parseDeviceOrientation\", dataView);\n const index = dataView.getUint8(0);\n const deviceOrientation = DeviceOrientations[index];\n _console.assertWithError(deviceOrientation, \"undefined deviceOrientation\");\n _console.log({ deviceOrientation });\n return deviceOrientation;\n }\n}\n\nexport default MotionSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nexport const BarometerSensorTypes = [\"barometer\"] as const;\nexport type BarometerSensorType = (typeof BarometerSensorTypes)[number];\n\nexport const ContinuousBarometerSensorTypes = BarometerSensorTypes;\nexport type ContinuousBarometerSensorType = (typeof ContinuousBarometerSensorTypes)[number];\n\nexport interface BarometerSensorDataEventMessages {\n barometer: {\n barometer: number;\n //altitude: number;\n };\n}\n\nconst _console = createConsole(\"BarometerSensorDataManager\", { log: false });\n\nclass BarometerSensorDataManager {\n #calculcateAltitude(pressure: number) {\n const P0 = 101325; // Standard atmospheric pressure at sea level in Pascals\n const T0 = 288.15; // Standard temperature at sea level in Kelvin\n const L = 0.0065; // Temperature lapse rate in K/m\n const R = 8.3144598; // Universal gas constant in J/(mol·K)\n const g = 9.80665; // Acceleration due to gravity in m/s²\n const M = 0.0289644; // Molar mass of Earth's air in kg/mol\n\n const exponent = (R * L) / (g * M);\n const h = (T0 / L) * (1 - Math.pow(pressure / P0, exponent));\n\n return h;\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure = dataView.getUint32(0, true) * scalar;\n const altitude = this.#calculcateAltitude(pressure);\n _console.log({ pressure, altitude });\n return { pressure };\n }\n}\n\nexport default BarometerSensorDataManager;\n","import { sliceDataView } from \"./ArrayBufferUtils.ts\";\nimport { createConsole } from \"./Console.ts\";\nimport { textDecoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ParseUtils\", { log: false });\n\nexport function parseStringFromDataView(\n dataView: DataView,\n byteOffset: number = 0\n) {\n const stringLength = dataView.getUint8(byteOffset++);\n const string = textDecoder.decode(\n dataView.buffer.slice(\n dataView.byteOffset + byteOffset,\n dataView.byteOffset + byteOffset + stringLength\n )\n );\n byteOffset += stringLength;\n return { string, byteOffset };\n}\n\nexport function parseMessage<MessageType extends string>(\n dataView: DataView,\n messageTypes: readonly MessageType[],\n callback: (\n messageType: MessageType,\n dataView: DataView,\n context?: any\n ) => void,\n context?: any,\n parseMessageLengthAsUint16: boolean = false\n) {\n let byteOffset = 0;\n while (byteOffset < dataView.byteLength) {\n const messageTypeEnum = dataView.getUint8(byteOffset++);\n _console.assertWithError(\n messageTypeEnum in messageTypes,\n `invalid messageTypeEnum ${messageTypeEnum}`\n );\n const messageType = messageTypes[messageTypeEnum];\n\n let messageLength: number;\n if (parseMessageLengthAsUint16) {\n messageLength = dataView.getUint16(byteOffset, true);\n byteOffset += 2;\n } else {\n messageLength = dataView.getUint8(byteOffset++);\n }\n\n _console.log({\n messageTypeEnum,\n messageType,\n messageLength,\n dataView,\n byteOffset,\n });\n\n const _dataView = sliceDataView(dataView, byteOffset, messageLength);\n _console.log({ _dataView });\n\n callback(messageType, _dataView, context);\n\n byteOffset += messageLength;\n }\n}\n","import Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInNode } from \"./utils/environment.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport autoBind from \"auto-bind\";\nimport { parseMessage } from \"./utils/ParseUtils.ts\";\nimport { concatenateArrayBuffers } from \"./utils/ArrayBufferUtils.ts\";\n\nconst _console = createConsole(\"CameraManager\", { log: false });\n\nexport const CameraSensorTypes = [\"camera\"] as const;\nexport type CameraSensorType = (typeof CameraSensorTypes)[number];\n\nexport const CameraCommands = [\n \"focus\",\n \"takePicture\",\n \"stop\",\n \"sleep\",\n \"wake\",\n] as const;\nexport type CameraCommand = (typeof CameraCommands)[number];\n\nexport const CameraStatuses = [\n \"idle\",\n \"focusing\",\n \"takingPicture\",\n \"asleep\",\n] as const;\nexport type CameraStatus = (typeof CameraStatuses)[number];\n\nexport const CameraDataTypes = [\n \"headerSize\",\n \"header\",\n \"imageSize\",\n \"image\",\n \"footerSize\",\n \"footer\",\n] as const;\nexport type CameraDataType = (typeof CameraDataTypes)[number];\n\nexport const CameraConfigurationTypes = [\n \"resolution\",\n \"qualityFactor\",\n \"shutter\",\n \"gain\",\n \"redGain\",\n \"greenGain\",\n \"blueGain\",\n] as const;\nexport type CameraConfigurationType = (typeof CameraConfigurationTypes)[number];\n\nexport const CameraMessageTypes = [\n \"cameraStatus\",\n \"cameraCommand\",\n \"getCameraConfiguration\",\n \"setCameraConfiguration\",\n \"cameraData\",\n] as const;\nexport type CameraMessageType = (typeof CameraMessageTypes)[number];\n\nexport type CameraConfiguration = {\n [cameraConfigurationType in CameraConfigurationType]?: number;\n};\nexport type CameraConfigurationRanges = {\n [cameraConfigurationType in CameraConfigurationType]: {\n min: number;\n max: number;\n };\n};\n\nexport const RequiredCameraMessageTypes: CameraMessageType[] = [\n \"getCameraConfiguration\",\n \"cameraStatus\",\n] as const;\n\nexport const CameraEventTypes = [\n ...CameraMessageTypes,\n \"cameraImageProgress\",\n \"cameraImage\",\n] as const;\nexport type CameraEventType = (typeof CameraEventTypes)[number];\n\nexport interface CameraEventMessages {\n cameraStatus: {\n cameraStatus: CameraStatus;\n previousCameraStatus: CameraStatus;\n };\n getCameraConfiguration: { cameraConfiguration: CameraConfiguration };\n cameraImageProgress: { progress: number; type: CameraDataType };\n cameraImage: { blob: Blob; url: string };\n}\n\nexport type CameraEventDispatcher = EventDispatcher<\n Device,\n CameraEventType,\n CameraEventMessages\n>;\nexport type SendCameraMessageCallback = SendMessageCallback<CameraMessageType>;\n\nclass CameraManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendCameraMessageCallback;\n\n eventDispatcher!: CameraEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n requestRequiredInformation() {\n _console.log(\"requesting required camera information\");\n const messages = RequiredCameraMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendMessage(messages, false);\n }\n\n // CAMERA STATUS\n #cameraStatus!: CameraStatus;\n get cameraStatus() {\n return this.#cameraStatus;\n }\n #parseCameraStatus(dataView: DataView) {\n const cameraStatusIndex = dataView.getUint8(0);\n const newCameraStatus = CameraStatuses[cameraStatusIndex];\n this.#updateCameraStatus(newCameraStatus);\n }\n #updateCameraStatus(newCameraStatus: CameraStatus) {\n _console.assertEnumWithError(newCameraStatus, CameraStatuses);\n if (newCameraStatus == this.#cameraStatus) {\n _console.log(`redundant cameraStatus ${newCameraStatus}`);\n return;\n }\n const previousCameraStatus = this.#cameraStatus;\n this.#cameraStatus = newCameraStatus;\n _console.log(`updated cameraStatus to \"${this.cameraStatus}\"`);\n this.#dispatchEvent(\"cameraStatus\", {\n cameraStatus: this.cameraStatus,\n previousCameraStatus,\n });\n\n if (\n this.#cameraStatus != \"takingPicture\" &&\n this.#imageProgress > 0 &&\n !this.#didBuildImage\n ) {\n this.#buildImage();\n }\n }\n\n // CAMERA COMMAND\n async #sendCameraCommand(command: CameraCommand, sendImmediately?: boolean) {\n _console.assertEnumWithError(command, CameraCommands);\n _console.log(`sending camera command \"${command}\"`);\n\n const promise = this.waitForEvent(\"cameraStatus\");\n _console.log(`setting command \"${command}\"`);\n const commandEnum = CameraCommands.indexOf(command);\n this.sendMessage(\n [\n {\n type: \"cameraCommand\",\n data: Uint8Array.from([commandEnum]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n #assertIsAsleep() {\n _console.assertWithError(\n this.#cameraStatus == \"asleep\",\n `camera is not asleep - currently ${this.#cameraStatus}`\n );\n }\n #assertIsAwake() {\n _console.assertWithError(\n this.#cameraStatus != \"asleep\",\n `camera is not awake - currently ${this.#cameraStatus}`\n );\n }\n async focus() {\n this.#assertIsAwake();\n await this.#sendCameraCommand(\"focus\");\n }\n async takePicture() {\n this.#assertIsAwake();\n await this.#sendCameraCommand(\"takePicture\");\n }\n async stop() {\n this.#assertIsAwake();\n await this.#sendCameraCommand(\"stop\");\n }\n async sleep() {\n this.#assertIsAwake();\n await this.#sendCameraCommand(\"sleep\");\n }\n async wake() {\n this.#assertIsAsleep();\n await this.#sendCameraCommand(\"wake\");\n }\n\n // CAMERA DATA\n #parseCameraData(dataView: DataView) {\n _console.log(\"parsing camera data\", dataView);\n parseMessage(\n dataView,\n CameraDataTypes,\n this.#onCameraData.bind(this),\n null,\n true\n );\n }\n #onCameraData(cameraDataType: CameraDataType, dataView: DataView) {\n _console.log({ cameraDataType, dataView });\n switch (cameraDataType) {\n case \"headerSize\":\n this.#headerSize = dataView.getUint16(0, true);\n _console.log({ headerSize: this.#headerSize });\n this.#headerData = undefined;\n this.#headerProgress == 0;\n break;\n case \"header\":\n this.#headerData = concatenateArrayBuffers(this.#headerData, dataView);\n _console.log({ headerData: this.#headerData });\n this.#headerProgress = this.#headerData?.byteLength / this.#headerSize;\n _console.log({ headerProgress: this.#headerProgress });\n this.#dispatchEvent(\"cameraImageProgress\", {\n progress: this.#headerProgress,\n type: \"header\",\n });\n if (this.#headerProgress == 1) {\n _console.log(\"finished getting header data\");\n }\n break;\n case \"imageSize\":\n this.#imageSize = dataView.getUint16(0, true);\n _console.log({ imageSize: this.#imageSize });\n this.#imageData = undefined;\n this.#imageProgress == 0;\n this.#didBuildImage = false;\n break;\n case \"image\":\n this.#imageData = concatenateArrayBuffers(this.#imageData, dataView);\n _console.log({ imageData: this.#imageData });\n this.#imageProgress = this.#imageData?.byteLength / this.#imageSize;\n _console.log({ imageProgress: this.#imageProgress });\n if (this.#imageProgress == 1) {\n _console.log(\"finished getting image data\");\n if (this.#headerProgress == 1) {\n this.#buildImage();\n }\n }\n this.#dispatchEvent(\"cameraImageProgress\", {\n progress: this.#imageProgress,\n type: \"image\",\n });\n break;\n case \"footerSize\":\n this.#footerSize = dataView.getUint16(0, true);\n _console.log({ footerSize: this.#footerSize });\n this.#footerData = undefined;\n this.#footerProgress == 0;\n break;\n case \"footer\":\n this.#footerData = concatenateArrayBuffers(this.#footerData, dataView);\n _console.log({ footerData: this.#footerData });\n this.#footerProgress = this.#footerData?.byteLength / this.#footerSize;\n _console.log({ footerProgress: this.#footerProgress });\n this.#dispatchEvent(\"cameraImageProgress\", {\n progress: this.#footerProgress,\n type: \"footer\",\n });\n if (this.#footerProgress == 1) {\n _console.log(\"finished getting footer data\");\n if (this.#imageProgress == 1) {\n this.#buildImage();\n }\n }\n break;\n }\n }\n\n #headerSize: number = 0;\n #headerData?: ArrayBuffer;\n #headerProgress: number = 0;\n\n #imageSize: number = 0;\n #imageData?: ArrayBuffer;\n #imageProgress: number = 0;\n\n #footerSize: number = 0;\n #footerData?: ArrayBuffer;\n #footerProgress: number = 0;\n\n #didBuildImage: boolean = false;\n #buildImage() {\n _console.log(\"building image...\");\n const imageData = concatenateArrayBuffers(\n this.#headerData,\n this.#imageData,\n this.#footerData\n );\n _console.log({ imageData });\n\n let blob = new Blob([imageData], { type: \"image/jpeg\" });\n _console.log(\"created blob\", blob);\n\n const url = URL.createObjectURL(blob);\n _console.log(\"created url\", url);\n\n // FILL - header stuff\n\n this.#dispatchEvent(\"cameraImage\", { url, blob });\n\n this.#didBuildImage = true;\n }\n\n // CONFIG\n #cameraConfiguration: CameraConfiguration = {};\n get cameraConfiguration() {\n return this.#cameraConfiguration;\n }\n #availableCameraConfigurationTypes!: CameraConfigurationType[];\n get availableCameraConfigurationTypes() {\n return this.#availableCameraConfigurationTypes;\n }\n\n #cameraConfigurationRanges: CameraConfigurationRanges = {\n resolution: { min: 100, max: 720 },\n qualityFactor: { min: 15, max: 60 },\n shutter: { min: 4, max: 16383 },\n gain: { min: 1, max: 248 },\n redGain: { min: 0, max: 1023 },\n greenGain: { min: 0, max: 1023 },\n blueGain: { min: 0, max: 1023 },\n };\n get cameraConfigurationRanges() {\n return this.#cameraConfigurationRanges;\n }\n\n #parseCameraConfiguration(dataView: DataView) {\n const parsedCameraConfiguration: CameraConfiguration = {};\n\n let byteOffset = 0;\n while (byteOffset < dataView.byteLength) {\n const cameraConfigurationTypeIndex = dataView.getUint8(byteOffset++);\n const cameraConfigurationType =\n CameraConfigurationTypes[cameraConfigurationTypeIndex];\n _console.assertWithError(\n cameraConfigurationType,\n `invalid cameraConfigurationTypeIndex ${cameraConfigurationTypeIndex}`\n );\n parsedCameraConfiguration[cameraConfigurationType] = dataView.getUint16(\n byteOffset,\n true\n );\n byteOffset += 2;\n }\n\n _console.log({ parsedCameraConfiguration });\n this.#availableCameraConfigurationTypes = Object.keys(\n parsedCameraConfiguration\n ) as CameraConfigurationType[];\n this.#cameraConfiguration = parsedCameraConfiguration;\n this.#dispatchEvent(\"getCameraConfiguration\", {\n cameraConfiguration: this.#cameraConfiguration,\n });\n }\n\n #isCameraConfigurationRedundant(cameraConfiguration: CameraConfiguration) {\n let cameraConfigurationTypes = Object.keys(\n cameraConfiguration\n ) as CameraConfigurationType[];\n return cameraConfigurationTypes.every((cameraConfigurationType) => {\n return (\n this.cameraConfiguration[cameraConfigurationType] ==\n cameraConfiguration[cameraConfigurationType]\n );\n });\n }\n async setCameraConfiguration(newCameraConfiguration: CameraConfiguration) {\n _console.log({ newCameraConfiguration });\n if (this.#isCameraConfigurationRedundant(newCameraConfiguration)) {\n _console.log(\"redundant camera configuration\");\n return;\n }\n const setCameraConfigurationData = this.#createData(newCameraConfiguration);\n _console.log({ setCameraConfigurationData });\n\n const promise = this.waitForEvent(\"getCameraConfiguration\");\n this.sendMessage([\n {\n type: \"setCameraConfiguration\",\n data: setCameraConfigurationData.buffer,\n },\n ]);\n await promise;\n }\n\n #assertAvailableCameraConfigurationType(\n cameraConfigurationType: CameraConfigurationType\n ) {\n _console.assertWithError(\n this.#availableCameraConfigurationTypes,\n \"must get initial cameraConfiguration\"\n );\n const isCameraConfigurationTypeAvailable =\n this.#availableCameraConfigurationTypes?.includes(\n cameraConfigurationType\n );\n _console.assertWithError(\n isCameraConfigurationTypeAvailable,\n `unavailable camera configuration type \"${cameraConfigurationType}\"`\n );\n return isCameraConfigurationTypeAvailable;\n }\n\n static AssertValidCameraConfigurationType(\n cameraConfigurationType: CameraConfigurationType\n ) {\n _console.assertEnumWithError(\n cameraConfigurationType,\n CameraConfigurationTypes\n );\n }\n static AssertValidCameraConfigurationTypeEnum(\n cameraConfigurationTypeEnum: number\n ) {\n _console.assertTypeWithError(cameraConfigurationTypeEnum, \"number\");\n _console.assertWithError(\n cameraConfigurationTypeEnum in CameraConfigurationTypes,\n `invalid cameraConfigurationTypeEnum ${cameraConfigurationTypeEnum}`\n );\n }\n\n #createData(cameraConfiguration: CameraConfiguration) {\n let cameraConfigurationTypes = Object.keys(\n cameraConfiguration\n ) as CameraConfigurationType[];\n cameraConfigurationTypes = cameraConfigurationTypes.filter(\n (cameraConfigurationType) =>\n this.#assertAvailableCameraConfigurationType(cameraConfigurationType)\n );\n\n const dataView = new DataView(\n new ArrayBuffer(cameraConfigurationTypes.length * 3)\n );\n cameraConfigurationTypes.forEach((cameraConfigurationType, index) => {\n CameraManager.AssertValidCameraConfigurationType(cameraConfigurationType);\n const cameraConfigurationTypeEnum = CameraConfigurationTypes.indexOf(\n cameraConfigurationType\n );\n dataView.setUint8(index * 3, cameraConfigurationTypeEnum);\n\n const value = cameraConfiguration[cameraConfigurationType]!;\n //this.#assertValidCameraConfigurationValue(cameraConfigurationType, value);\n dataView.setUint16(index * 3 + 1, value, true);\n });\n _console.log({ sensorConfigurationData: dataView });\n return dataView;\n }\n\n // MESSAGE\n parseMessage(messageType: CameraMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n\n switch (messageType) {\n case \"cameraStatus\":\n this.#parseCameraStatus(dataView);\n break;\n case \"getCameraConfiguration\":\n case \"setCameraConfiguration\":\n this.#parseCameraConfiguration(dataView);\n break;\n case \"cameraData\":\n this.#parseCameraData(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n // @ts-ignore\n this.#cameraStatus = undefined;\n this.#headerProgress = 0;\n this.#imageProgress = 0;\n this.#footerProgress = 0;\n }\n}\n\nexport default CameraManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport { parseTimestamp } from \"../utils/MathUtils.ts\";\nimport PressureSensorDataManager, {\n PressureDataEventMessages,\n} from \"./PressureSensorDataManager.ts\";\nimport MotionSensorDataManager, {\n MotionSensorDataEventMessages,\n} from \"./MotionSensorDataManager.ts\";\nimport BarometerSensorDataManager, {\n BarometerSensorDataEventMessages,\n} from \"./BarometerSensorDataManager.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport {\n MotionSensorTypes,\n ContinuousMotionTypes,\n} from \"./MotionSensorDataManager.ts\";\nimport {\n PressureSensorTypes,\n ContinuousPressureSensorTypes,\n} from \"./PressureSensorDataManager.ts\";\nimport {\n BarometerSensorTypes,\n ContinuousBarometerSensorTypes,\n} from \"./BarometerSensorDataManager.ts\";\nimport Device from \"../Device.ts\";\nimport {\n AddKeysAsPropertyToInterface,\n ExtendInterfaceValues,\n ValueOf,\n} from \"../utils/TypeScriptUtils.ts\";\nimport { CameraSensorTypes } from \"../CameraManager.ts\";\n\nconst _console = createConsole(\"SensorDataManager\", { log: false });\n\nexport const SensorTypes = [\n ...PressureSensorTypes,\n ...MotionSensorTypes,\n ...BarometerSensorTypes,\n ...CameraSensorTypes,\n] as const;\nexport type SensorType = (typeof SensorTypes)[number];\n\nexport const ContinuousSensorTypes = [\n ...ContinuousPressureSensorTypes,\n ...ContinuousMotionTypes,\n ...ContinuousBarometerSensorTypes,\n] as const;\nexport type ContinuousSensorType = (typeof ContinuousSensorTypes)[number];\n\nexport const SensorDataMessageTypes = [\n \"getPressurePositions\",\n \"getSensorScalars\",\n \"sensorData\",\n] as const;\nexport type SensorDataMessageType = (typeof SensorDataMessageTypes)[number];\n\nexport const RequiredPressureMessageTypes: SensorDataMessageType[] = [\n \"getPressurePositions\",\n] as const;\n\nexport const SensorDataEventTypes = [\n ...SensorDataMessageTypes,\n ...SensorTypes,\n] as const;\nexport type SensorDataEventType = (typeof SensorDataEventTypes)[number];\n\ninterface BaseSensorDataEventMessage {\n timestamp: number;\n}\n\ntype BaseSensorDataEventMessages = BarometerSensorDataEventMessages &\n MotionSensorDataEventMessages &\n PressureDataEventMessages;\ntype _SensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseSensorDataEventMessages, \"sensorType\">,\n BaseSensorDataEventMessage\n>;\nexport type SensorDataEventMessage = ValueOf<_SensorDataEventMessages>;\ninterface AnySensorDataEventMessages {\n sensorData: SensorDataEventMessage;\n}\nexport type SensorDataEventMessages = _SensorDataEventMessages &\n AnySensorDataEventMessages;\n\nexport type SensorDataEventDispatcher = EventDispatcher<\n Device,\n SensorDataEventType,\n SensorDataEventMessages\n>;\n\nclass SensorDataManager {\n pressureSensorDataManager = new PressureSensorDataManager();\n motionSensorDataManager = new MotionSensorDataManager();\n barometerSensorDataManager = new BarometerSensorDataManager();\n\n #scalars: Map<SensorType, number> = new Map();\n\n static AssertValidSensorType(sensorType: SensorType) {\n _console.assertEnumWithError(sensorType, SensorTypes);\n }\n static AssertValidSensorTypeEnum(sensorTypeEnum: number) {\n _console.assertTypeWithError(sensorTypeEnum, \"number\");\n _console.assertWithError(\n sensorTypeEnum in SensorTypes,\n `invalid sensorTypeEnum ${sensorTypeEnum}`\n );\n }\n\n eventDispatcher!: SensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n parseMessage(messageType: SensorDataMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorScalars\":\n this.parseScalars(dataView);\n break;\n case \"getPressurePositions\":\n this.pressureSensorDataManager.parsePositions(dataView);\n break;\n case \"sensorData\":\n this.parseData(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n parseScalars(dataView: DataView) {\n for (\n let byteOffset = 0;\n byteOffset < dataView.byteLength;\n byteOffset += 5\n ) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorScalar = dataView.getFloat32(byteOffset + 1, true);\n _console.log({ sensorType, sensorScalar });\n this.#scalars.set(sensorType, sensorScalar);\n }\n }\n\n private parseData(dataView: DataView) {\n _console.log(\"sensorData\", Array.from(new Uint8Array(dataView.buffer)));\n\n let byteOffset = 0;\n const timestamp = parseTimestamp(dataView, byteOffset);\n byteOffset += 2;\n\n const _dataView = new DataView(dataView.buffer, byteOffset);\n\n parseMessage(_dataView, SensorTypes, this.parseDataCallback.bind(this), {\n timestamp,\n });\n }\n\n private parseDataCallback(\n sensorType: SensorType,\n dataView: DataView,\n { timestamp }: { timestamp: number }\n ) {\n const scalar = this.#scalars.get(sensorType) || 1;\n\n let sensorData = null;\n switch (sensorType) {\n case \"pressure\":\n sensorData = this.pressureSensorDataManager.parseData(dataView, scalar);\n break;\n case \"acceleration\":\n case \"gravity\":\n case \"linearAcceleration\":\n case \"gyroscope\":\n case \"magnetometer\":\n sensorData = this.motionSensorDataManager.parseVector3(\n dataView,\n scalar\n );\n break;\n case \"gameRotation\":\n case \"rotation\":\n sensorData = this.motionSensorDataManager.parseQuaternion(\n dataView,\n scalar\n );\n break;\n case \"orientation\":\n sensorData = this.motionSensorDataManager.parseEuler(dataView, scalar);\n break;\n case \"stepCounter\":\n sensorData = this.motionSensorDataManager.parseStepCounter(dataView);\n break;\n case \"stepDetector\":\n sensorData = {};\n break;\n case \"activity\":\n sensorData = this.motionSensorDataManager.parseActivity(dataView);\n break;\n case \"deviceOrientation\":\n sensorData =\n this.motionSensorDataManager.parseDeviceOrientation(dataView);\n break;\n case \"tapDetector\":\n sensorData = {};\n break;\n case \"barometer\":\n sensorData = this.barometerSensorDataManager.parseData(\n dataView,\n scalar\n );\n break;\n case \"camera\":\n // we parse camera data using CameraManager\n return;\n default:\n _console.error(`uncaught sensorType \"${sensorType}\"`);\n }\n\n _console.assertWithError(\n sensorData != null,\n `no sensorData defined for sensorType \"${sensorType}\"`\n );\n\n _console.log({ sensorType, sensorData });\n // @ts-expect-error\n this.dispatchEvent(sensorType, {\n sensorType,\n [sensorType]: sensorData,\n timestamp,\n });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", {\n sensorType,\n [sensorType]: sensorData,\n timestamp,\n });\n }\n}\n\nexport default SensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport SensorDataManager, {\n SensorTypes,\n SensorType,\n} from \"./SensorDataManager.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport Device, { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"../../node_modules/auto-bind/index.js\";\n\nconst _console = createConsole(\"SensorConfigurationManager\", { log: false });\n\nexport type SensorConfiguration = { [sensorType in SensorType]?: number };\n\nexport const MaxSensorRate = 2 ** 16 - 1;\nexport const SensorRateStep = 5;\n\nexport const SensorConfigurationMessageTypes = [\n \"getSensorConfiguration\",\n \"setSensorConfiguration\",\n] as const;\nexport type SensorConfigurationMessageType =\n (typeof SensorConfigurationMessageTypes)[number];\n\nexport const SensorConfigurationEventTypes = SensorConfigurationMessageTypes;\nexport type SensorConfigurationEventType =\n (typeof SensorConfigurationEventTypes)[number];\n\nexport interface SensorConfigurationEventMessages {\n getSensorConfiguration: { sensorConfiguration: SensorConfiguration };\n}\n\nexport type SensorConfigurationEventDispatcher = EventDispatcher<\n Device,\n SensorConfigurationEventType,\n SensorConfigurationEventMessages\n>;\n\nexport type SendSensorConfigurationMessageCallback =\n SendMessageCallback<SensorConfigurationMessageType>;\n\nclass SensorConfigurationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendSensorConfigurationMessageCallback;\n\n eventDispatcher!: SensorConfigurationEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #availableSensorTypes!: SensorType[];\n #assertAvailableSensorType(sensorType: SensorType) {\n _console.assertWithError(\n this.#availableSensorTypes,\n \"must get initial sensorConfiguration\"\n );\n const isSensorTypeAvailable =\n this.#availableSensorTypes?.includes(sensorType);\n _console.log(\n isSensorTypeAvailable,\n `unavailable sensor type \"${sensorType}\"`\n );\n return isSensorTypeAvailable;\n }\n\n #configuration: SensorConfiguration = {};\n get configuration() {\n return this.#configuration;\n }\n\n #updateConfiguration(updatedConfiguration: SensorConfiguration) {\n this.#configuration = updatedConfiguration;\n _console.log({ updatedConfiguration: this.#configuration });\n this.#dispatchEvent(\"getSensorConfiguration\", {\n sensorConfiguration: this.configuration,\n });\n }\n\n #isRedundant(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n return sensorTypes.every((sensorType) => {\n return this.configuration[sensorType] == sensorConfiguration[sensorType];\n });\n }\n\n async setConfiguration(\n newSensorConfiguration: SensorConfiguration,\n clearRest?: boolean\n ) {\n if (clearRest) {\n newSensorConfiguration = Object.assign(\n { ...this.zeroSensorConfiguration },\n newSensorConfiguration\n );\n }\n _console.log({ newSensorConfiguration });\n if (this.#isRedundant(newSensorConfiguration)) {\n _console.log(\"redundant sensor configuration\");\n return;\n }\n const setSensorConfigurationData = this.#createData(newSensorConfiguration);\n _console.log({ setSensorConfigurationData });\n\n const promise = this.waitForEvent(\"getSensorConfiguration\");\n this.sendMessage([\n {\n type: \"setSensorConfiguration\",\n data: setSensorConfigurationData.buffer,\n },\n ]);\n await promise;\n }\n\n #parse(dataView: DataView) {\n const parsedSensorConfiguration: SensorConfiguration = {};\n for (\n let byteOffset = 0;\n byteOffset < dataView.byteLength;\n byteOffset += 3\n ) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n\n const sensorRate = dataView.getUint16(byteOffset + 1, true);\n _console.log({ sensorType, sensorRate });\n\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n parsedSensorConfiguration[sensorType] = sensorRate;\n }\n _console.log({ parsedSensorConfiguration });\n this.#availableSensorTypes = Object.keys(\n parsedSensorConfiguration\n ) as SensorType[];\n return parsedSensorConfiguration;\n }\n\n static #AssertValidSensorRate(sensorRate: number) {\n _console.assertTypeWithError(sensorRate, \"number\");\n _console.assertWithError(\n sensorRate >= 0,\n `sensorRate must be 0 or greater (got ${sensorRate})`\n );\n _console.assertWithError(\n sensorRate < MaxSensorRate,\n `sensorRate must be 0 or greater (got ${sensorRate})`\n );\n _console.assertWithError(\n sensorRate % SensorRateStep == 0,\n `sensorRate must be multiple of ${SensorRateStep}`\n );\n }\n\n #assertValidSensorRate(sensorRate: number) {\n SensorConfigurationManager.#AssertValidSensorRate(sensorRate);\n }\n\n #createData(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n sensorTypes = sensorTypes.filter((sensorType) =>\n this.#assertAvailableSensorType(sensorType)\n );\n\n const dataView = new DataView(new ArrayBuffer(sensorTypes.length * 3));\n sensorTypes.forEach((sensorType, index) => {\n SensorDataManager.AssertValidSensorType(sensorType);\n const sensorTypeEnum = SensorTypes.indexOf(sensorType);\n dataView.setUint8(index * 3, sensorTypeEnum);\n\n const sensorRate = sensorConfiguration[sensorType]!;\n this.#assertValidSensorRate(sensorRate);\n dataView.setUint16(index * 3 + 1, sensorRate, true);\n });\n _console.log({ sensorConfigurationData: dataView });\n return dataView;\n }\n\n // ZERO\n static #ZeroSensorConfiguration: SensorConfiguration = {};\n static get ZeroSensorConfiguration() {\n return this.#ZeroSensorConfiguration;\n }\n static {\n SensorTypes.forEach((sensorType) => {\n this.#ZeroSensorConfiguration[sensorType] = 0;\n });\n }\n get zeroSensorConfiguration() {\n const zeroSensorConfiguration: SensorConfiguration = {};\n this.#availableSensorTypes.forEach((sensorType) => {\n zeroSensorConfiguration[sensorType] = 0;\n });\n return zeroSensorConfiguration;\n }\n async clearSensorConfiguration() {\n return this.setConfiguration(this.zeroSensorConfiguration);\n }\n\n // MESSAGE\n parseMessage(\n messageType: SensorConfigurationMessageType,\n dataView: DataView\n ) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorConfiguration\":\n case \"setSensorConfiguration\":\n const newSensorConfiguration = this.#parse(dataView);\n this.#updateConfiguration(newSensorConfiguration);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default SensorConfigurationManager;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport SensorDataManager, { SensorTypes } from \"./sensor/SensorDataManager.ts\";\nimport { arrayWithoutDuplicates } from \"./utils/ArrayUtils.ts\";\nimport { SensorRateStep } from \"./sensor/SensorConfigurationManager.ts\";\nimport { parseTimestamp } from \"./utils/MathUtils.ts\";\nimport { SensorType } from \"./sensor/SensorDataManager.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport autoBind from \"auto-bind\";\nimport { FileConfiguration as BaseFileConfiguration } from \"./FileTransferManager.ts\";\n\nconst _console = createConsole(\"TfliteManager\", { log: false });\n\nexport const TfliteMessageTypes = [\n \"getTfliteName\",\n \"setTfliteName\",\n \"getTfliteTask\",\n \"setTfliteTask\",\n \"getTfliteSampleRate\",\n \"setTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"setTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"setTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"setTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n \"setTfliteInferencingEnabled\",\n \"tfliteInference\",\n] as const;\nexport type TfliteMessageType = (typeof TfliteMessageTypes)[number];\n\nexport const TfliteEventTypes = TfliteMessageTypes;\nexport type TfliteEventType = (typeof TfliteEventTypes)[number];\n\nexport const RequiredTfliteMessageTypes: TfliteMessageType[] = [\n \"getTfliteName\",\n \"getTfliteTask\",\n \"getTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n];\n\nexport const TfliteTasks = [\"classification\", \"regression\"] as const;\nexport type TfliteTask = (typeof TfliteTasks)[number];\n\nexport interface TfliteEventMessages {\n getTfliteName: { tfliteName: string };\n getTfliteTask: { tfliteTask: TfliteTask };\n getTfliteSampleRate: { tfliteSampleRate: number };\n getTfliteSensorTypes: { tfliteSensorTypes: SensorType[] };\n tfliteIsReady: { tfliteIsReady: boolean };\n getTfliteCaptureDelay: { tfliteCaptureDelay: number };\n getTfliteThreshold: { tfliteThreshold: number };\n getTfliteInferencingEnabled: { tfliteInferencingEnabled: boolean };\n tfliteInference: { tfliteInference: TfliteInference };\n}\n\nexport interface TfliteInference {\n timestamp: number;\n values: number[];\n maxValue?: number;\n maxIndex?: number;\n maxClass?: string;\n classValues?: { [key: string]: number };\n}\n\nexport type TfliteEventDispatcher = EventDispatcher<\n Device,\n TfliteEventType,\n TfliteEventMessages\n>;\nexport type SendTfliteMessageCallback = SendMessageCallback<TfliteMessageType>;\n\nexport const TfliteSensorTypes = [\n \"pressure\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n] as const satisfies readonly SensorType[];\nexport type TfliteSensorType = (typeof TfliteSensorTypes)[number];\n\nexport interface TfliteFileConfiguration extends BaseFileConfiguration {\n type: \"tflite\";\n name: string;\n sensorTypes: TfliteSensorType[];\n task: TfliteTask;\n sampleRate: number;\n captureDelay?: number;\n threshold?: number;\n classes?: string[];\n}\n\nclass TfliteManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendTfliteMessageCallback;\n\n #assertValidTask(task: TfliteTask) {\n _console.assertEnumWithError(task, TfliteTasks);\n }\n #assertValidTaskEnum(taskEnum: number) {\n _console.assertWithError(\n taskEnum in TfliteTasks,\n `invalid taskEnum ${taskEnum}`\n );\n }\n\n eventDispatcher!: TfliteEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #name!: string;\n get name() {\n return this.#name;\n }\n #parseName(dataView: DataView) {\n _console.log(\"parseName\", dataView);\n const name = textDecoder.decode(dataView.buffer);\n this.#updateName(name);\n }\n #updateName(name: string) {\n _console.log({ name });\n this.#name = name;\n this.#dispatchEvent(\"getTfliteName\", { tfliteName: name });\n }\n async setName(newName: string, sendImmediately?: boolean) {\n _console.assertTypeWithError(newName, \"string\");\n if (this.name == newName) {\n _console.log(`redundant name assignment ${newName}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteName\");\n\n const setNameData = textEncoder.encode(newName);\n this.sendMessage(\n [{ type: \"setTfliteName\", data: setNameData.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #task!: TfliteTask;\n get task() {\n return this.#task;\n }\n #parseTask(dataView: DataView) {\n _console.log(\"parseTask\", dataView);\n const taskEnum = dataView.getUint8(0);\n this.#assertValidTaskEnum(taskEnum);\n const task = TfliteTasks[taskEnum];\n this.#updateTask(task);\n }\n #updateTask(task: TfliteTask) {\n _console.log({ task });\n this.#task = task;\n this.#dispatchEvent(\"getTfliteTask\", { tfliteTask: task });\n }\n async setTask(newTask: TfliteTask, sendImmediately?: boolean) {\n this.#assertValidTask(newTask);\n if (this.task == newTask) {\n _console.log(`redundant task assignment ${newTask}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteTask\");\n\n const taskEnum = TfliteTasks.indexOf(newTask);\n this.sendMessage(\n [{ type: \"setTfliteTask\", data: Uint8Array.from([taskEnum]).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #sampleRate!: number;\n get sampleRate() {\n return this.#sampleRate;\n }\n #parseSampleRate(dataView: DataView) {\n _console.log(\"parseSampleRate\", dataView);\n const sampleRate = dataView.getUint16(0, true);\n this.#updateSampleRate(sampleRate);\n }\n #updateSampleRate(sampleRate: number) {\n _console.log({ sampleRate });\n this.#sampleRate = sampleRate;\n this.#dispatchEvent(\"getTfliteSampleRate\", {\n tfliteSampleRate: sampleRate,\n });\n }\n async setSampleRate(newSampleRate: number, sendImmediately?: boolean) {\n _console.assertTypeWithError(newSampleRate, \"number\");\n newSampleRate -= newSampleRate % SensorRateStep;\n _console.assertWithError(\n newSampleRate >= SensorRateStep,\n `sampleRate must be multiple of ${SensorRateStep} greater than 0 (got ${newSampleRate})`\n );\n if (this.#sampleRate == newSampleRate) {\n _console.log(`redundant sampleRate assignment ${newSampleRate}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteSampleRate\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newSampleRate, true);\n this.sendMessage(\n [{ type: \"setTfliteSampleRate\", data: dataView.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n static AssertValidSensorType(sensorType: SensorType) {\n SensorDataManager.AssertValidSensorType(sensorType);\n const tfliteSensorType = sensorType as TfliteSensorType;\n _console.assertWithError(\n TfliteSensorTypes.includes(tfliteSensorType),\n `invalid tflite sensorType \"${sensorType}\"`\n );\n }\n\n #sensorTypes: TfliteSensorType[] = [];\n get sensorTypes() {\n return this.#sensorTypes.slice();\n }\n #parseSensorTypes(dataView: DataView) {\n _console.log(\"parseSensorTypes\", dataView);\n const sensorTypes: TfliteSensorType[] = [];\n for (let index = 0; index < dataView.byteLength; index++) {\n const sensorTypeEnum = dataView.getUint8(index);\n const sensorType = SensorTypes[sensorTypeEnum] as TfliteSensorType;\n if (sensorType) {\n if (TfliteSensorTypes.includes(sensorType)) {\n sensorTypes.push(sensorType);\n } else {\n _console.error(`invalid tfliteSensorType ${sensorType}`);\n }\n } else {\n _console.error(`invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n }\n this.#updateSensorTypes(sensorTypes);\n }\n #updateSensorTypes(sensorTypes: TfliteSensorType[]) {\n _console.log({ sensorTypes });\n this.#sensorTypes = sensorTypes;\n this.#dispatchEvent(\"getTfliteSensorTypes\", {\n tfliteSensorTypes: sensorTypes,\n });\n }\n async setSensorTypes(\n newSensorTypes: SensorType[],\n sendImmediately?: boolean\n ) {\n newSensorTypes.forEach((sensorType) => {\n TfliteManager.AssertValidSensorType(sensorType);\n });\n\n const promise = this.waitForEvent(\"getTfliteSensorTypes\");\n\n newSensorTypes = arrayWithoutDuplicates(newSensorTypes);\n const newSensorTypeEnums = newSensorTypes\n .map((sensorType) => SensorTypes.indexOf(sensorType))\n .sort();\n _console.log(newSensorTypes, newSensorTypeEnums);\n this.sendMessage(\n [\n {\n type: \"setTfliteSensorTypes\",\n data: Uint8Array.from(newSensorTypeEnums).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n\n #isReady!: boolean;\n get isReady() {\n return this.#isReady;\n }\n #parseIsReady(dataView: DataView) {\n _console.log(\"parseIsReady\", dataView);\n const isReady = Boolean(dataView.getUint8(0));\n this.#updateIsReady(isReady);\n }\n #updateIsReady(isReady: boolean) {\n _console.log({ isReady });\n this.#isReady = isReady;\n this.#dispatchEvent(\"tfliteIsReady\", { tfliteIsReady: isReady });\n }\n #assertIsReady() {\n _console.assertWithError(this.isReady, `tflite is not ready`);\n }\n\n #captureDelay!: number;\n get captureDelay() {\n return this.#captureDelay;\n }\n #parseCaptureDelay(dataView: DataView) {\n _console.log(\"parseCaptureDelay\", dataView);\n const captureDelay = dataView.getUint16(0, true);\n this.#updateCaptueDelay(captureDelay);\n }\n #updateCaptueDelay(captureDelay: number) {\n _console.log({ captureDelay });\n this.#captureDelay = captureDelay;\n this.#dispatchEvent(\"getTfliteCaptureDelay\", {\n tfliteCaptureDelay: captureDelay,\n });\n }\n async setCaptureDelay(newCaptureDelay: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newCaptureDelay, \"number\");\n if (this.#captureDelay == newCaptureDelay) {\n _console.log(`redundant captureDelay assignment ${newCaptureDelay}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteCaptureDelay\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newCaptureDelay, true);\n this.sendMessage(\n [{ type: \"setTfliteCaptureDelay\", data: dataView.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #threshold!: number;\n get threshold() {\n return this.#threshold;\n }\n #parseThreshold(dataView: DataView) {\n _console.log(\"parseThreshold\", dataView);\n const threshold = dataView.getFloat32(0, true);\n this.#updateThreshold(threshold);\n }\n #updateThreshold(threshold: number) {\n _console.log({ threshold });\n this.#threshold = threshold;\n this.#dispatchEvent(\"getTfliteThreshold\", { tfliteThreshold: threshold });\n }\n async setThreshold(newThreshold: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newThreshold, \"number\");\n _console.assertWithError(\n newThreshold >= 0,\n `threshold must be positive (got ${newThreshold})`\n );\n if (this.#threshold == newThreshold) {\n _console.log(`redundant threshold assignment ${newThreshold}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteThreshold\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setFloat32(0, newThreshold, true);\n this.sendMessage(\n [{ type: \"setTfliteThreshold\", data: dataView.buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #inferencingEnabled!: boolean;\n get inferencingEnabled() {\n return this.#inferencingEnabled;\n }\n #parseInferencingEnabled(dataView: DataView) {\n _console.log(\"parseInferencingEnabled\", dataView);\n const inferencingEnabled = Boolean(dataView.getUint8(0));\n this.#updateInferencingEnabled(inferencingEnabled);\n }\n #updateInferencingEnabled(inferencingEnabled: boolean) {\n _console.log({ inferencingEnabled });\n this.#inferencingEnabled = inferencingEnabled;\n this.#dispatchEvent(\"getTfliteInferencingEnabled\", {\n tfliteInferencingEnabled: inferencingEnabled,\n });\n }\n async setInferencingEnabled(\n newInferencingEnabled: boolean,\n sendImmediately: boolean = true\n ) {\n _console.assertTypeWithError(newInferencingEnabled, \"boolean\");\n if (!newInferencingEnabled && !this.isReady) {\n return;\n }\n this.#assertIsReady();\n if (this.#inferencingEnabled == newInferencingEnabled) {\n _console.log(\n `redundant inferencingEnabled assignment ${newInferencingEnabled}`\n );\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteInferencingEnabled\");\n\n this.sendMessage(\n [\n {\n type: \"setTfliteInferencingEnabled\",\n data: Uint8Array.from([Number(newInferencingEnabled)]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n async toggleInferencingEnabled() {\n return this.setInferencingEnabled(!this.inferencingEnabled);\n }\n\n async enableInferencing() {\n if (this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(true);\n }\n async disableInferencing() {\n if (!this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(false);\n }\n\n #parseInference(dataView: DataView) {\n _console.log(\"parseInference\", dataView);\n\n const timestamp = parseTimestamp(dataView, 0);\n _console.log({ timestamp });\n\n const values: number[] = [];\n for (\n let index = 0, byteOffset = 2;\n byteOffset < dataView.byteLength;\n index++, byteOffset += 4\n ) {\n const value = dataView.getFloat32(byteOffset, true);\n values.push(value);\n }\n _console.log(\"values\", values);\n\n const inference: TfliteInference = {\n timestamp,\n values,\n };\n\n if (this.task == \"classification\") {\n let maxValue = 0;\n let maxIndex = 0;\n values.forEach((value, index) => {\n if (value > maxValue) {\n maxValue = value;\n maxIndex = index;\n }\n });\n _console.log({ maxIndex, maxValue });\n inference.maxIndex = maxIndex;\n inference.maxValue = maxValue;\n if (this.#configuration?.classes) {\n const { classes } = this.#configuration;\n inference.maxClass = classes[maxIndex];\n inference.classValues = {};\n values.forEach((value, index) => {\n const key = classes[index];\n inference.classValues![key] = value;\n });\n }\n }\n\n this.#dispatchEvent(\"tfliteInference\", { tfliteInference: inference });\n }\n\n parseMessage(messageType: TfliteMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getTfliteName\":\n case \"setTfliteName\":\n this.#parseName(dataView);\n break;\n case \"getTfliteTask\":\n case \"setTfliteTask\":\n this.#parseTask(dataView);\n break;\n case \"getTfliteSampleRate\":\n case \"setTfliteSampleRate\":\n this.#parseSampleRate(dataView);\n break;\n case \"getTfliteSensorTypes\":\n case \"setTfliteSensorTypes\":\n this.#parseSensorTypes(dataView);\n break;\n case \"tfliteIsReady\":\n this.#parseIsReady(dataView);\n break;\n case \"getTfliteCaptureDelay\":\n case \"setTfliteCaptureDelay\":\n this.#parseCaptureDelay(dataView);\n break;\n case \"getTfliteThreshold\":\n case \"setTfliteThreshold\":\n this.#parseThreshold(dataView);\n break;\n case \"getTfliteInferencingEnabled\":\n case \"setTfliteInferencingEnabled\":\n this.#parseInferencingEnabled(dataView);\n break;\n case \"tfliteInference\":\n this.#parseInference(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n #configuration?: TfliteFileConfiguration;\n get configuration() {\n return this.#configuration;\n }\n sendConfiguration(\n configuration: TfliteFileConfiguration,\n sendImmediately?: boolean\n ) {\n if (configuration == this.#configuration) {\n _console.log(\"redundant tflite configuration assignment\");\n return;\n }\n this.#configuration = configuration;\n _console.log(\"assigned new tflite configuration\", this.configuration);\n if (!this.configuration) {\n return;\n }\n const { name, task, captureDelay, sampleRate, threshold, sensorTypes } =\n this.configuration;\n this.setName(name, false);\n this.setTask(task, false);\n if (captureDelay != undefined) {\n this.setCaptureDelay(captureDelay, false);\n }\n this.setSampleRate(sampleRate, false);\n if (threshold != undefined) {\n this.setThreshold(threshold, false);\n }\n this.setSensorTypes(sensorTypes, sendImmediately);\n }\n\n clear() {\n this.#configuration = undefined;\n this.#inferencingEnabled = false;\n this.#sensorTypes = [];\n this.#sampleRate = 0;\n this.#isReady = false;\n }\n\n requestRequiredInformation() {\n _console.log(\"requesting required tflite information\");\n const messages = RequiredTfliteMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendMessage(messages, false);\n }\n}\n\nexport default TfliteManager;\n","import Device from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder } from \"./utils/Text.ts\";\n\nconst _console = createConsole(\"DeviceInformationManager\", { log: false });\n\nexport interface PnpId {\n source: \"Bluetooth\" | \"USB\";\n vendorId: number;\n productId: number;\n productVersion: number;\n}\n\nexport interface DeviceInformation {\n manufacturerName: string;\n modelNumber: string;\n softwareRevision: string;\n hardwareRevision: string;\n firmwareRevision: string;\n pnpId: PnpId;\n serialNumber: string;\n}\n\nexport const DeviceInformationTypes = [\n \"manufacturerName\",\n \"modelNumber\",\n \"hardwareRevision\",\n \"firmwareRevision\",\n \"softwareRevision\",\n \"pnpId\",\n \"serialNumber\",\n] as const;\nexport type DeviceInformationType = (typeof DeviceInformationTypes)[number];\n\nexport const DeviceInformationEventTypes = [\n ...DeviceInformationTypes,\n \"deviceInformation\",\n] as const;\nexport type DeviceInformationEventType =\n (typeof DeviceInformationEventTypes)[number];\n\nexport interface DeviceInformationEventMessages {\n manufacturerName: { manufacturerName: string };\n modelNumber: { modelNumber: string };\n softwareRevision: { softwareRevision: string };\n hardwareRevision: { hardwareRevision: string };\n firmwareRevision: { firmwareRevision: string };\n pnpId: { pnpId: PnpId };\n serialNumber: { serialNumber: string };\n deviceInformation: { deviceInformation: DeviceInformation };\n}\n\nexport type DeviceInformationEventDispatcher = EventDispatcher<\n Device,\n DeviceInformationEventType,\n DeviceInformationEventMessages\n>;\n\nclass DeviceInformationManager {\n eventDispatcher!: DeviceInformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #information: Partial<DeviceInformation> = {};\n get information() {\n return this.#information as DeviceInformation;\n }\n clear() {\n this.#information = {};\n }\n get #isComplete() {\n return DeviceInformationTypes.filter((key) => key != \"serialNumber\").every(\n (key) => key in this.#information\n );\n }\n\n #update(partialDeviceInformation: Partial<DeviceInformation>) {\n _console.log({ partialDeviceInformation });\n const deviceInformationNames = Object.keys(\n partialDeviceInformation\n ) as (keyof DeviceInformation)[];\n deviceInformationNames.forEach((deviceInformationName) => {\n // @ts-expect-error\n this.#dispatchEvent(deviceInformationName, {\n [deviceInformationName]:\n partialDeviceInformation[deviceInformationName],\n });\n });\n\n Object.assign(this.#information, partialDeviceInformation);\n _console.log({ deviceInformation: this.#information });\n if (this.#isComplete) {\n _console.log(\"completed deviceInformation\");\n this.#dispatchEvent(\"deviceInformation\", {\n deviceInformation: this.information,\n });\n }\n }\n\n parseMessage(messageType: DeviceInformationType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"manufacturerName\":\n const manufacturerName = textDecoder.decode(dataView.buffer);\n _console.log({ manufacturerName });\n this.#update({ manufacturerName });\n break;\n case \"modelNumber\":\n const modelNumber = textDecoder.decode(dataView.buffer);\n _console.log({ modelNumber });\n this.#update({ modelNumber });\n break;\n case \"softwareRevision\":\n const softwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ softwareRevision });\n this.#update({ softwareRevision });\n break;\n case \"hardwareRevision\":\n const hardwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ hardwareRevision });\n this.#update({ hardwareRevision });\n break;\n case \"firmwareRevision\":\n const firmwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ firmwareRevision });\n this.#update({ firmwareRevision });\n break;\n case \"pnpId\":\n const pnpId: PnpId = {\n source: dataView.getUint8(0) === 1 ? \"Bluetooth\" : \"USB\",\n productId: dataView.getUint16(3, true),\n productVersion: dataView.getUint16(5, true),\n vendorId: 0,\n };\n if (pnpId.source == \"Bluetooth\") {\n pnpId.vendorId = dataView.getUint16(1, true);\n } else {\n // no need to implement\n }\n _console.log({ pnpId });\n this.#update({ pnpId });\n break;\n case \"serialNumber\":\n const serialNumber = textDecoder.decode(dataView.buffer);\n _console.log({ serialNumber });\n // will only be used for node\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default DeviceInformationManager;\n","import { ConnectionType } from \"./connection/BaseConnectionManager.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInBrowser } from \"./utils/environment.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { Uint16Max } from \"./utils/MathUtils.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"InformationManager\", { log: false });\n\nexport const DeviceTypes = [\n \"leftInsole\",\n \"rightInsole\",\n \"leftGlove\",\n \"rightGlove\",\n \"glasses\",\n \"generic\",\n] as const;\nexport type DeviceType = (typeof DeviceTypes)[number];\n\nexport const Sides = [\"left\", \"right\"] as const;\nexport type Side = (typeof Sides)[number];\n\nexport const MinNameLength = 2;\nexport const MaxNameLength = 30;\n\nexport const InformationMessageTypes = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getMtu\",\n \"getId\",\n \"getName\",\n \"setName\",\n \"getType\",\n \"setType\",\n \"getCurrentTime\",\n \"setCurrentTime\",\n] as const;\nexport type InformationMessageType = (typeof InformationMessageTypes)[number];\n\nexport const InformationEventTypes = InformationMessageTypes;\nexport type InformationEventType = (typeof InformationEventTypes)[number];\n\nexport interface InformationEventMessages {\n isCharging: { isCharging: boolean };\n getBatteryCurrent: { batteryCurrent: number };\n getMtu: { mtu: number };\n getId: { id: string };\n getName: { name: string };\n getType: { type: DeviceType };\n getCurrentTime: { currentTime: number };\n}\n\nexport type InformationEventDispatcher = EventDispatcher<\n Device,\n InformationEventType,\n InformationEventMessages\n>;\nexport type SendInformationMessageCallback =\n SendMessageCallback<InformationMessageType>;\n\nclass InformationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendInformationMessageCallback;\n\n eventDispatcher!: InformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #isCharging = false;\n get isCharging() {\n return this.#isCharging;\n }\n #updateIsCharging(updatedIsCharging: boolean) {\n _console.assertTypeWithError(updatedIsCharging, \"boolean\");\n this.#isCharging = updatedIsCharging;\n _console.log({ isCharging: this.#isCharging });\n this.#dispatchEvent(\"isCharging\", { isCharging: this.#isCharging });\n }\n\n #batteryCurrent!: number;\n get batteryCurrent() {\n return this.#batteryCurrent;\n }\n async getBatteryCurrent() {\n _console.log(\"getting battery current...\");\n const promise = this.waitForEvent(\"getBatteryCurrent\");\n this.sendMessage([{ type: \"getBatteryCurrent\" }]);\n await promise;\n }\n #updateBatteryCurrent(updatedBatteryCurrent: number) {\n _console.assertTypeWithError(updatedBatteryCurrent, \"number\");\n this.#batteryCurrent = updatedBatteryCurrent;\n _console.log({ batteryCurrent: this.#batteryCurrent });\n this.#dispatchEvent(\"getBatteryCurrent\", {\n batteryCurrent: this.#batteryCurrent,\n });\n }\n\n #id!: string;\n get id() {\n return this.#id;\n }\n #updateId(updatedId: string) {\n _console.assertTypeWithError(updatedId, \"string\");\n this.#id = updatedId;\n _console.log({ id: this.#id });\n this.#dispatchEvent(\"getId\", { id: this.#id });\n }\n\n #name = \"\";\n get name() {\n return this.#name;\n }\n\n updateName(updatedName: string) {\n _console.assertTypeWithError(updatedName, \"string\");\n this.#name = updatedName;\n _console.log({ updatedName: this.#name });\n this.#dispatchEvent(\"getName\", { name: this.#name });\n }\n async setName(newName: string) {\n _console.assertTypeWithError(newName, \"string\");\n _console.assertRangeWithError(\n \"newName\",\n newName.length,\n MinNameLength,\n MaxNameLength\n );\n const setNameData = textEncoder.encode(newName);\n _console.log({ setNameData });\n\n const promise = this.waitForEvent(\"getName\");\n this.sendMessage([{ type: \"setName\", data: setNameData.buffer }]);\n await promise;\n }\n\n // TYPE\n #type!: DeviceType;\n get type() {\n return this.#type;\n }\n get typeEnum() {\n return DeviceTypes.indexOf(this.type);\n }\n #assertValidDeviceType(type: DeviceType) {\n _console.assertEnumWithError(type, DeviceTypes);\n }\n #assertValidDeviceTypeEnum(typeEnum: number) {\n _console.assertTypeWithError(typeEnum, \"number\");\n _console.assertWithError(\n typeEnum in DeviceTypes,\n `invalid typeEnum ${typeEnum}`\n );\n }\n updateType(updatedType: DeviceType) {\n this.#assertValidDeviceType(updatedType);\n if (updatedType == this.type) {\n _console.log(\"redundant type assignment\");\n return;\n }\n this.#type = updatedType;\n _console.log({ updatedType: this.#type });\n\n this.#dispatchEvent(\"getType\", { type: this.#type });\n }\n async #setTypeEnum(newTypeEnum: number) {\n this.#assertValidDeviceTypeEnum(newTypeEnum);\n const setTypeData = Uint8Array.from([newTypeEnum]);\n _console.log({ setTypeData });\n const promise = this.waitForEvent(\"getType\");\n this.sendMessage([{ type: \"setType\", data: setTypeData.buffer }]);\n await promise;\n }\n async setType(newType: DeviceType) {\n this.#assertValidDeviceType(newType);\n const newTypeEnum = DeviceTypes.indexOf(newType);\n this.#setTypeEnum(newTypeEnum);\n }\n\n get isInsole() {\n switch (this.type) {\n case \"leftInsole\":\n case \"rightInsole\":\n return true;\n default:\n return false;\n }\n }\n\n get isGlove() {\n switch (this.type) {\n case \"leftGlove\":\n case \"rightGlove\":\n return true;\n default:\n return false;\n }\n }\n\n get side(): Side {\n switch (this.type) {\n case \"leftInsole\":\n case \"leftGlove\":\n return \"left\";\n case \"rightInsole\":\n case \"rightGlove\":\n return \"right\";\n default:\n return \"left\";\n }\n }\n\n #mtu = 0;\n get mtu() {\n return this.#mtu;\n }\n #updateMtu(newMtu: number) {\n _console.assertTypeWithError(newMtu, \"number\");\n if (this.#mtu == newMtu) {\n _console.log(\"redundant mtu assignment\", newMtu);\n return;\n }\n this.#mtu = newMtu;\n\n this.#dispatchEvent(\"getMtu\", { mtu: this.#mtu });\n }\n\n #isCurrentTimeSet = false;\n get isCurrentTimeSet() {\n return this.#isCurrentTimeSet;\n }\n\n #onCurrentTime(currentTime: number) {\n _console.log({ currentTime });\n this.#isCurrentTimeSet =\n currentTime != 0 || Math.abs(Date.now() - currentTime) < Uint16Max;\n if (!this.#isCurrentTimeSet) {\n this.#setCurrentTime(false);\n }\n }\n async #setCurrentTime(sendImmediately?: boolean) {\n _console.log(\"setting current time...\");\n const dataView = new DataView(new ArrayBuffer(8));\n dataView.setBigUint64(0, BigInt(Date.now()), true);\n const promise = this.waitForEvent(\"getCurrentTime\");\n this.sendMessage(\n [{ type: \"setCurrentTime\", data: dataView.buffer }],\n sendImmediately\n );\n await promise;\n }\n\n // MESSAGE\n parseMessage(messageType: InformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"isCharging\":\n const isCharging = Boolean(dataView.getUint8(0));\n _console.log({ isCharging });\n this.#updateIsCharging(isCharging);\n break;\n case \"getBatteryCurrent\":\n const batteryCurrent = dataView.getFloat32(0, true);\n _console.log({ batteryCurrent });\n this.#updateBatteryCurrent(batteryCurrent);\n break;\n case \"getId\":\n const id = textDecoder.decode(dataView.buffer);\n _console.log({ id });\n this.#updateId(id);\n break;\n case \"getName\":\n case \"setName\":\n const name = textDecoder.decode(dataView.buffer);\n _console.log({ name });\n this.updateName(name);\n break;\n case \"getType\":\n case \"setType\":\n const typeEnum = dataView.getUint8(0);\n const type = DeviceTypes[typeEnum];\n _console.log({ typeEnum, type });\n this.updateType(type);\n break;\n case \"getMtu\":\n let mtu = dataView.getUint16(0, true);\n if (\n this.connectionType != \"webSocket\" &&\n this.connectionType != \"udp\"\n ) {\n mtu = Math.min(mtu, 512);\n }\n _console.log({ mtu });\n this.#updateMtu(mtu);\n break;\n case \"getCurrentTime\":\n case \"setCurrentTime\":\n const currentTime = Number(dataView.getBigUint64(0, true));\n this.#onCurrentTime(currentTime);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n this.#isCurrentTimeSet = false;\n }\n\n connectionType?: ConnectionType;\n}\n\nexport default InformationManager;\n","export const VibrationWaveformEffects = [\n \"none\",\n \"strongClick100\",\n \"strongClick60\",\n \"strongClick30\",\n \"sharpClick100\",\n \"sharpClick60\",\n \"sharpClick30\",\n \"softBump100\",\n \"softBump60\",\n \"softBump30\",\n \"doubleClick100\",\n \"doubleClick60\",\n \"tripleClick100\",\n \"softFuzz60\",\n \"strongBuzz100\",\n \"alert750ms\",\n \"alert1000ms\",\n \"strongClick1_100\",\n \"strongClick2_80\",\n \"strongClick3_60\",\n \"strongClick4_30\",\n \"mediumClick100\",\n \"mediumClick80\",\n \"mediumClick60\",\n \"sharpTick100\",\n \"sharpTick80\",\n \"sharpTick60\",\n \"shortDoubleClickStrong100\",\n \"shortDoubleClickStrong80\",\n \"shortDoubleClickStrong60\",\n \"shortDoubleClickStrong30\",\n \"shortDoubleClickMedium100\",\n \"shortDoubleClickMedium80\",\n \"shortDoubleClickMedium60\",\n \"shortDoubleSharpTick100\",\n \"shortDoubleSharpTick80\",\n \"shortDoubleSharpTick60\",\n \"longDoubleSharpClickStrong100\",\n \"longDoubleSharpClickStrong80\",\n \"longDoubleSharpClickStrong60\",\n \"longDoubleSharpClickStrong30\",\n \"longDoubleSharpClickMedium100\",\n \"longDoubleSharpClickMedium80\",\n \"longDoubleSharpClickMedium60\",\n \"longDoubleSharpTick100\",\n \"longDoubleSharpTick80\",\n \"longDoubleSharpTick60\",\n \"buzz100\",\n \"buzz80\",\n \"buzz60\",\n \"buzz40\",\n \"buzz20\",\n \"pulsingStrong100\",\n \"pulsingStrong60\",\n \"pulsingMedium100\",\n \"pulsingMedium60\",\n \"pulsingSharp100\",\n \"pulsingSharp60\",\n \"transitionClick100\",\n \"transitionClick80\",\n \"transitionClick60\",\n \"transitionClick40\",\n \"transitionClick20\",\n \"transitionClick10\",\n \"transitionHum100\",\n \"transitionHum80\",\n \"transitionHum60\",\n \"transitionHum40\",\n \"transitionHum20\",\n \"transitionHum10\",\n \"transitionRampDownLongSmooth2_100\",\n \"transitionRampDownLongSmooth1_100\",\n \"transitionRampDownMediumSmooth1_100\",\n \"transitionRampDownMediumSmooth2_100\",\n \"transitionRampDownShortSmooth1_100\",\n \"transitionRampDownShortSmooth2_100\",\n \"transitionRampDownLongSharp1_100\",\n \"transitionRampDownLongSharp2_100\",\n \"transitionRampDownMediumSharp1_100\",\n \"transitionRampDownMediumSharp2_100\",\n \"transitionRampDownShortSharp1_100\",\n \"transitionRampDownShortSharp2_100\",\n \"transitionRampUpLongSmooth1_100\",\n \"transitionRampUpLongSmooth2_100\",\n \"transitionRampUpMediumSmooth1_100\",\n \"transitionRampUpMediumSmooth2_100\",\n \"transitionRampUpShortSmooth1_100\",\n \"transitionRampUpShortSmooth2_100\",\n \"transitionRampUpLongSharp1_100\",\n \"transitionRampUpLongSharp2_100\",\n \"transitionRampUpMediumSharp1_100\",\n \"transitionRampUpMediumSharp2_100\",\n \"transitionRampUpShortSharp1_100\",\n \"transitionRampUpShortSharp2_100\",\n \"transitionRampDownLongSmooth1_50\",\n \"transitionRampDownLongSmooth2_50\",\n \"transitionRampDownMediumSmooth1_50\",\n \"transitionRampDownMediumSmooth2_50\",\n \"transitionRampDownShortSmooth1_50\",\n \"transitionRampDownShortSmooth2_50\",\n \"transitionRampDownLongSharp1_50\",\n \"transitionRampDownLongSharp2_50\",\n \"transitionRampDownMediumSharp1_50\",\n \"transitionRampDownMediumSharp2_50\",\n \"transitionRampDownShortSharp1_50\",\n \"transitionRampDownShortSharp2_50\",\n \"transitionRampUpLongSmooth1_50\",\n \"transitionRampUpLongSmooth2_50\",\n \"transitionRampUpMediumSmooth1_50\",\n \"transitionRampUpMediumSmooth2_50\",\n \"transitionRampUpShortSmooth1_50\",\n \"transitionRampUpShortSmooth2_50\",\n \"transitionRampUpLongSharp1_50\",\n \"transitionRampUpLongSharp2_50\",\n \"transitionRampUpMediumSharp1_50\",\n \"transitionRampUpMediumSharp2_50\",\n \"transitionRampUpShortSharp1_50\",\n \"transitionRampUpShortSharp2_50\",\n \"longBuzz100\",\n \"smoothHum50\",\n \"smoothHum40\",\n \"smoothHum30\",\n \"smoothHum20\",\n \"smoothHum10\",\n] as const;\n\nexport type VibrationWaveformEffect = (typeof VibrationWaveformEffects)[number];\n","import { createConsole } from \"../utils/Console.ts\";\nimport {\n VibrationWaveformEffect,\n VibrationWaveformEffects,\n} from \"./VibrationWaveformEffects.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport Device, { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"auto-bind\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\n\nconst _console = createConsole(\"VibrationManager\", { log: false });\n\nexport const VibrationLocations = [\"front\", \"rear\"] as const;\nexport type VibrationLocation = (typeof VibrationLocations)[number];\n\nexport const VibrationTypes = [\"waveformEffect\", \"waveform\"] as const;\nexport type VibrationType = (typeof VibrationTypes)[number];\n\nexport interface VibrationWaveformEffectSegment {\n effect?: VibrationWaveformEffect;\n delay?: number;\n loopCount?: number;\n}\n\nexport interface VibrationWaveformSegment {\n duration: number;\n amplitude: number;\n}\n\nexport const VibrationMessageTypes = [\n \"getVibrationLocations\",\n \"triggerVibration\",\n] as const;\nexport type VibrationMessageType = (typeof VibrationMessageTypes)[number];\n\nexport const VibrationEventTypes = VibrationMessageTypes;\nexport type VibrationEventType = (typeof VibrationEventTypes)[number];\n\nexport interface VibrationEventMessages {\n getVibrationLocations: { vibrationLocations: VibrationLocation[] };\n}\n\nexport const MaxNumberOfVibrationWaveformEffectSegments = 8;\nexport const MaxVibrationWaveformSegmentDuration = 2550;\nexport const MaxVibrationWaveformEffectSegmentDelay = 1270;\nexport const MaxVibrationWaveformEffectSegmentLoopCount = 3;\nexport const MaxNumberOfVibrationWaveformSegments = 20;\nexport const MaxVibrationWaveformEffectSequenceLoopCount = 6;\n\ninterface BaseVibrationConfiguration {\n type: VibrationType;\n locations?: VibrationLocation[];\n}\n\nexport interface VibrationWaveformEffectConfiguration\n extends BaseVibrationConfiguration {\n type: \"waveformEffect\";\n segments: VibrationWaveformEffectSegment[];\n loopCount?: number;\n}\n\nexport interface VibrationWaveformConfiguration\n extends BaseVibrationConfiguration {\n type: \"waveform\";\n segments: VibrationWaveformSegment[];\n}\n\nexport type VibrationConfiguration =\n | VibrationWaveformEffectConfiguration\n | VibrationWaveformConfiguration;\n\nexport type SendVibrationMessageCallback =\n SendMessageCallback<VibrationMessageType>;\n\nexport type VibrationEventDispatcher = EventDispatcher<\n Device,\n VibrationEventType,\n VibrationEventMessages\n>;\n\nclass VibrationManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendVibrationMessageCallback;\n\n eventDispatcher!: VibrationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #verifyLocation(location: VibrationLocation) {\n _console.assertTypeWithError(location, \"string\");\n _console.assertWithError(\n VibrationLocations.includes(location),\n `invalid location \"${location}\"`\n );\n }\n #verifyLocations(locations: VibrationLocation[]) {\n this.#assertNonEmptyArray(locations);\n locations.forEach((location) => {\n this.#verifyLocation(location);\n });\n }\n #createLocationsBitmask(locations: VibrationLocation[]) {\n this.#verifyLocations(locations);\n\n let locationsBitmask = 0;\n locations.forEach((location) => {\n const locationIndex = VibrationLocations.indexOf(location);\n locationsBitmask |= 1 << locationIndex;\n });\n _console.log({ locationsBitmask });\n _console.assertWithError(\n locationsBitmask > 0,\n `locationsBitmask must not be zero`\n );\n return locationsBitmask;\n }\n\n #assertNonEmptyArray(array: any[]) {\n _console.assertWithError(Array.isArray(array), \"passed non-array\");\n _console.assertWithError(array.length > 0, \"passed empty array\");\n }\n\n #verifyWaveformEffect(waveformEffect: VibrationWaveformEffect) {\n _console.assertWithError(\n VibrationWaveformEffects.includes(waveformEffect),\n `invalid waveformEffect \"${waveformEffect}\"`\n );\n }\n\n #verifyWaveformEffectSegment(\n waveformEffectSegment: VibrationWaveformEffectSegment\n ) {\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n this.#verifyWaveformEffect(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n _console.assertWithError(\n delay >= 0,\n `delay must be 0ms or greater (got ${delay})`\n );\n _console.assertWithError(\n delay <= MaxVibrationWaveformEffectSegmentDelay,\n `delay must be ${MaxVibrationWaveformEffectSegmentDelay}ms or less (got ${delay})`\n );\n } else {\n throw Error(\"no effect or delay found in waveformEffectSegment\");\n }\n\n if (waveformEffectSegment.loopCount != undefined) {\n const { loopCount } = waveformEffectSegment;\n this.#verifyWaveformEffectSegmentLoopCount(loopCount);\n }\n }\n\n #verifyWaveformEffectSegmentLoopCount(\n waveformEffectSegmentLoopCount: number\n ) {\n _console.assertTypeWithError(waveformEffectSegmentLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSegmentLoopCount >= 0,\n `waveformEffectSegmentLoopCount must be 0 or greater (got ${waveformEffectSegmentLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSegmentLoopCount <=\n MaxVibrationWaveformEffectSegmentLoopCount,\n `waveformEffectSegmentLoopCount must be ${MaxVibrationWaveformEffectSegmentLoopCount} or fewer (got ${waveformEffectSegmentLoopCount})`\n );\n }\n\n #verifyWaveformEffectSegments(\n waveformEffectSegments: VibrationWaveformEffectSegment[]\n ) {\n this.#assertNonEmptyArray(waveformEffectSegments);\n _console.assertWithError(\n waveformEffectSegments.length <=\n MaxNumberOfVibrationWaveformEffectSegments,\n `must have ${MaxNumberOfVibrationWaveformEffectSegments} waveformEffectSegments or fewer (got ${waveformEffectSegments.length})`\n );\n waveformEffectSegments.forEach((waveformEffectSegment) => {\n this.#verifyWaveformEffectSegment(waveformEffectSegment);\n });\n }\n\n #verifyWaveformEffectSequenceLoopCount(\n waveformEffectSequenceLoopCount: number\n ) {\n _console.assertTypeWithError(waveformEffectSequenceLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSequenceLoopCount >= 0,\n `waveformEffectSequenceLoopCount must be 0 or greater (got ${waveformEffectSequenceLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSequenceLoopCount <=\n MaxVibrationWaveformEffectSequenceLoopCount,\n `waveformEffectSequenceLoopCount must be ${MaxVibrationWaveformEffectSequenceLoopCount} or fewer (got ${waveformEffectSequenceLoopCount})`\n );\n }\n\n #verifyWaveformSegment(waveformSegment: VibrationWaveformSegment) {\n _console.assertTypeWithError(waveformSegment.amplitude, \"number\");\n _console.assertWithError(\n waveformSegment.amplitude >= 0,\n `amplitude must be 0 or greater (got ${waveformSegment.amplitude})`\n );\n _console.assertWithError(\n waveformSegment.amplitude <= 1,\n `amplitude must be 1 or less (got ${waveformSegment.amplitude})`\n );\n\n _console.assertTypeWithError(waveformSegment.duration, \"number\");\n _console.assertWithError(\n waveformSegment.duration > 0,\n `duration must be greater than 0ms (got ${waveformSegment.duration}ms)`\n );\n _console.assertWithError(\n waveformSegment.duration <= MaxVibrationWaveformSegmentDuration,\n `duration must be ${MaxVibrationWaveformSegmentDuration}ms or less (got ${waveformSegment.duration}ms)`\n );\n }\n\n #verifyWaveformSegments(waveformSegments: VibrationWaveformSegment[]) {\n this.#assertNonEmptyArray(waveformSegments);\n _console.assertWithError(\n waveformSegments.length <= MaxNumberOfVibrationWaveformSegments,\n `must have ${MaxNumberOfVibrationWaveformSegments} waveformSegments or fewer (got ${waveformSegments.length})`\n );\n waveformSegments.forEach((waveformSegment) => {\n this.#verifyWaveformSegment(waveformSegment);\n });\n }\n\n #createWaveformEffectsData(\n locations: VibrationLocation[],\n waveformEffectSegments: VibrationWaveformEffectSegment[],\n waveformEffectSequenceLoopCount: number = 0\n ) {\n this.#verifyWaveformEffectSegments(waveformEffectSegments);\n this.#verifyWaveformEffectSequenceLoopCount(\n waveformEffectSequenceLoopCount\n );\n\n let dataArray = [];\n let byteOffset = 0;\n\n const hasAtLeast1WaveformEffectWithANonzeroLoopCount =\n waveformEffectSegments.some((waveformEffectSegment) => {\n const { loopCount } = waveformEffectSegment;\n return loopCount != undefined && loopCount > 0;\n });\n\n const includeAllWaveformEffectSegments =\n hasAtLeast1WaveformEffectWithANonzeroLoopCount ||\n waveformEffectSequenceLoopCount != 0;\n\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegments &&\n index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegment = waveformEffectSegments[index] || {\n effect: \"none\",\n };\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n dataArray[byteOffset++] =\n VibrationWaveformEffects.indexOf(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n dataArray[byteOffset++] = (1 << 7) | Math.floor(delay / 10); // set most significant bit to 1\n } else {\n throw Error(\"invalid waveformEffectSegment\");\n }\n }\n\n const includeAllWaveformEffectSegmentLoopCounts =\n waveformEffectSequenceLoopCount != 0;\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegmentLoopCounts &&\n index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegmentLoopCount =\n waveformEffectSegments[index]?.loopCount || 0;\n if (index == 0 || index == 4) {\n dataArray[byteOffset] = 0;\n }\n const bitOffset = 2 * (index % 4);\n dataArray[byteOffset] |= waveformEffectSegmentLoopCount << bitOffset;\n if (index == 3 || index == 7) {\n byteOffset++;\n }\n }\n\n if (waveformEffectSequenceLoopCount != 0) {\n dataArray[byteOffset++] = waveformEffectSequenceLoopCount;\n }\n const dataView = new DataView(Uint8Array.from(dataArray).buffer);\n _console.log({ dataArray, dataView });\n return this.#createData(locations, \"waveformEffect\", dataView);\n }\n #createWaveformData(\n locations: VibrationLocation[],\n waveformSegments: VibrationWaveformSegment[]\n ) {\n this.#verifyWaveformSegments(waveformSegments);\n const dataView = new DataView(new ArrayBuffer(waveformSegments.length * 2));\n waveformSegments.forEach((waveformSegment, index) => {\n dataView.setUint8(index * 2, Math.floor(waveformSegment.amplitude * 127));\n dataView.setUint8(\n index * 2 + 1,\n Math.floor(waveformSegment.duration / 10)\n );\n });\n _console.log({ dataView });\n return this.#createData(locations, \"waveform\", dataView);\n }\n\n #verifyVibrationType(vibrationType: VibrationType) {\n _console.assertTypeWithError(vibrationType, \"string\");\n _console.assertWithError(\n VibrationTypes.includes(vibrationType),\n `invalid vibrationType \"${vibrationType}\"`\n );\n }\n\n #createData(\n locations: VibrationLocation[],\n vibrationType: VibrationType,\n dataView: DataView\n ) {\n _console.assertWithError(dataView?.byteLength > 0, \"no data received\");\n const locationsBitmask = this.#createLocationsBitmask(locations);\n this.#verifyVibrationType(vibrationType);\n const vibrationTypeIndex = VibrationTypes.indexOf(vibrationType);\n _console.log({ locationsBitmask, vibrationTypeIndex, dataView });\n const data = concatenateArrayBuffers(\n locationsBitmask,\n vibrationTypeIndex,\n dataView.byteLength,\n dataView\n );\n _console.log({ data });\n return data;\n }\n\n async triggerVibration(\n vibrationConfigurations: VibrationConfiguration[],\n sendImmediately: boolean = true\n ) {\n let triggerVibrationData!: ArrayBuffer;\n vibrationConfigurations.forEach((vibrationConfiguration) => {\n const { type } = vibrationConfiguration;\n\n let { locations } = vibrationConfiguration;\n locations = locations || this.vibrationLocations.slice();\n locations = locations.filter((location) =>\n this.vibrationLocations.includes(location)\n );\n\n let arrayBuffer: ArrayBuffer;\n\n switch (type) {\n case \"waveformEffect\":\n {\n const { segments, loopCount } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformEffectsData(\n locations,\n segments,\n loopCount\n );\n }\n break;\n case \"waveform\":\n {\n const { segments } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformData(locations, segments);\n }\n break;\n default:\n throw Error(`invalid vibration type \"${type}\"`);\n }\n _console.log({ type, arrayBuffer });\n triggerVibrationData = concatenateArrayBuffers(\n triggerVibrationData,\n arrayBuffer\n );\n });\n await this.sendMessage(\n [{ type: \"triggerVibration\", data: triggerVibrationData }],\n sendImmediately\n );\n }\n\n #vibrationLocations: VibrationLocation[] = [];\n get vibrationLocations() {\n return this.#vibrationLocations;\n }\n #onVibrationLocations(vibrationLocations: VibrationLocation[]) {\n this.#vibrationLocations = vibrationLocations;\n _console.log(\"vibrationLocations\", vibrationLocations);\n this.#dispatchEvent(\"getVibrationLocations\", {\n vibrationLocations: this.#vibrationLocations,\n });\n }\n\n // MESSAGE\n parseMessage(messageType: VibrationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getVibrationLocations\":\n const vibrationLocations = Array.from(new Uint8Array(dataView.buffer))\n .map((index) => VibrationLocations[index])\n .filter(Boolean);\n this.#onVibrationLocations(vibrationLocations);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default VibrationManager;\n","import Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInNode } from \"./utils/environment.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"WifiManager\", { log: false });\n\nexport const MinWifiSSIDLength = 1;\nexport const MaxWifiSSIDLength = 32;\n\nexport const MinWifiPasswordLength = 8;\nexport const MaxWifiPasswordLength = 64;\n\nexport const WifiMessageTypes = [\n \"isWifiAvailable\",\n \"getWifiSSID\",\n \"setWifiSSID\",\n \"getWifiPassword\",\n \"setWifiPassword\",\n \"getWifiConnectionEnabled\",\n \"setWifiConnectionEnabled\",\n \"isWifiConnected\",\n \"ipAddress\",\n \"isWifiSecure\",\n] as const;\nexport type WifiMessageType = (typeof WifiMessageTypes)[number];\n\nexport const RequiredWifiMessageTypes: WifiMessageType[] = [\n \"getWifiSSID\",\n \"getWifiPassword\",\n \"getWifiConnectionEnabled\",\n \"isWifiConnected\",\n \"ipAddress\",\n \"isWifiSecure\",\n] as const;\n\nexport const WifiEventTypes = WifiMessageTypes;\nexport type WifiEventType = (typeof WifiEventTypes)[number];\n\nexport interface WifiEventMessages {\n isWifiAvailable: { isWifiAvailable: boolean };\n getWifiSSID: { wifiSSID: string };\n getWifiPassword: { wifiPassword: string };\n getEnableWifiConnection: { wifiConnectionEnabled: boolean };\n isWifiConnected: { isWifiConnected: boolean };\n ipAddress: { ipAddress?: string };\n}\n\nexport type WifiEventDispatcher = EventDispatcher<\n Device,\n WifiEventType,\n WifiEventMessages\n>;\nexport type SendWifiMessageCallback = SendMessageCallback<WifiMessageType>;\n\nclass WifiManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendWifiMessageCallback;\n\n eventDispatcher!: WifiEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n requestRequiredInformation() {\n _console.log(\"requesting required wifi information\");\n const messages = RequiredWifiMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendMessage(messages, false);\n }\n\n // PROPERTIES\n\n #isWifiAvailable = false;\n get isWifiAvailable() {\n return this.#isWifiAvailable;\n }\n #updateIsWifiAvailable(updatedIsWifiAvailable: boolean) {\n _console.assertTypeWithError(updatedIsWifiAvailable, \"boolean\");\n this.#isWifiAvailable = updatedIsWifiAvailable;\n _console.log({ isWifiAvailable: this.#isWifiAvailable });\n this.#dispatchEvent(\"isWifiAvailable\", {\n isWifiAvailable: this.#isWifiAvailable,\n });\n }\n\n #assertWifiIsAvailable() {\n _console.assertWithError(this.#isWifiAvailable, \"wifi is not available\");\n }\n\n // WIFI SSID\n #wifiSSID = \"\";\n get wifiSSID() {\n return this.#wifiSSID;\n }\n\n #updateWifiSSID(updatedWifiSSID: string) {\n _console.assertTypeWithError(updatedWifiSSID, \"string\");\n this.#wifiSSID = updatedWifiSSID;\n _console.log({ wifiSSID: this.#wifiSSID });\n this.#dispatchEvent(\"getWifiSSID\", { wifiSSID: this.#wifiSSID });\n }\n async setWifiSSID(newWifiSSID: string) {\n this.#assertWifiIsAvailable();\n if (this.#wifiConnectionEnabled) {\n _console.error(\"cannot change ssid while wifi connection is enabled\");\n return;\n }\n _console.assertTypeWithError(newWifiSSID, \"string\");\n _console.assertRangeWithError(\n \"wifiSSID\",\n newWifiSSID.length,\n MinWifiSSIDLength,\n MaxWifiSSIDLength\n );\n\n const setWifiSSIDData = textEncoder.encode(newWifiSSID);\n _console.log({ setWifiSSIDData });\n\n const promise = this.waitForEvent(\"getWifiSSID\");\n this.sendMessage([{ type: \"setWifiSSID\", data: setWifiSSIDData.buffer }]);\n await promise;\n }\n\n // WIFI PASSWORD\n #wifiPassword = \"\";\n get wifiPassword() {\n return this.#wifiPassword;\n }\n\n #updateWifiPassword(updatedWifiPassword: string) {\n _console.assertTypeWithError(updatedWifiPassword, \"string\");\n this.#wifiPassword = updatedWifiPassword;\n _console.log({ wifiPassword: this.#wifiPassword });\n this.#dispatchEvent(\"getWifiPassword\", {\n wifiPassword: this.#wifiPassword,\n });\n }\n async setWifiPassword(newWifiPassword: string) {\n this.#assertWifiIsAvailable();\n if (this.#wifiConnectionEnabled) {\n _console.error(\"cannot change password while wifi connection is enabled\");\n return;\n }\n _console.assertTypeWithError(newWifiPassword, \"string\");\n if (newWifiPassword.length > 0) {\n _console.assertRangeWithError(\n \"wifiPassword\",\n newWifiPassword.length,\n MinWifiPasswordLength,\n MaxWifiPasswordLength\n );\n }\n\n const setWifiPasswordData = textEncoder.encode(newWifiPassword);\n _console.log({ setWifiPasswordData });\n\n const promise = this.waitForEvent(\"getWifiPassword\");\n this.sendMessage([\n { type: \"setWifiPassword\", data: setWifiPasswordData.buffer },\n ]);\n await promise;\n }\n\n // ENABLE WIFI CONNECTION\n #wifiConnectionEnabled!: boolean;\n get wifiConnectionEnabled() {\n return this.#wifiConnectionEnabled;\n }\n #updateWifiConnectionEnabled(wifiConnectionEnabled: boolean) {\n _console.log({ wifiConnectionEnabled });\n this.#wifiConnectionEnabled = wifiConnectionEnabled;\n this.#dispatchEvent(\"getWifiConnectionEnabled\", {\n wifiConnectionEnabled: wifiConnectionEnabled,\n });\n }\n async setWifiConnectionEnabled(\n newWifiConnectionEnabled: boolean,\n sendImmediately: boolean = true\n ) {\n this.#assertWifiIsAvailable();\n _console.assertTypeWithError(newWifiConnectionEnabled, \"boolean\");\n if (this.#wifiConnectionEnabled == newWifiConnectionEnabled) {\n _console.log(\n `redundant wifiConnectionEnabled assignment ${newWifiConnectionEnabled}`\n );\n return;\n }\n\n const promise = this.waitForEvent(\"getWifiConnectionEnabled\");\n this.sendMessage(\n [\n {\n type: \"setWifiConnectionEnabled\",\n data: Uint8Array.from([Number(newWifiConnectionEnabled)]).buffer,\n },\n ],\n sendImmediately\n );\n await promise;\n }\n async toggleWifiConnection() {\n return this.setWifiConnectionEnabled(!this.wifiConnectionEnabled);\n }\n async enableWifiConnection() {\n return this.setWifiConnectionEnabled(true);\n }\n async disableWifiConnection() {\n return this.setWifiConnectionEnabled(false);\n }\n\n // IS WIFI CONNECTED\n #isWifiConnected = false;\n get isWifiConnected() {\n return this.#isWifiConnected;\n }\n #updateIsWifiConnected(updatedIsWifiConnected: boolean) {\n _console.assertTypeWithError(updatedIsWifiConnected, \"boolean\");\n this.#isWifiConnected = updatedIsWifiConnected;\n _console.log({ isWifiConnected: this.#isWifiConnected });\n this.#dispatchEvent(\"isWifiConnected\", {\n isWifiConnected: this.#isWifiConnected,\n });\n }\n\n // IP ADDRESS\n #ipAddress?: string;\n get ipAddress() {\n return this.#ipAddress;\n }\n\n #updateIpAddress(updatedIpAddress?: string) {\n this.#ipAddress = updatedIpAddress;\n _console.log({ ipAddress: this.#ipAddress });\n this.#dispatchEvent(\"ipAddress\", {\n ipAddress: this.#ipAddress,\n });\n }\n\n // IS WIFI SECURE\n #isWifiSecure = false;\n get isWifiSecure() {\n return this.#isWifiSecure;\n }\n #updateIsWifiSecure(updatedIsWifiSecure: boolean) {\n _console.assertTypeWithError(updatedIsWifiSecure, \"boolean\");\n this.#isWifiSecure = updatedIsWifiSecure;\n _console.log({ isWifiSecure: this.#isWifiSecure });\n this.#dispatchEvent(\"isWifiSecure\", {\n isWifiSecure: this.#isWifiSecure,\n });\n }\n\n // MESSAGE\n parseMessage(messageType: WifiMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"isWifiAvailable\":\n const isWifiAvailable = Boolean(dataView.getUint8(0));\n _console.log({ isWifiAvailable });\n this.#updateIsWifiAvailable(isWifiAvailable);\n break;\n case \"getWifiSSID\":\n case \"setWifiSSID\":\n const ssid = textDecoder.decode(dataView.buffer);\n _console.log({ ssid });\n this.#updateWifiSSID(ssid);\n break;\n case \"getWifiPassword\":\n case \"setWifiPassword\":\n const password = textDecoder.decode(dataView.buffer);\n _console.log({ password });\n this.#updateWifiPassword(password);\n break;\n case \"getWifiConnectionEnabled\":\n case \"setWifiConnectionEnabled\":\n const enableWifiConnection = Boolean(dataView.getUint8(0));\n _console.log({ enableWifiConnection });\n this.#updateWifiConnectionEnabled(enableWifiConnection);\n break;\n case \"isWifiConnected\":\n const isWifiConnected = Boolean(dataView.getUint8(0));\n _console.log({ isWifiConnected });\n this.#updateIsWifiConnected(isWifiConnected);\n break;\n case \"ipAddress\":\n let ipAddress: string | undefined = undefined;\n if (dataView.byteLength == 4) {\n ipAddress = new Uint8Array(dataView.buffer.slice(0, 4)).join(\".\");\n }\n _console.log({ ipAddress });\n this.#updateIpAddress(ipAddress);\n break;\n case \"isWifiSecure\":\n const isWifiSecure = Boolean(dataView.getUint8(0));\n _console.log({ isWifiSecure });\n this.#updateIsWifiSecure(isWifiSecure);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n this.#wifiSSID = \"\";\n this.#wifiPassword = \"\";\n this.#ipAddress = \"\";\n this.#isWifiConnected = false;\n this.#isWifiAvailable = false;\n }\n}\n\nexport default WifiManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport Timer from \"../utils/Timer.ts\";\n\nimport { FileTransferMessageTypes } from \"../FileTransferManager.ts\";\nimport { TfliteMessageTypes } from \"../TfliteManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationTypes } from \"../DeviceInformationManager.ts\";\nimport { InformationMessageTypes } from \"../InformationManager.ts\";\nimport { VibrationMessageTypes } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfigurationMessageTypes } from \"../sensor/SensorConfigurationManager.ts\";\nimport { SensorDataMessageTypes } from \"../sensor/SensorDataManager.ts\";\nimport { WifiMessageTypes } from \"../WifiManager.ts\";\nimport { CameraMessageTypes } from \"../CameraManager.ts\";\n\nconst _console = createConsole(\"BaseConnectionManager\", { log: false });\n\nexport const ConnectionTypes = [\n \"webBluetooth\",\n \"noble\",\n \"client\",\n \"webSocket\",\n \"udp\",\n] as const;\nexport type ConnectionType = (typeof ConnectionTypes)[number];\n\nexport const ClientConnectionTypes = [\"noble\", \"webSocket\", \"udp\"] as const;\nexport type ClientConnectionType = (typeof ClientConnectionTypes)[number];\n\ninterface BaseConnectOptions {\n type: \"client\" | \"webBluetooth\" | \"webSocket\" | \"udp\";\n}\nexport interface WebBluetoothConnectOptions extends BaseConnectOptions {\n type: \"webBluetooth\";\n}\ninterface BaseWifiConnectOptions extends BaseConnectOptions {\n ipAddress: string;\n}\nexport interface ClientConnectOptions extends BaseConnectOptions {\n type: \"client\";\n subType?: \"noble\" | \"webSocket\" | \"udp\";\n}\nexport interface WebSocketConnectOptions extends BaseWifiConnectOptions {\n type: \"webSocket\";\n isWifiSecure?: boolean;\n}\nexport interface UDPConnectOptions extends BaseWifiConnectOptions {\n type: \"udp\";\n //sendPort: number;\n receivePort?: number;\n}\nexport type ConnectOptions =\n | WebBluetoothConnectOptions\n | WebSocketConnectOptions\n | UDPConnectOptions\n | ClientConnectOptions;\n\nexport const ConnectionStatuses = [\n \"notConnected\",\n \"connecting\",\n \"connected\",\n \"disconnecting\",\n] as const;\nexport type ConnectionStatus = (typeof ConnectionStatuses)[number];\n\nexport const ConnectionEventTypes = [\n ...ConnectionStatuses,\n \"connectionStatus\",\n \"isConnected\",\n] as const;\nexport type ConnectionEventType = (typeof ConnectionEventTypes)[number];\n\nexport interface ConnectionStatusEventMessages {\n notConnected: any;\n connecting: any;\n connected: any;\n disconnecting: any;\n connectionStatus: { connectionStatus: ConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport interface TxMessage {\n type: TxRxMessageType;\n data?: ArrayBuffer;\n}\n\nexport const TxRxMessageTypes = [\n ...InformationMessageTypes,\n ...SensorConfigurationMessageTypes,\n ...SensorDataMessageTypes,\n ...VibrationMessageTypes,\n ...FileTransferMessageTypes,\n ...TfliteMessageTypes,\n ...WifiMessageTypes,\n ...CameraMessageTypes,\n] as const;\nexport type TxRxMessageType = (typeof TxRxMessageTypes)[number];\n\nexport const SMPMessageTypes = [\"smp\"] as const;\nexport type SMPMessageType = (typeof SMPMessageTypes)[number];\n\nexport const BatteryLevelMessageTypes = [\"batteryLevel\"] as const;\nexport type BatteryLevelMessageType = (typeof BatteryLevelMessageTypes)[number];\n\nexport const MetaConnectionMessageTypes = [\"rx\", \"tx\"] as const;\nexport type MetaConnectionMessageType =\n (typeof MetaConnectionMessageTypes)[number];\n\nexport const ConnectionMessageTypes = [\n ...BatteryLevelMessageTypes,\n ...DeviceInformationTypes,\n ...MetaConnectionMessageTypes,\n ...TxRxMessageTypes,\n ...SMPMessageTypes,\n] as const;\nexport type ConnectionMessageType = (typeof ConnectionMessageTypes)[number];\n\nexport type ConnectionStatusCallback = (status: ConnectionStatus) => void;\nexport type MessageReceivedCallback = (\n messageType: ConnectionMessageType,\n dataView: DataView\n) => void;\nexport type MessagesReceivedCallback = () => void;\n\nabstract class BaseConnectionManager {\n static #AssertValidTxRxMessageType(messageType: TxRxMessageType) {\n _console.assertEnumWithError(messageType, TxRxMessageTypes);\n }\n\n abstract get bluetoothId(): string;\n\n // CALLBACKS\n onStatusUpdated?: ConnectionStatusCallback;\n onMessageReceived?: MessageReceivedCallback;\n onMessagesReceived?: MessagesReceivedCallback;\n\n protected get baseConstructor() {\n return this.constructor as typeof BaseConnectionManager;\n }\n static get isSupported() {\n return false;\n }\n get isSupported() {\n return this.baseConstructor.isSupported;\n }\n\n get canUpdateFirmware() {\n return false;\n }\n\n static type: ConnectionType;\n get type(): ConnectionType {\n return this.baseConstructor.type;\n }\n\n /** @throws {Error} if not supported */\n #assertIsSupported() {\n _console.assertWithError(\n this.isSupported,\n `${this.constructor.name} is not supported`\n );\n }\n\n constructor() {\n this.#assertIsSupported();\n }\n\n #status: ConnectionStatus = \"notConnected\";\n get status() {\n return this.#status;\n }\n protected set status(newConnectionStatus) {\n _console.assertEnumWithError(newConnectionStatus, ConnectionStatuses);\n if (this.#status == newConnectionStatus) {\n _console.log(\n `tried to assign same connection status \"${newConnectionStatus}\"`\n );\n return;\n }\n _console.log(`new connection status \"${newConnectionStatus}\"`);\n this.#status = newConnectionStatus;\n this.onStatusUpdated!(this.status);\n\n if (this.isConnected) {\n this.#timer.start();\n } else {\n this.#timer.stop();\n }\n\n if (this.#status == \"notConnected\") {\n this.mtu = this.defaultMtu;\n }\n }\n\n get isConnected() {\n return this.status == \"connected\";\n }\n\n get isAvailable() {\n return false;\n }\n\n /** @throws {Error} if connected */\n protected assertIsNotConnected() {\n _console.assertWithError(!this.isConnected, \"device is already connected\");\n }\n /** @throws {Error} if connecting */\n #assertIsNotConnecting() {\n _console.assertWithError(\n this.status != \"connecting\",\n \"device is already connecting\"\n );\n }\n /** @throws {Error} if not connected */\n protected assertIsConnected() {\n _console.assertWithError(this.isConnected, \"device is not connected\");\n }\n /** @throws {Error} if disconnecting */\n #assertIsNotDisconnecting() {\n _console.assertWithError(\n this.status != \"disconnecting\",\n \"device is already disconnecting\"\n );\n }\n /** @throws {Error} if not connected or is disconnecting */\n assertIsConnectedAndNotDisconnecting() {\n this.assertIsConnected();\n this.#assertIsNotDisconnecting();\n }\n\n async connect() {\n this.assertIsNotConnected();\n this.#assertIsNotConnecting();\n this.status = \"connecting\";\n }\n get canReconnect() {\n return false;\n }\n async reconnect() {\n this.assertIsNotConnected();\n this.#assertIsNotConnecting();\n _console.assertWithError(this.canReconnect, \"unable to reconnect\");\n this.status = \"connecting\";\n _console.log(\"attempting to reconnect...\");\n }\n async disconnect() {\n this.assertIsConnected();\n this.#assertIsNotDisconnecting();\n this.status = \"disconnecting\";\n _console.log(\"disconnecting from device...\");\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n this.assertIsConnectedAndNotDisconnecting();\n _console.log(\"sending smp message\", data);\n }\n\n #pendingMessages: TxMessage[] = [];\n #isSendingMessages = false;\n async sendTxMessages(\n messages: TxMessage[] | undefined,\n sendImmediately: boolean = true\n ) {\n this.assertIsConnectedAndNotDisconnecting();\n\n if (messages) {\n this.#pendingMessages.push(...messages);\n _console.log(`appended ${messages.length} messages`);\n }\n\n if (!sendImmediately) {\n _console.log(\"not sending immediately - waiting until later\");\n return;\n }\n\n if (this.#isSendingMessages) {\n _console.log(\"already sending messages - waiting until later\");\n return;\n }\n if (this.#pendingMessages.length == 0) {\n _console.log(\"no pendingMessages\");\n return;\n }\n this.#isSendingMessages = true;\n\n _console.log(\"sendTxMessages\", this.#pendingMessages.slice());\n\n const arrayBuffers = this.#pendingMessages.map((message) => {\n BaseConnectionManager.#AssertValidTxRxMessageType(message.type);\n const messageTypeEnum = TxRxMessageTypes.indexOf(message.type);\n const dataLength = new DataView(new ArrayBuffer(2));\n dataLength.setUint16(0, message.data?.byteLength || 0, true);\n return concatenateArrayBuffers(messageTypeEnum, dataLength, message.data);\n });\n this.#pendingMessages.length = 0;\n\n if (this.mtu) {\n while (arrayBuffers.length > 0) {\n if (\n arrayBuffers.every(\n (arrayBuffer) => arrayBuffer.byteLength > this.mtu! - 3\n )\n ) {\n _console.log(\"every arrayBuffer is too big to send\");\n break;\n }\n _console.log(\"remaining arrayBuffers.length\", arrayBuffers.length);\n let arrayBufferByteLength = 0;\n let arrayBufferCount = 0;\n arrayBuffers.some((arrayBuffer) => {\n if (arrayBufferByteLength + arrayBuffer.byteLength > this.mtu! - 3) {\n _console.log(\n `stopping appending arrayBuffers ( length ${arrayBuffer.byteLength} too much)`\n );\n return true;\n }\n _console.log(\n `allowing arrayBuffer with length ${arrayBuffer.byteLength}`\n );\n arrayBufferCount++;\n arrayBufferByteLength += arrayBuffer.byteLength;\n });\n const arrayBuffersToSend = arrayBuffers.splice(0, arrayBufferCount);\n _console.log({ arrayBufferCount, arrayBuffersToSend });\n\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffersToSend);\n _console.log(\"sending arrayBuffer (partitioned)\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n } else {\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffers);\n _console.log(\"sending arrayBuffer (all)\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n\n this.#isSendingMessages = false;\n\n this.sendTxMessages(undefined, true);\n }\n\n protected defaultMtu = 23;\n //mtu?: number;\n mtu?: number = this.defaultMtu;\n\n async sendTxData(data: ArrayBuffer) {\n _console.log(\"sendTxData\", data);\n }\n\n parseRxMessage(dataView: DataView) {\n parseMessage(\n dataView,\n TxRxMessageTypes,\n this.#onRxMessage.bind(this),\n null,\n true\n );\n this.onMessagesReceived!();\n }\n\n #onRxMessage(messageType: TxRxMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n this.onMessageReceived!(messageType, dataView);\n }\n\n #timer = new Timer(this.#checkConnection.bind(this), 5000);\n #checkConnection() {\n //console.log(\"checking connection...\");\n if (!this.isConnected) {\n _console.log(\"timer detected disconnection\");\n this.status = \"notConnected\";\n }\n }\n\n clear() {\n this.#isSendingMessages = false;\n this.#pendingMessages.length = 0;\n }\n\n remove() {\n this.clear();\n\n this.onStatusUpdated = undefined;\n this.onMessageReceived = undefined;\n this.onMessagesReceived = undefined;\n }\n}\n\nexport default BaseConnectionManager;\n","export function spacesToPascalCase(string: string) {\n return string\n .replace(/(?:^\\w|\\b\\w)/g, function (match) {\n return match.toUpperCase();\n })\n .replace(/\\s+/g, \"\");\n}\n\nexport function capitalizeFirstCharacter(string: string) {\n return string[0].toUpperCase() + string.slice(1);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { spacesToPascalCase } from \"./stringUtils.ts\";\n\nconst _console = createConsole(\"EventUtils\", { log: false });\n\ntype BoundEventListeners = { [eventType: string]: EventListener };\nexport type BoundGenericEventListeners = { [eventType: string]: Function };\n\nexport function bindEventListeners(\n eventTypes: readonly string[],\n boundEventListeners: BoundGenericEventListeners,\n target: any\n) {\n _console.log(\"bindEventListeners\", { eventTypes, boundEventListeners, target });\n eventTypes.forEach((eventType) => {\n const _eventType = `_on${spacesToPascalCase(eventType)}`;\n _console.assertWithError(target[_eventType], `no event \"${_eventType}\" found in target`);\n _console.log(`binding eventType \"${eventType}\" as ${_eventType} from target`, target);\n const boundEvent = target[_eventType].bind(target);\n target[_eventType] = boundEvent;\n boundEventListeners[eventType] = boundEvent;\n });\n}\n\nexport function addEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let addEventListener = target.addEventListener || target.addListener || target.on || target.AddEventListener;\n _console.assertWithError(addEventListener, \"no add listener function found for target\");\n addEventListener = addEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n addEventListener(eventType, eventListener);\n });\n}\n\nexport function removeEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let removeEventListener = target.removeEventListener || target.removeListener || target.RemoveEventListener;\n _console.assertWithError(removeEventListener, \"no remove listener function found for target\");\n removeEventListener = removeEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n removeEventListener(eventType, eventListener);\n });\n}\n","import { isInBrowser, isInNode } from \"../../utils/environment.ts\";\nimport { createConsole } from \"../../utils/Console.ts\";\n\nconst _console = createConsole(\"bluetoothUUIDs\", { log: false });\n\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nvar BluetoothUUID = webbluetooth.BluetoothUUID;\n/** NODE_END */\n/** BROWSER_START */\nif (isInBrowser) {\n var BluetoothUUID = window.BluetoothUUID;\n}\n/** BROWSER_END */\n\nfunction generateBluetoothUUID(value: string): BluetoothServiceUUID {\n _console.assertTypeWithError(value, \"string\");\n _console.assertWithError(\n value.length == 4,\n \"value must be 4 characters long\"\n );\n return `ea6d${value}-a725-4f9b-893d-c3913e33b39f`;\n}\n\nfunction stringToCharacteristicUUID(\n identifier: string\n): BluetoothCharacteristicUUID {\n return BluetoothUUID?.getCharacteristic?.(identifier);\n}\n\nfunction stringToServiceUUID(identifier: string): BluetoothServiceUUID {\n return BluetoothUUID?.getService?.(identifier);\n}\n\nexport type BluetoothServiceName =\n | \"deviceInformation\"\n | \"battery\"\n | \"main\"\n | \"smp\";\nimport { DeviceInformationType } from \"../../DeviceInformationManager.ts\";\nexport type BluetoothCharacteristicName =\n | DeviceInformationType\n | \"batteryLevel\"\n | \"rx\"\n | \"tx\"\n | \"smp\";\n\ninterface BluetoothCharacteristicInformation {\n uuid: BluetoothCharacteristicUUID;\n}\ninterface BluetoothServiceInformation {\n uuid: BluetoothServiceUUID;\n characteristics: {\n [characteristicName in BluetoothCharacteristicName]?: BluetoothCharacteristicInformation;\n };\n}\ninterface BluetoothServicesInformation {\n services: {\n [serviceName in BluetoothServiceName]: BluetoothServiceInformation;\n };\n}\nconst bluetoothUUIDs: BluetoothServicesInformation = Object.freeze({\n services: {\n deviceInformation: {\n uuid: stringToServiceUUID(\"device_information\"),\n characteristics: {\n manufacturerName: {\n uuid: stringToCharacteristicUUID(\"manufacturer_name_string\"),\n },\n modelNumber: {\n uuid: stringToCharacteristicUUID(\"model_number_string\"),\n },\n hardwareRevision: {\n uuid: stringToCharacteristicUUID(\"hardware_revision_string\"),\n },\n firmwareRevision: {\n uuid: stringToCharacteristicUUID(\"firmware_revision_string\"),\n },\n softwareRevision: {\n uuid: stringToCharacteristicUUID(\"software_revision_string\"),\n },\n pnpId: {\n uuid: stringToCharacteristicUUID(\"pnp_id\"),\n },\n serialNumber: {\n uuid: stringToCharacteristicUUID(\"serial_number_string\"),\n },\n },\n },\n battery: {\n uuid: stringToServiceUUID(\"battery_service\"),\n characteristics: {\n batteryLevel: {\n uuid: stringToCharacteristicUUID(\"battery_level\"),\n },\n },\n },\n main: {\n uuid: generateBluetoothUUID(\"0000\"),\n characteristics: {\n rx: { uuid: generateBluetoothUUID(\"1000\") },\n tx: { uuid: generateBluetoothUUID(\"1001\") },\n },\n },\n smp: {\n uuid: \"8d53dc1d-1db7-4cd3-868b-8a527460aa84\",\n characteristics: {\n smp: { uuid: \"da2e7828-fbce-4e01-ae9e-261174997c48\" },\n },\n },\n },\n});\n\nexport const serviceUUIDs = [bluetoothUUIDs.services.main.uuid];\nexport const optionalServiceUUIDs = [\n bluetoothUUIDs.services.deviceInformation.uuid,\n bluetoothUUIDs.services.battery.uuid,\n bluetoothUUIDs.services.smp.uuid,\n];\nexport const allServiceUUIDs = [...serviceUUIDs, ...optionalServiceUUIDs];\n\nexport function getServiceNameFromUUID(\n serviceUUID: BluetoothServiceUUID\n): BluetoothServiceName | undefined {\n serviceUUID = serviceUUID.toString().toLowerCase();\n const serviceNames = Object.keys(\n bluetoothUUIDs.services\n ) as BluetoothServiceName[];\n return serviceNames.find((serviceName) => {\n const serviceInfo = bluetoothUUIDs.services[serviceName];\n let serviceInfoUUID = serviceInfo.uuid.toString();\n if (serviceUUID.length == 4) {\n serviceInfoUUID = serviceInfoUUID.slice(4, 8);\n }\n if (!serviceUUID.includes(\"-\")) {\n serviceInfoUUID = serviceInfoUUID.replaceAll(\"-\", \"\");\n }\n return serviceUUID == serviceInfoUUID;\n });\n}\n\nexport const characteristicUUIDs: BluetoothCharacteristicUUID[] = [];\nexport const allCharacteristicUUIDs: BluetoothCharacteristicUUID[] = [];\n\nexport const characteristicNames: BluetoothCharacteristicName[] = [];\nexport const allCharacteristicNames: BluetoothCharacteristicName[] = [];\n\nObject.values(bluetoothUUIDs.services).forEach((serviceInfo) => {\n if (!serviceInfo.characteristics) {\n return;\n }\n const characteristicNames = Object.keys(\n serviceInfo.characteristics\n ) as BluetoothCharacteristicName[];\n characteristicNames.forEach((characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[characteristicName]!;\n if (serviceUUIDs.includes(serviceInfo.uuid)) {\n characteristicUUIDs.push(characteristicInfo.uuid);\n characteristicNames.push(characteristicName);\n }\n allCharacteristicUUIDs.push(characteristicInfo.uuid);\n allCharacteristicNames.push(characteristicName);\n });\n}, []);\n\n//_console.log({ characteristicUUIDs, allCharacteristicUUIDs });\n\nexport function getCharacteristicNameFromUUID(\n characteristicUUID: BluetoothCharacteristicUUID\n): BluetoothCharacteristicName | undefined {\n //_console.log({ characteristicUUID });\n characteristicUUID = characteristicUUID.toString().toLowerCase();\n var characteristicName: BluetoothCharacteristicName | undefined;\n Object.values(bluetoothUUIDs.services).some((serviceInfo) => {\n const characteristicNames = Object.keys(\n serviceInfo.characteristics\n ) as BluetoothCharacteristicName[];\n characteristicName = characteristicNames.find((_characteristicName) => {\n const characteristicInfo =\n serviceInfo.characteristics[_characteristicName]!;\n let characteristicInfoUUID = characteristicInfo.uuid.toString();\n if (characteristicUUID.length == 4) {\n characteristicInfoUUID = characteristicInfoUUID.slice(4, 8);\n }\n if (!characteristicUUID.includes(\"-\")) {\n characteristicInfoUUID = characteristicInfoUUID.replaceAll(\"-\", \"\");\n }\n return characteristicUUID == characteristicInfoUUID;\n });\n return characteristicName;\n });\n return characteristicName;\n}\n\nexport function getCharacteristicProperties(\n characteristicName: BluetoothCharacteristicName\n): BluetoothCharacteristicProperties {\n const properties = {\n broadcast: false,\n read: true,\n writeWithoutResponse: false,\n write: false,\n notify: false,\n indicate: false,\n authenticatedSignedWrites: false,\n reliableWrite: false,\n writableAuxiliaries: false,\n };\n\n // read\n switch (characteristicName) {\n case \"rx\":\n case \"tx\":\n case \"smp\":\n properties.read = false;\n break;\n }\n\n // notify\n switch (characteristicName) {\n case \"batteryLevel\":\n case \"rx\":\n case \"smp\":\n properties.notify = true;\n break;\n }\n\n // write without response\n switch (characteristicName) {\n case \"smp\":\n properties.writeWithoutResponse = true;\n break;\n }\n\n // write\n switch (characteristicName) {\n case \"tx\":\n properties.write = true;\n break;\n }\n\n return properties;\n}\n\nexport const serviceDataUUID = \"0000\";\n","import { createConsole } from \"../../utils/Console.ts\";\nimport BaseConnectionManager from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"BluetoothConnectionManager\", { log: false });\n\nimport { BluetoothCharacteristicName } from \"./bluetoothUUIDs.ts\";\n\nabstract class BluetoothConnectionManager extends BaseConnectionManager {\n get isAvailable() {\n // no way to tell if the user has turned bluetooth on or off\n return true;\n }\n\n isInRange = true;\n\n protected onCharacteristicValueChanged(\n characteristicName: BluetoothCharacteristicName,\n dataView: DataView\n ) {\n if (characteristicName == \"rx\") {\n this.parseRxMessage(dataView);\n } else {\n this.onMessageReceived?.(characteristicName, dataView);\n }\n }\n\n protected async writeCharacteristic(\n characteristicName: BluetoothCharacteristicName,\n data: ArrayBuffer\n ) {\n _console.log(\"writeCharacteristic\", ...arguments);\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n await this.writeCharacteristic(\"smp\", data);\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n await this.writeCharacteristic(\"tx\", data);\n }\n}\n\nexport default BluetoothConnectionManager;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport {\n isInNode,\n isInBrowser,\n isInBluefy,\n isInWebBLE,\n} from \"../../utils/environment.ts\";\nimport {\n addEventListeners,\n removeEventListeners,\n} from \"../../utils/EventUtils.ts\";\nimport {\n serviceUUIDs,\n optionalServiceUUIDs,\n getServiceNameFromUUID,\n getCharacteristicNameFromUUID,\n getCharacteristicProperties,\n} from \"./bluetoothUUIDs.ts\";\nimport BluetoothConnectionManager from \"./BluetoothConnectionManager.ts\";\nimport {\n BluetoothCharacteristicName,\n BluetoothServiceName,\n} from \"./bluetoothUUIDs.ts\";\nimport { ConnectionType } from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"WebBluetoothConnectionManager\", { log: false });\n\ntype WebBluetoothInterface = webbluetooth.Bluetooth | Bluetooth;\n\ninterface BluetoothService extends BluetoothRemoteGATTService {\n name?: BluetoothServiceName;\n}\ninterface BluetoothCharacteristic extends BluetoothRemoteGATTCharacteristic {\n name?: BluetoothCharacteristicName;\n}\n\nvar bluetooth: WebBluetoothInterface | undefined;\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nif (isInNode) {\n bluetooth = webbluetooth.bluetooth;\n}\n/** NODE_END */\n\n/** BROWSER_START */\nif (isInBrowser) {\n bluetooth = window.navigator.bluetooth;\n}\n/** BROWSER_END */\n\nclass WebBluetoothConnectionManager extends BluetoothConnectionManager {\n get bluetoothId() {\n return this.device!.id;\n }\n\n get canUpdateFirmware() {\n return this.#characteristics.has(\"smp\");\n }\n\n #boundBluetoothCharacteristicEventListeners: {\n [eventType: string]: EventListener;\n } = {\n characteristicvaluechanged: this.#onCharacteristicvaluechanged.bind(this),\n };\n #boundBluetoothDeviceEventListeners: { [eventType: string]: EventListener } =\n {\n gattserverdisconnected: this.#onGattserverdisconnected.bind(this),\n };\n\n static get isSupported() {\n return Boolean(bluetooth);\n }\n static get type(): ConnectionType {\n return \"webBluetooth\";\n }\n\n #device?: BluetoothDevice;\n get device() {\n return this.#device;\n }\n set device(newDevice) {\n if (this.#device == newDevice) {\n _console.log(\"tried to assign the same BluetoothDevice\");\n return;\n }\n if (this.#device) {\n removeEventListeners(\n this.#device,\n this.#boundBluetoothDeviceEventListeners\n );\n }\n if (newDevice) {\n addEventListeners(newDevice, this.#boundBluetoothDeviceEventListeners);\n }\n this.#device = newDevice;\n }\n\n get server(): BluetoothRemoteGATTServer | undefined {\n return this.#device?.gatt;\n }\n get isConnected() {\n return this.server?.connected || false;\n }\n\n #services: Map<BluetoothServiceName, BluetoothService> = new Map();\n #characteristics: Map<BluetoothCharacteristicName, BluetoothCharacteristic> =\n new Map();\n\n async connect() {\n await super.connect();\n\n try {\n const device = await bluetooth!.requestDevice({\n filters: [{ services: serviceUUIDs }],\n optionalServices: isInBrowser ? optionalServiceUUIDs : [],\n });\n\n _console.log(\"got BluetoothDevice\");\n this.device = device;\n\n _console.log(\"connecting to device...\");\n const server = await this.server!.connect();\n _console.log(`connected to device? ${server.connected}`);\n\n await this.#getServicesAndCharacteristics();\n\n _console.log(\"fully connected\");\n\n this.status = \"connected\";\n } catch (error) {\n _console.error(error);\n this.status = \"notConnected\";\n this.server?.disconnect();\n this.#removeEventListeners();\n }\n }\n async #getServicesAndCharacteristics() {\n this.#removeEventListeners();\n\n _console.log(\"getting services...\");\n const services = await this.server!.getPrimaryServices();\n _console.log(\"got services\", services.length);\n //const service = await this.server!.getPrimaryService(\"8d53dc1d-1db7-4cd3-868b-8a527460aa84\");\n\n _console.log(\"getting characteristics...\");\n for (const serviceIndex in services) {\n const service = services[serviceIndex] as BluetoothService;\n _console.log({ service });\n const serviceName = getServiceNameFromUUID(service.uuid)!;\n _console.assertWithError(\n serviceName,\n `no name found for service uuid \"${service.uuid}\"`\n );\n _console.log(`got \"${serviceName}\" service`);\n service.name = serviceName;\n this.#services.set(serviceName, service);\n _console.log(`getting characteristics for \"${serviceName}\" service`);\n const characteristics = await service.getCharacteristics();\n _console.log(`got characteristics for \"${serviceName}\" service`);\n for (const characteristicIndex in characteristics) {\n const characteristic = characteristics[\n characteristicIndex\n ] as BluetoothCharacteristic;\n _console.log({ characteristic });\n const characteristicName = getCharacteristicNameFromUUID(\n characteristic.uuid\n )!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic uuid \"${characteristic.uuid}\" in \"${serviceName}\" service`\n );\n _console.log(\n `got \"${characteristicName}\" characteristic in \"${serviceName}\" service`\n );\n characteristic.name = characteristicName;\n this.#characteristics.set(characteristicName, characteristic);\n addEventListeners(\n characteristic,\n this.#boundBluetoothCharacteristicEventListeners\n );\n const characteristicProperties =\n characteristic.properties ||\n getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(\n `starting notifications for \"${characteristicName}\" characteristic`\n );\n await characteristic.startNotifications();\n }\n if (characteristicProperties.read) {\n _console.log(`reading \"${characteristicName}\" characteristic...`);\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n }\n }\n async #removeEventListeners() {\n if (this.device) {\n removeEventListeners(\n this.device,\n this.#boundBluetoothDeviceEventListeners\n );\n }\n\n const promises = Array.from(this.#characteristics.keys()).map(\n (characteristicName) => {\n const characteristic = this.#characteristics.get(characteristicName)!;\n removeEventListeners(\n characteristic,\n this.#boundBluetoothCharacteristicEventListeners\n );\n const characteristicProperties =\n characteristic.properties ||\n getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(\n `stopping notifications for \"${characteristicName}\" characteristic`\n );\n return characteristic.stopNotifications();\n }\n }\n );\n\n return Promise.allSettled(promises);\n }\n async disconnect() {\n await this.#removeEventListeners();\n await super.disconnect();\n this.server?.disconnect();\n this.status = \"notConnected\";\n }\n\n #onCharacteristicvaluechanged(event: Event) {\n _console.log(\"oncharacteristicvaluechanged\");\n\n const characteristic = event.target as BluetoothCharacteristic;\n this.#onCharacteristicValueChanged(characteristic);\n }\n\n #onCharacteristicValueChanged(characteristic: BluetoothCharacteristic) {\n _console.log(\"onCharacteristicValue\");\n\n const characteristicName = characteristic.name!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic with uuid \"${characteristic.uuid}\"`\n );\n\n _console.log(\n `oncharacteristicvaluechanged for \"${characteristicName}\" characteristic`\n );\n const dataView = characteristic.value!;\n _console.assertWithError(\n dataView,\n `no data found for \"${characteristicName}\" characteristic`\n );\n _console.log(\n `data for \"${characteristicName}\" characteristic`,\n Array.from(new Uint8Array(dataView.buffer))\n );\n\n try {\n this.onCharacteristicValueChanged(characteristicName, dataView);\n } catch (error) {\n _console.error(error);\n }\n }\n\n async writeCharacteristic(\n characteristicName: BluetoothCharacteristicName,\n data: ArrayBuffer\n ) {\n super.writeCharacteristic(characteristicName, data);\n\n const characteristic = this.#characteristics.get(characteristicName)!;\n _console.assertWithError(\n characteristic,\n `${characteristicName} characteristic not found`\n );\n _console.log(\"writing characteristic\", characteristic, data);\n const characteristicProperties =\n characteristic.properties ||\n getCharacteristicProperties(characteristicName);\n if (characteristicProperties.writeWithoutResponse) {\n _console.log(\"writing without response\");\n await characteristic.writeValueWithoutResponse(data);\n } else {\n _console.log(\"writing with response\");\n await characteristic.writeValueWithResponse(data);\n }\n _console.log(\"wrote characteristic\");\n\n if (characteristicProperties.read && !characteristicProperties.notify) {\n _console.log(\"reading value after write...\");\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n\n #onGattserverdisconnected() {\n _console.log(\"gattserverdisconnected\");\n this.status = \"notConnected\";\n }\n\n get canReconnect() {\n return Boolean(this.server && !this.server.connected && this.isInRange);\n }\n async reconnect() {\n await super.reconnect();\n try {\n await this.server!.connect();\n } catch (error) {\n _console.error(error);\n this.isInRange = false;\n }\n\n if (this.isConnected) {\n _console.log(\"successfully reconnected!\");\n await this.#getServicesAndCharacteristics();\n this.status = \"connected\";\n } else {\n _console.log(\"unable to reconnect\");\n this.status = \"notConnected\";\n }\n }\n\n remove() {\n super.remove();\n this.device = undefined;\n }\n}\n\nexport default WebBluetoothConnectionManager;\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nconst POW_2_24 = 5.960464477539063e-8;\nconst POW_2_32 = 4294967296;\nconst POW_2_53 = 9007199254740992;\n\nexport function encode(value) {\n let data = new ArrayBuffer(256);\n let dataView = new DataView(data);\n let lastLength;\n let offset = 0;\n\n function prepareWrite(length) {\n let newByteLength = data.byteLength;\n const requiredLength = offset + length;\n while (newByteLength < requiredLength) {\n newByteLength <<= 1;\n }\n if (newByteLength !== data.byteLength) {\n const oldDataView = dataView;\n data = new ArrayBuffer(newByteLength);\n dataView = new DataView(data);\n const uint32count = (offset + 3) >> 2;\n for (let i = 0; i < uint32count; ++i) {\n dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));\n }\n }\n\n lastLength = length;\n return dataView;\n }\n function commitWrite() {\n offset += lastLength;\n }\n function writeFloat64(value) {\n commitWrite(prepareWrite(8).setFloat64(offset, value));\n }\n function writeUint8(value) {\n commitWrite(prepareWrite(1).setUint8(offset, value));\n }\n function writeUint8Array(value) {\n const dataView = prepareWrite(value.length);\n for (let i = 0; i < value.length; ++i) {\n dataView.setUint8(offset + i, value[i]);\n }\n commitWrite();\n }\n function writeUint16(value) {\n commitWrite(prepareWrite(2).setUint16(offset, value));\n }\n function writeUint32(value) {\n commitWrite(prepareWrite(4).setUint32(offset, value));\n }\n function writeUint64(value) {\n const low = value % POW_2_32;\n const high = (value - low) / POW_2_32;\n const dataView = prepareWrite(8);\n dataView.setUint32(offset, high);\n dataView.setUint32(offset + 4, low);\n commitWrite();\n }\n function writeTypeAndLength(type, length) {\n if (length < 24) {\n writeUint8((type << 5) | length);\n } else if (length < 0x100) {\n writeUint8((type << 5) | 24);\n writeUint8(length);\n } else if (length < 0x10000) {\n writeUint8((type << 5) | 25);\n writeUint16(length);\n } else if (length < 0x100000000) {\n writeUint8((type << 5) | 26);\n writeUint32(length);\n } else {\n writeUint8((type << 5) | 27);\n writeUint64(length);\n }\n }\n\n function encodeItem(value) {\n let i;\n const utf8data = [];\n let length;\n\n if (value === false) {\n return writeUint8(0xf4);\n }\n if (value === true) {\n return writeUint8(0xf5);\n }\n if (value === null) {\n return writeUint8(0xf6);\n }\n if (value === undefined) {\n return writeUint8(0xf7);\n }\n\n switch (typeof value) {\n case \"number\":\n if (Math.floor(value) === value) {\n if (value >= 0 && value <= POW_2_53) {\n return writeTypeAndLength(0, value);\n }\n if (-POW_2_53 <= value && value < 0) {\n return writeTypeAndLength(1, -(value + 1));\n }\n }\n writeUint8(0xfb);\n return writeFloat64(value);\n\n case \"string\":\n for (i = 0; i < value.length; ++i) {\n let charCode = value.charCodeAt(i);\n if (charCode < 0x80) {\n utf8data.push(charCode);\n } else if (charCode < 0x800) {\n utf8data.push(0xc0 | (charCode >> 6));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else if (charCode < 0xd800) {\n utf8data.push(0xe0 | (charCode >> 12));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else {\n charCode = (charCode & 0x3ff) << 10;\n charCode |= value.charCodeAt(++i) & 0x3ff;\n charCode += 0x10000;\n\n utf8data.push(0xf0 | (charCode >> 18));\n utf8data.push(0x80 | ((charCode >> 12) & 0x3f));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n }\n }\n\n writeTypeAndLength(3, utf8data.length);\n return writeUint8Array(utf8data);\n\n default:\n if (Array.isArray(value)) {\n length = value.length;\n writeTypeAndLength(4, length);\n for (i = 0; i < length; ++i) {\n encodeItem(value[i]);\n }\n } else if (value instanceof Uint8Array) {\n writeTypeAndLength(2, value.length);\n writeUint8Array(value);\n } else {\n const keys = Object.keys(value);\n length = keys.length;\n writeTypeAndLength(5, length);\n for (i = 0; i < length; ++i) {\n const key = keys[i];\n encodeItem(key);\n encodeItem(value[key]);\n }\n }\n }\n }\n\n encodeItem(value);\n\n if (\"slice\" in data) {\n return data.slice(0, offset);\n }\n\n const ret = new ArrayBuffer(offset);\n const retView = new DataView(ret);\n for (let i = 0; i < offset; ++i) {\n retView.setUint8(i, dataView.getUint8(i));\n }\n return ret;\n}\n\nexport function decode(data, tagger, simpleValue) {\n const dataView = new DataView(data);\n let offset = 0;\n\n if (typeof tagger !== \"function\") {\n tagger = function (value) {\n return value;\n };\n }\n if (typeof simpleValue !== \"function\") {\n simpleValue = function () {\n return undefined;\n };\n }\n\n function commitRead(length, value) {\n offset += length;\n return value;\n }\n function readArrayBuffer(length) {\n return commitRead(length, new Uint8Array(data, offset, length));\n }\n function readFloat16() {\n const tempArrayBuffer = new ArrayBuffer(4);\n const tempDataView = new DataView(tempArrayBuffer);\n const value = readUint16();\n\n const sign = value & 0x8000;\n let exponent = value & 0x7c00;\n const fraction = value & 0x03ff;\n\n if (exponent === 0x7c00) {\n exponent = 0xff << 10;\n } else if (exponent !== 0) {\n exponent += (127 - 15) << 10;\n } else if (fraction !== 0) {\n return (sign ? -1 : 1) * fraction * POW_2_24;\n }\n\n tempDataView.setUint32(0, (sign << 16) | (exponent << 13) | (fraction << 13));\n return tempDataView.getFloat32(0);\n }\n function readFloat32() {\n return commitRead(4, dataView.getFloat32(offset));\n }\n function readFloat64() {\n return commitRead(8, dataView.getFloat64(offset));\n }\n function readUint8() {\n return commitRead(1, dataView.getUint8(offset));\n }\n function readUint16() {\n return commitRead(2, dataView.getUint16(offset));\n }\n function readUint32() {\n return commitRead(4, dataView.getUint32(offset));\n }\n function readUint64() {\n return readUint32() * POW_2_32 + readUint32();\n }\n function readBreak() {\n if (dataView.getUint8(offset) !== 0xff) {\n return false;\n }\n offset += 1;\n return true;\n }\n function readLength(additionalInformation) {\n if (additionalInformation < 24) {\n return additionalInformation;\n }\n if (additionalInformation === 24) {\n return readUint8();\n }\n if (additionalInformation === 25) {\n return readUint16();\n }\n if (additionalInformation === 26) {\n return readUint32();\n }\n if (additionalInformation === 27) {\n return readUint64();\n }\n if (additionalInformation === 31) {\n return -1;\n }\n throw new Error(\"Invalid length encoding\");\n }\n function readIndefiniteStringLength(majorType) {\n const initialByte = readUint8();\n if (initialByte === 0xff) {\n return -1;\n }\n const length = readLength(initialByte & 0x1f);\n if (length < 0 || initialByte >> 5 !== majorType) {\n throw new Error(\"Invalid indefinite length element\");\n }\n return length;\n }\n\n function appendUtf16Data(utf16data, length) {\n for (let i = 0; i < length; ++i) {\n let value = readUint8();\n if (value & 0x80) {\n if (value < 0xe0) {\n value = ((value & 0x1f) << 6) | (readUint8() & 0x3f);\n length -= 1;\n } else if (value < 0xf0) {\n value = ((value & 0x0f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 2;\n } else {\n value =\n ((value & 0x0f) << 18) | ((readUint8() & 0x3f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 3;\n }\n }\n\n if (value < 0x10000) {\n utf16data.push(value);\n } else {\n value -= 0x10000;\n utf16data.push(0xd800 | (value >> 10));\n utf16data.push(0xdc00 | (value & 0x3ff));\n }\n }\n }\n\n function decodeItem() {\n const initialByte = readUint8();\n const majorType = initialByte >> 5;\n const additionalInformation = initialByte & 0x1f;\n let i;\n let length;\n\n if (majorType === 7) {\n switch (additionalInformation) {\n case 25:\n return readFloat16();\n case 26:\n return readFloat32();\n case 27:\n return readFloat64();\n }\n }\n\n length = readLength(additionalInformation);\n if (length < 0 && (majorType < 2 || majorType > 6)) {\n throw new Error(\"Invalid length\");\n }\n\n const utf16data = [];\n let retArray;\n const retObject = {};\n\n switch (majorType) {\n case 0:\n return length;\n case 1:\n return -1 - length;\n case 2:\n if (length < 0) {\n const elements = [];\n let fullArrayLength = 0;\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n fullArrayLength += length;\n elements.push(readArrayBuffer(length));\n }\n const fullArray = new Uint8Array(fullArrayLength);\n let fullArrayOffset = 0;\n for (i = 0; i < elements.length; ++i) {\n fullArray.set(elements[i], fullArrayOffset);\n fullArrayOffset += elements[i].length;\n }\n return fullArray;\n }\n return readArrayBuffer(length);\n case 3:\n if (length < 0) {\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n appendUtf16Data(utf16data, length);\n }\n } else {\n appendUtf16Data(utf16data, length);\n }\n return String.fromCharCode.apply(null, utf16data);\n case 4:\n if (length < 0) {\n retArray = [];\n while (!readBreak()) {\n retArray.push(decodeItem());\n }\n } else {\n retArray = new Array(length);\n for (i = 0; i < length; ++i) {\n retArray[i] = decodeItem();\n }\n }\n return retArray;\n case 5:\n for (i = 0; i < length || (length < 0 && !readBreak()); ++i) {\n const key = decodeItem();\n retObject[key] = decodeItem();\n }\n return retObject;\n case 6:\n return tagger(decodeItem(), length);\n case 7:\n switch (length) {\n case 20:\n return false;\n case 21:\n return true;\n case 22:\n return null;\n case 23:\n return undefined;\n default:\n return simpleValue(length);\n }\n }\n }\n\n const ret = decodeItem();\n if (offset !== data.byteLength) {\n throw new Error(\"Remaining bytes\");\n }\n return ret;\n}\n\nexport const CBOR = {\n encode,\n decode,\n};\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2023 Laird Connectivity\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * @file mcumgr\n * @brief Provides MCU manager operation functions for the Xbit USB Shell.\n * This file is inspired by the MIT licensed mcumgr file originally\n * authored by Andras Barthazi (https://github.com/boogie/mcumgr-web),\n * updated to also support file upload/download over SMP.\n */\n\nimport { CBOR } from \"./cbor.js\";\nimport { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"mcumgr\", { log: false });\n\nexport const constants = {\n // Opcodes\n MGMT_OP_READ: 0,\n MGMT_OP_READ_RSP: 1,\n MGMT_OP_WRITE: 2,\n MGMT_OP_WRITE_RSP: 3,\n\n // Groups\n MGMT_GROUP_ID_OS: 0,\n MGMT_GROUP_ID_IMAGE: 1,\n MGMT_GROUP_ID_STAT: 2,\n MGMT_GROUP_ID_CONFIG: 3,\n MGMT_GROUP_ID_LOG: 4,\n MGMT_GROUP_ID_CRASH: 5,\n MGMT_GROUP_ID_SPLIT: 6,\n MGMT_GROUP_ID_RUN: 7,\n MGMT_GROUP_ID_FS: 8,\n MGMT_GROUP_ID_SHELL: 9,\n\n // OS group\n OS_MGMT_ID_ECHO: 0,\n OS_MGMT_ID_CONS_ECHO_CTRL: 1,\n OS_MGMT_ID_TASKSTAT: 2,\n OS_MGMT_ID_MPSTAT: 3,\n OS_MGMT_ID_DATETIME_STR: 4,\n OS_MGMT_ID_RESET: 5,\n\n // Image group\n IMG_MGMT_ID_STATE: 0,\n IMG_MGMT_ID_UPLOAD: 1,\n IMG_MGMT_ID_FILE: 2,\n IMG_MGMT_ID_CORELIST: 3,\n IMG_MGMT_ID_CORELOAD: 4,\n IMG_MGMT_ID_ERASE: 5,\n\n // Filesystem group\n FS_MGMT_ID_FILE: 0,\n};\n\nexport class MCUManager {\n constructor() {\n this._mtu = 256;\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n onMessage(callback) {\n this._messageCallback = callback;\n return this;\n }\n\n onImageUploadNext(callback) {\n this._imageUploadNextCallback = callback;\n return this;\n }\n\n onImageUploadProgress(callback) {\n this._imageUploadProgressCallback = callback;\n return this;\n }\n\n onImageUploadFinished(callback) {\n this._imageUploadFinishedCallback = callback;\n return this;\n }\n\n onFileUploadNext(callback) {\n this._fileUploadNextCallback = callback;\n return this;\n }\n\n onFileUploadProgress(callback) {\n this._fileUploadProgressCallback = callback;\n return this;\n }\n\n onFileUploadFinished(callback) {\n this._fileUploadFinishedCallback = callback;\n return this;\n }\n\n onFileDownloadNext(callback) {\n this._fileDownloadNextCallback = callback;\n return this;\n }\n\n onFileDownloadProgress(callback) {\n this._fileDownloadProgressCallback = callback;\n return this;\n }\n\n onFileDownloadFinished(callback) {\n this._fileDownloadFinishedCallback = callback;\n return this;\n }\n\n _getMessage(op, group, id, data) {\n const _flags = 0;\n let encodedData = [];\n if (typeof data !== \"undefined\") {\n encodedData = [...new Uint8Array(CBOR.encode(data))];\n }\n const lengthLo = encodedData.length & 255;\n const lengthHi = encodedData.length >> 8;\n const groupLo = group & 255;\n const groupHi = group >> 8;\n const message = [op, _flags, lengthHi, lengthLo, groupHi, groupLo, this._seq, id, ...encodedData];\n this._seq = (this._seq + 1) % 256;\n\n return message;\n }\n\n _notification(buffer) {\n _console.log(\"mcumgr - message received\");\n const message = new Uint8Array(buffer);\n this._buffer = new Uint8Array([...this._buffer, ...message]);\n const messageLength = this._buffer[2] * 256 + this._buffer[3];\n if (this._buffer.length < messageLength + 8) return;\n this._processMessage(this._buffer.slice(0, messageLength + 8));\n this._buffer = this._buffer.slice(messageLength + 8);\n }\n\n _processMessage(message) {\n const [op, , lengthHi, lengthLo, groupHi, groupLo, , id] = message;\n const data = CBOR.decode(message.slice(8).buffer);\n const length = lengthHi * 256 + lengthLo;\n const group = groupHi * 256 + groupLo;\n\n _console.log(\"mcumgr - Process Message - Group: \" + group + \", Id: \" + id + \", Off: \" + data.off);\n if (group === constants.MGMT_GROUP_ID_IMAGE && id === constants.IMG_MGMT_ID_UPLOAD && data.off) {\n this._uploadOffset = data.off;\n this._uploadNext();\n return;\n }\n if (\n op === constants.MGMT_OP_WRITE_RSP &&\n group === constants.MGMT_GROUP_ID_FS &&\n id === constants.FS_MGMT_ID_FILE &&\n data.off\n ) {\n this._uploadFileOffset = data.off;\n this._uploadFileNext();\n return;\n }\n if (op === constants.MGMT_OP_READ_RSP && group === constants.MGMT_GROUP_ID_FS && id === constants.FS_MGMT_ID_FILE) {\n this._downloadFileOffset += data.data.length;\n if (data.len != undefined) {\n this._downloadFileLength = data.len;\n }\n _console.log(\"downloaded \" + this._downloadFileOffset + \" bytes of \" + this._downloadFileLength);\n if (this._downloadFileLength > 0) {\n this._fileDownloadProgressCallback({\n percentage: Math.floor((this._downloadFileOffset / this._downloadFileLength) * 100),\n });\n }\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n this._downloadFileNext();\n return;\n }\n\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n }\n\n cmdReset() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_RESET);\n }\n\n smpEcho(message) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_ECHO, {\n d: message,\n });\n }\n\n cmdImageState() {\n return this._getMessage(constants.MGMT_OP_READ, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE);\n }\n\n cmdImageErase() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_ERASE, {});\n }\n\n cmdImageTest(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: false,\n });\n }\n\n cmdImageConfirm(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: true,\n });\n }\n\n _hash(image) {\n return crypto.subtle.digest(\"SHA-256\", image);\n }\n\n async _uploadNext() {\n if (!this._uploadImage) {\n return;\n }\n\n if (this._uploadOffset >= this._uploadImage.byteLength) {\n this._uploadIsInProgress = false;\n this._imageUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadOffset };\n if (this._uploadOffset === 0) {\n message.len = this._uploadImage.byteLength;\n message.sha = new Uint8Array(await this._hash(this._uploadImage));\n }\n this._imageUploadProgressCallback({\n percentage: Math.floor((this._uploadOffset / this._uploadImage.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead - 3 - 5;\n\n message.data = new Uint8Array(this._uploadImage.slice(this._uploadOffset, this._uploadOffset + length));\n\n this._uploadOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_IMAGE,\n constants.IMG_MGMT_ID_UPLOAD,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._imageUploadNextCallback({ packet });\n }\n async reset() {\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n async cmdUpload(image, slot = 0) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n\n this._uploadOffset = 0;\n this._uploadImage = image;\n this._uploadSlot = slot;\n\n this._uploadNext();\n }\n\n async cmdUploadFile(filebuf, destFilename) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n this._uploadFileOffset = 0;\n this._uploadFile = filebuf;\n this._uploadFilename = destFilename;\n\n this._uploadFileNext();\n }\n\n async _uploadFileNext() {\n _console.log(\"uploadFileNext - offset: \" + this._uploadFileOffset + \", length: \" + this._uploadFile.byteLength);\n\n if (this._uploadFileOffset >= this._uploadFile.byteLength) {\n this._uploadIsInProgress = false;\n this._fileUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadFileOffset };\n if (this._uploadFileOffset === 0) {\n message.len = this._uploadFile.byteLength;\n }\n message.name = this._uploadFilename;\n this._fileUploadProgressCallback({\n percentage: Math.floor((this._uploadFileOffset / this._uploadFile.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead;\n\n message.data = new Uint8Array(this._uploadFile.slice(this._uploadFileOffset, this._uploadFileOffset + length));\n\n this._uploadFileOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._fileUploadNextCallback({ packet });\n }\n\n async cmdDownloadFile(filename, destFilename) {\n if (this._downloadIsInProgress) {\n _console.error(\"Download is already in progress.\");\n return;\n }\n this._downloadIsInProgress = true;\n this._downloadFileOffset = 0;\n this._downloadFileLength = 0;\n this._downloadRemoteFilename = filename;\n this._downloadLocalFilename = destFilename;\n\n this._downloadFileNext();\n }\n\n async _downloadFileNext() {\n if (this._downloadFileLength > 0) {\n if (this._downloadFileOffset >= this._downloadFileLength) {\n this._downloadIsInProgress = false;\n this._fileDownloadFinishedCallback();\n return;\n }\n }\n\n const message = { off: this._downloadFileOffset };\n if (this._downloadFileOffset === 0) {\n message.name = this._downloadRemoteFilename;\n }\n\n const packet = this._getMessage(\n constants.MGMT_OP_READ,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n _console.log(\"mcumgr - _downloadNext: Message Length: \" + packet.length);\n this._fileDownloadNextCallback({ packet });\n }\n\n async imageInfo(image) {\n const info = {};\n const view = new Uint8Array(image);\n\n // check header length\n if (view.length < 32) {\n throw new Error(\"Invalid image (too short file)\");\n }\n\n // check MAGIC bytes 0x96f3b83d\n if (view[0] !== 0x3d || view[1] !== 0xb8 || view[2] !== 0xf3 || view[3] !== 0x96) {\n throw new Error(\"Invalid image (wrong magic bytes)\");\n }\n\n // check load address is 0x00000000\n if (view[4] !== 0x00 || view[5] !== 0x00 || view[6] !== 0x00 || view[7] !== 0x00) {\n throw new Error(\"Invalid image (wrong load address)\");\n }\n\n const headerSize = view[8] + view[9] * 2 ** 8;\n\n // check protected TLV area size is 0\n if (view[10] !== 0x00 || view[11] !== 0x00) {\n throw new Error(\"Invalid image (wrong protected TLV area size)\");\n }\n\n const imageSize = view[12] + view[13] * 2 ** 8 + view[14] * 2 ** 16 + view[15] * 2 ** 24;\n info.imageSize = imageSize;\n\n // check image size is correct\n if (view.length < imageSize + headerSize) {\n throw new Error(\"Invalid image (wrong image size)\");\n }\n\n // check flags is 0x00000000\n if (view[16] !== 0x00 || view[17] !== 0x00 || view[18] !== 0x00 || view[19] !== 0x00) {\n throw new Error(\"Invalid image (wrong flags)\");\n }\n\n const version = `${view[20]}.${view[21]}.${view[22] + view[23] * 2 ** 8}`;\n info.version = version;\n\n info.hash = [...new Uint8Array(await this._hash(image.slice(0, imageSize + 32)))]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return info;\n }\n}\n","import Device, { SendSmpMessageCallback } from \"./Device.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { MCUManager, constants } from \"./utils/mcumgr.js\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FirmwareManager\", { log: false });\n\nexport const FirmwareMessageTypes = [\"smp\"] as const;\nexport type FirmwareMessageType = (typeof FirmwareMessageTypes)[number];\n\nexport const FirmwareEventTypes = [\n ...FirmwareMessageTypes,\n \"firmwareImages\",\n \"firmwareUploadProgress\",\n \"firmwareStatus\",\n \"firmwareUploadComplete\",\n] as const;\nexport type FirmwareEventType = (typeof FirmwareEventTypes)[number];\n\nexport const FirmwareStatuses = [\"idle\", \"uploading\", \"uploaded\", \"pending\", \"testing\", \"erasing\"] as const;\nexport type FirmwareStatus = (typeof FirmwareStatuses)[number];\n\nexport interface FirmwareImage {\n slot: number;\n active: boolean;\n confirmed: boolean;\n pending: boolean;\n permanent: boolean;\n bootable: boolean;\n version: string;\n hash?: Uint8Array;\n empty?: boolean;\n}\n\nexport interface FirmwareEventMessages {\n smp: { dataView: DataView };\n firmwareImages: { firmwareImages: FirmwareImage[] };\n firmwareUploadProgress: { progress: number };\n firmwareStatus: { firmwareStatus: FirmwareStatus };\n //firmwareUploadComplete: {};\n}\n\nexport type FirmwareEventDispatcher = EventDispatcher<Device, FirmwareEventType, FirmwareEventMessages>;\n\nclass FirmwareManager {\n sendMessage!: SendSmpMessageCallback;\n\n constructor() {\n this.#assignMcuManagerCallbacks();\n autoBind(this);\n }\n\n eventDispatcher!: FirmwareEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n parseMessage(messageType: FirmwareMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"smp\":\n this.#mcuManager._notification(Array.from(new Uint8Array(dataView.buffer)));\n this.#dispatchEvent(\"smp\", { dataView });\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async uploadFirmware(file: FileLike) {\n _console.log(\"uploadFirmware\", file);\n\n const promise = this.waitForEvent(\"firmwareUploadComplete\");\n\n await this.getImages();\n\n const arrayBuffer = await getFileBuffer(file);\n const imageInfo = await this.#mcuManager.imageInfo(arrayBuffer);\n _console.log({ imageInfo });\n\n this.#mcuManager.cmdUpload(arrayBuffer, 1);\n\n this.#updateStatus(\"uploading\");\n\n await promise;\n }\n\n #status: FirmwareStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #updateStatus(newStatus: FirmwareStatus) {\n _console.assertEnumWithError(newStatus, FirmwareStatuses);\n if (this.#status == newStatus) {\n _console.log(`redundant firmwareStatus assignment \"${newStatus}\"`);\n return;\n }\n\n this.#status = newStatus;\n _console.log({ firmwareStatus: this.#status });\n this.#dispatchEvent(\"firmwareStatus\", { firmwareStatus: this.#status });\n }\n\n // COMMANDS\n\n #images!: FirmwareImage[];\n get images() {\n return this.#images;\n }\n #assertImages() {\n _console.assertWithError(this.#images, \"didn't get imageState\");\n }\n #assertValidImageIndex(imageIndex: number) {\n _console.assertTypeWithError(imageIndex, \"number\");\n _console.assertWithError(imageIndex == 0 || imageIndex == 1, \"imageIndex must be 0 or 1\");\n }\n async getImages() {\n const promise = this.waitForEvent(\"firmwareImages\");\n\n _console.log(\"getting firmware image state...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageState()).buffer);\n\n await promise;\n }\n\n async testImage(imageIndex: number = 1) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (!this.#images[imageIndex]) {\n _console.log(`image ${imageIndex} not found`);\n return;\n }\n if (this.#images[imageIndex].pending == true) {\n _console.log(`image ${imageIndex} is already pending`);\n return;\n }\n if (this.#images[imageIndex].empty) {\n _console.log(`image ${imageIndex} is empty`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"testing firmware image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageTest(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async eraseImage() {\n this.#assertImages();\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"erasing image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageErase()).buffer);\n\n this.#updateStatus(\"erasing\");\n\n await promise;\n await this.getImages();\n }\n\n async confirmImage(imageIndex: number = 0) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (this.#images[imageIndex].confirmed === true) {\n _console.log(`image ${imageIndex} is already confirmed`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"confirming image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageConfirm(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async echo(string: string) {\n _console.assertTypeWithError(string, \"string\");\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"sending echo...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.smpEcho(string)).buffer);\n\n await promise;\n }\n\n async reset() {\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"resetting...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdReset()).buffer);\n\n await promise;\n }\n\n // MTU\n #mtu!: number;\n get mtu() {\n return this.#mtu;\n }\n set mtu(newMtu: number) {\n this.#mtu = newMtu;\n this.#mcuManager._mtu = newMtu;\n }\n\n // MCUManager\n #mcuManager = new MCUManager();\n\n #assignMcuManagerCallbacks() {\n this.#mcuManager.onMessage(this.#onMcuMessage.bind(this));\n\n this.#mcuManager.onFileDownloadNext(this.#onMcuFileDownloadNext);\n this.#mcuManager.onFileDownloadProgress(this.#onMcuFileDownloadProgress.bind(this));\n this.#mcuManager.onFileDownloadFinished(this.#onMcuFileDownloadFinished.bind(this));\n\n this.#mcuManager.onFileUploadNext(this.#onMcuFileUploadNext.bind(this));\n this.#mcuManager.onFileUploadProgress(this.#onMcuFileUploadProgress.bind(this));\n this.#mcuManager.onFileUploadFinished(this.#onMcuFileUploadFinished.bind(this));\n\n this.#mcuManager.onImageUploadNext(this.#onMcuImageUploadNext.bind(this));\n this.#mcuManager.onImageUploadProgress(this.#onMcuImageUploadProgress.bind(this));\n this.#mcuManager.onImageUploadFinished(this.#onMcuImageUploadFinished.bind(this));\n }\n\n #onMcuMessage({ op, group, id, data, length }: { op: number; group: number; id: number; data: any; length: number }) {\n _console.log(\"onMcuMessage\", ...arguments);\n\n switch (group) {\n case constants.MGMT_GROUP_ID_OS:\n switch (id) {\n case constants.OS_MGMT_ID_ECHO:\n _console.log(`echo \"${data.r}\"`);\n break;\n case constants.OS_MGMT_ID_TASKSTAT:\n _console.table(data.tasks);\n break;\n case constants.OS_MGMT_ID_MPSTAT:\n _console.log(data);\n break;\n }\n break;\n case constants.MGMT_GROUP_ID_IMAGE:\n switch (id) {\n case constants.IMG_MGMT_ID_STATE:\n this.#onMcuImageState(data);\n }\n break;\n default:\n throw Error(`uncaught mcuMessage group ${group}`);\n }\n }\n\n #onMcuFileDownloadNext() {\n _console.log(\"onMcuFileDownloadNext\", ...arguments);\n }\n #onMcuFileDownloadProgress() {\n _console.log(\"onMcuFileDownloadProgress\", ...arguments);\n }\n #onMcuFileDownloadFinished() {\n _console.log(\"onMcuFileDownloadFinished\", ...arguments);\n }\n\n #onMcuFileUploadNext() {\n _console.log(\"onMcuFileUploadNext\");\n }\n #onMcuFileUploadProgress() {\n _console.log(\"onMcuFileUploadProgress\");\n }\n #onMcuFileUploadFinished() {\n _console.log(\"onMcuFileUploadFinished\");\n }\n\n #onMcuImageUploadNext({ packet }: { packet: number[] }) {\n _console.log(\"onMcuImageUploadNext\");\n this.sendMessage(Uint8Array.from(packet).buffer);\n }\n #onMcuImageUploadProgress({ percentage }: { percentage: number }) {\n const progress = percentage / 100;\n _console.log(\"onMcuImageUploadProgress\", ...arguments);\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress });\n }\n async #onMcuImageUploadFinished() {\n _console.log(\"onMcuImageUploadFinished\", ...arguments);\n\n await this.getImages();\n\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress: 100 });\n this.#dispatchEvent(\"firmwareUploadComplete\", {});\n }\n\n #onMcuImageState({ images }: { images?: FirmwareImage[] }) {\n if (images) {\n this.#images = images;\n _console.log(\"images\", this.#images);\n } else {\n _console.log(\"no images found\");\n return;\n }\n\n let newStatus: FirmwareStatus = \"idle\";\n\n if (this.#images.length == 2) {\n if (!this.#images[1].bootable) {\n _console.warn('Slot 1 has a invalid image. Click \"Erase Image\" to erase it or upload a different image');\n } else if (!this.#images[0].confirmed) {\n _console.log(\n 'Slot 0 has a valid image. Click \"Confirm Image\" to confirm it or wait and the device will swap images back.'\n );\n newStatus = \"testing\";\n } else {\n if (this.#images[1].pending) {\n _console.log(\"reset to upload to the new firmware image\");\n newStatus = \"pending\";\n } else {\n _console.log(\"Slot 1 has a valid image. run testImage() to test it or upload a different image.\");\n newStatus = \"uploaded\";\n }\n }\n }\n\n if (this.#images.length == 1) {\n this.#images.push({\n slot: 1,\n empty: true,\n version: \"Empty\",\n pending: false,\n confirmed: false,\n bootable: false,\n active: false,\n permanent: false,\n });\n\n _console.log(\"Select a firmware upload image to upload to slot 1.\");\n }\n\n this.#updateStatus(newStatus);\n this.#dispatchEvent(\"firmwareImages\", { firmwareImages: this.#images });\n }\n}\n\nexport default FirmwareManager;\n","import { ConnectionStatus } from \"./connection/BaseConnectionManager.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport Device, { BoundDeviceEventListeners, DeviceEventMap } from \"./Device.ts\";\nimport { DeviceType } from \"./InformationManager.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInBluefy, isInBrowser } from \"./utils/environment.ts\";\nimport EventDispatcher, {\n BoundEventListeners,\n Event,\n EventListenerMap,\n EventMap,\n} from \"./utils/EventDispatcher.ts\";\nimport { addEventListeners } from \"./utils/EventUtils.ts\";\n\nconst _console = createConsole(\"DeviceManager\", { log: false });\n\nexport interface LocalStorageDeviceInformation {\n type: DeviceType;\n bluetoothId: string;\n ipAddress?: string;\n isWifiSecure?: boolean;\n}\n\nexport interface LocalStorageConfiguration {\n devices: LocalStorageDeviceInformation[];\n}\n\nexport const DeviceManagerEventTypes = [\n \"deviceConnected\",\n \"deviceDisconnected\",\n \"deviceIsConnected\",\n \"availableDevices\",\n \"connectedDevices\",\n] as const;\nexport type DeviceManagerEventType = (typeof DeviceManagerEventTypes)[number];\n\ninterface DeviceManagerEventMessage {\n device: Device;\n}\nexport interface DeviceManagerEventMessages {\n deviceConnected: DeviceManagerEventMessage;\n deviceDisconnected: DeviceManagerEventMessage;\n deviceIsConnected: DeviceManagerEventMessage;\n availableDevices: { availableDevices: Device[] };\n connectedDevices: { connectedDevices: Device[] };\n}\n\nexport type DeviceManagerEventDispatcher = EventDispatcher<\n DeviceManager,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEventMap = EventMap<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEventListenerMap = EventListenerMap<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEvent = Event<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type BoundDeviceManagerEventListeners = BoundEventListeners<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\n\nclass DeviceManager {\n static readonly shared = new DeviceManager();\n\n constructor() {\n if (DeviceManager.shared && this != DeviceManager.shared) {\n throw Error(\"DeviceManager is a singleton - use DeviceManager.shared\");\n }\n\n if (this.CanUseLocalStorage) {\n this.UseLocalStorage = true;\n }\n }\n\n // DEVICE LISTENERS\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n getType: this.#onDeviceType.bind(this),\n isConnected: this.#OnDeviceIsConnected.bind(this),\n };\n /** @private */\n onDevice(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n }\n\n #onDeviceType(event: DeviceEventMap[\"getType\"]) {\n if (this.#UseLocalStorage) {\n this.#UpdateLocalStorageConfigurationForDevice(event.target);\n }\n }\n\n // CONNECTION STATUS\n /** @private */\n OnDeviceConnectionStatusUpdated(\n device: Device,\n connectionStatus: ConnectionStatus\n ) {\n if (\n connectionStatus == \"notConnected\" &&\n !device.canReconnect &&\n this.#AvailableDevices.includes(device)\n ) {\n const deviceIndex = this.#AvailableDevices.indexOf(device);\n this.AvailableDevices.splice(deviceIndex, 1);\n this.#DispatchAvailableDevices();\n }\n }\n\n // CONNECTED DEVICES\n\n #ConnectedDevices: Device[] = [];\n get ConnectedDevices() {\n return this.#ConnectedDevices;\n }\n\n #UseLocalStorage = false;\n get UseLocalStorage() {\n return this.#UseLocalStorage;\n }\n set UseLocalStorage(newUseLocalStorage) {\n this.#AssertLocalStorage();\n _console.assertTypeWithError(newUseLocalStorage, \"boolean\");\n this.#UseLocalStorage = newUseLocalStorage;\n if (this.#UseLocalStorage && !this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n }\n\n #DefaultLocalStorageConfiguration: LocalStorageConfiguration = {\n devices: [],\n };\n #LocalStorageConfiguration?: LocalStorageConfiguration;\n\n get CanUseLocalStorage() {\n return isInBrowser && window.localStorage;\n }\n\n #AssertLocalStorage() {\n _console.assertWithError(\n isInBrowser,\n \"localStorage is only available in the browser\"\n );\n _console.assertWithError(window.localStorage, \"localStorage not found\");\n }\n #LocalStorageKey = \"BS.Device\";\n #SaveToLocalStorage() {\n this.#AssertLocalStorage();\n localStorage.setItem(\n this.#LocalStorageKey,\n JSON.stringify(this.#LocalStorageConfiguration)\n );\n }\n async #LoadFromLocalStorage() {\n this.#AssertLocalStorage();\n let localStorageString = localStorage.getItem(this.#LocalStorageKey);\n if (typeof localStorageString != \"string\") {\n _console.log(\"no info found in localStorage\");\n this.#LocalStorageConfiguration = Object.assign(\n {},\n this.#DefaultLocalStorageConfiguration\n );\n this.#SaveToLocalStorage();\n return;\n }\n try {\n const configuration = JSON.parse(localStorageString);\n _console.log({ configuration });\n this.#LocalStorageConfiguration = configuration;\n if (this.CanGetDevices) {\n await this.GetDevices(); // redundant?\n }\n } catch (error) {\n _console.error(error);\n }\n }\n\n #UpdateLocalStorageConfigurationForDevice(device: Device) {\n if (device.connectionType != \"webBluetooth\") {\n _console.log(\"localStorage is only for webBluetooth devices\");\n return;\n }\n this.#AssertLocalStorage();\n const deviceInformationIndex =\n this.#LocalStorageConfiguration!.devices.findIndex(\n (deviceInformation) => {\n return deviceInformation.bluetoothId == device.bluetoothId;\n }\n );\n if (deviceInformationIndex == -1) {\n return;\n }\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex].type =\n device.type;\n this.#SaveToLocalStorage();\n }\n\n // AVAILABLE DEVICES\n #AvailableDevices: Device[] = [];\n get AvailableDevices() {\n return this.#AvailableDevices;\n }\n\n get CanGetDevices() {\n return isInBrowser && navigator.bluetooth?.getDevices;\n }\n /**\n * retrieves devices already connected via web bluetooth in other tabs/windows\n *\n * _only available on web-bluetooth enabled browsers_\n */\n async GetDevices(): Promise<Device[] | undefined> {\n if (!isInBrowser) {\n _console.warn(\"GetDevices is only available in the browser\");\n return;\n }\n\n if (!navigator.bluetooth) {\n _console.warn(\"bluetooth is not available in this browser\");\n return;\n }\n\n if (isInBluefy) {\n _console.warn(\"bluefy lists too many devices...\");\n return;\n }\n\n if (!navigator.bluetooth.getDevices) {\n _console.warn(\"bluetooth.getDevices() is not available in this browser\");\n return;\n }\n\n if (!this.CanGetDevices) {\n _console.log(\"CanGetDevices is false\");\n return;\n }\n\n if (!this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n\n const configuration = this.#LocalStorageConfiguration!;\n if (!configuration.devices || configuration.devices.length == 0) {\n _console.log(\"no devices found in configuration\");\n return;\n }\n\n const bluetoothDevices = await navigator.bluetooth.getDevices();\n\n _console.log({ bluetoothDevices });\n\n bluetoothDevices.forEach((bluetoothDevice) => {\n if (!bluetoothDevice.gatt) {\n return;\n }\n let deviceInformation = configuration.devices.find(\n (deviceInformation) =>\n bluetoothDevice.id == deviceInformation.bluetoothId\n );\n if (!deviceInformation) {\n return;\n }\n\n let existingConnectedDevice = this.ConnectedDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n\n const existingAvailableDevice = this.AvailableDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n if (existingAvailableDevice) {\n if (\n existingConnectedDevice &&\n existingConnectedDevice?.bluetoothId ==\n existingAvailableDevice.bluetoothId &&\n existingConnectedDevice != existingAvailableDevice\n ) {\n this.AvailableDevices[\n this.#AvailableDevices.indexOf(existingAvailableDevice)\n ] = existingConnectedDevice;\n }\n return;\n }\n\n if (existingConnectedDevice) {\n this.AvailableDevices.push(existingConnectedDevice);\n return;\n }\n\n const device = new Device();\n const connectionManager = new WebBluetoothConnectionManager();\n connectionManager.device = bluetoothDevice;\n if (bluetoothDevice.name) {\n device._informationManager.updateName(bluetoothDevice.name);\n }\n device._informationManager.updateType(deviceInformation.type);\n device.connectionManager = connectionManager;\n this.AvailableDevices.push(device);\n });\n this.#DispatchAvailableDevices();\n return this.AvailableDevices;\n }\n\n // STATIC EVENTLISTENERS\n\n #EventDispatcher: DeviceManagerEventDispatcher = new EventDispatcher(\n this as DeviceManager,\n DeviceManagerEventTypes\n );\n\n get AddEventListener() {\n return this.#EventDispatcher.addEventListener;\n }\n get #DispatchEvent() {\n return this.#EventDispatcher.dispatchEvent;\n }\n get RemoveEventListener() {\n return this.#EventDispatcher.removeEventListener;\n }\n get RemoveEventListeners() {\n return this.#EventDispatcher.removeEventListeners;\n }\n get RemoveAllEventListeners() {\n return this.#EventDispatcher.removeAllEventListeners;\n }\n\n #OnDeviceIsConnected(event: DeviceEventMap[\"isConnected\"]) {\n const { target: device } = event;\n if (device.isConnected) {\n if (!this.#ConnectedDevices.includes(device)) {\n _console.log(\"adding device\", device);\n this.#ConnectedDevices.push(device);\n if (this.UseLocalStorage && device.connectionType == \"webBluetooth\") {\n const deviceInformation: LocalStorageDeviceInformation = {\n type: device.type,\n bluetoothId: device.bluetoothId!,\n ipAddress: device.ipAddress,\n isWifiSecure: device.isWifiSecure,\n };\n const deviceInformationIndex =\n this.#LocalStorageConfiguration!.devices.findIndex(\n (_deviceInformation) =>\n _deviceInformation.bluetoothId == deviceInformation.bluetoothId\n );\n if (deviceInformationIndex == -1) {\n this.#LocalStorageConfiguration!.devices.push(deviceInformation);\n } else {\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex] =\n deviceInformation;\n }\n this.#SaveToLocalStorage();\n }\n this.#DispatchEvent(\"deviceConnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already included\");\n }\n } else {\n if (this.#ConnectedDevices.includes(device)) {\n _console.log(\"removing device\", device);\n this.#ConnectedDevices.splice(\n this.#ConnectedDevices.indexOf(device),\n 1\n );\n this.#DispatchEvent(\"deviceDisconnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already not included\");\n }\n }\n if (this.CanGetDevices) {\n this.GetDevices();\n }\n if (device.isConnected && !this.AvailableDevices.includes(device)) {\n const existingAvailableDevice = this.AvailableDevices.find(\n (_device) => _device.bluetoothId == device.bluetoothId\n );\n _console.log({ existingAvailableDevice });\n if (existingAvailableDevice) {\n this.AvailableDevices[\n this.AvailableDevices.indexOf(existingAvailableDevice)\n ] = device;\n } else {\n this.AvailableDevices.push(device);\n }\n this.#DispatchAvailableDevices();\n }\n this._CheckDeviceAvailability(device);\n }\n\n _CheckDeviceAvailability(device: Device) {\n if (\n !device.isConnected &&\n !device.isAvailable &&\n this.#AvailableDevices.includes(device)\n ) {\n _console.log(\"removing device from availableDevices...\");\n this.#AvailableDevices.splice(this.#AvailableDevices.indexOf(device), 1);\n this.#DispatchAvailableDevices();\n }\n }\n\n #DispatchAvailableDevices() {\n _console.log({ AvailableDevices: this.AvailableDevices });\n this.#DispatchEvent(\"availableDevices\", {\n availableDevices: this.AvailableDevices,\n });\n }\n #DispatchConnectedDevices() {\n _console.log({ ConnectedDevices: this.ConnectedDevices });\n this.#DispatchEvent(\"connectedDevices\", {\n connectedDevices: this.ConnectedDevices,\n });\n }\n}\n\nexport default DeviceManager.shared;\n","import { DeviceEventTypes } from \"../Device.ts\";\nimport {\n ConnectionMessageType,\n ConnectionMessageTypes,\n} from \"../connection/BaseConnectionManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\n\nconst _console = createConsole(\"ServerUtils\", { log: false });\n\nexport const ServerMessageTypes = [\n \"isScanningAvailable\",\n \"isScanning\",\n \"startScan\",\n \"stopScan\",\n \"discoveredDevice\",\n \"discoveredDevices\",\n \"expiredDiscoveredDevice\",\n \"connectToDevice\",\n \"disconnectFromDevice\",\n \"connectedDevices\",\n \"deviceMessage\",\n \"requiredDeviceInformation\",\n] as const;\nexport type ServerMessageType = (typeof ServerMessageTypes)[number];\n\nexport const DeviceMessageTypes = [\n \"connectionStatus\",\n \"batteryLevel\",\n \"deviceInformation\",\n \"rx\",\n \"smp\",\n] as const;\nexport type DeviceMessageType = (typeof DeviceMessageTypes)[number];\n\n// MESSAGING\n\nexport type MessageLike =\n | number\n | number[]\n | ArrayBufferLike\n | DataView\n | boolean\n | string\n | any;\n\nexport interface Message<MessageType extends string> {\n type: MessageType;\n data?: MessageLike | MessageLike[];\n}\n\nexport function createMessage<MessageType extends string>(\n enumeration: readonly MessageType[],\n ...messages: (Message<MessageType> | MessageType)[]\n) {\n _console.log(\"createMessage\", ...messages);\n\n const messageBuffers = messages.map((message) => {\n if (typeof message == \"string\") {\n message = { type: message };\n }\n\n if (message.data != undefined) {\n if (!Array.isArray(message.data)) {\n message.data = [message.data];\n }\n } else {\n message.data = [];\n }\n\n const messageDataArrayBuffer = concatenateArrayBuffers(...message.data);\n const messageDataArrayBufferByteLength = messageDataArrayBuffer.byteLength;\n\n _console.assertEnumWithError(message.type, enumeration);\n const messageTypeEnum = enumeration.indexOf(message.type);\n\n const messageDataLengthDataView = new DataView(new ArrayBuffer(2));\n messageDataLengthDataView.setUint16(\n 0,\n messageDataArrayBufferByteLength,\n true\n );\n\n return concatenateArrayBuffers(\n messageTypeEnum,\n messageDataLengthDataView,\n messageDataArrayBuffer\n );\n });\n _console.log(\"messageBuffers\", ...messageBuffers);\n return concatenateArrayBuffers(...messageBuffers);\n}\n\nexport type ServerMessage = ServerMessageType | Message<ServerMessageType>;\nexport function createServerMessage(...messages: ServerMessage[]) {\n _console.log(\"createServerMessage\", ...messages);\n return createMessage(ServerMessageTypes, ...messages);\n}\n\nexport type DeviceMessage = DeviceEventType | Message<DeviceEventType>;\nexport function createDeviceMessage(...messages: DeviceMessage[]) {\n _console.log(\"createDeviceMessage\", ...messages);\n return createMessage(DeviceEventTypes, ...messages);\n}\n\nexport type ClientDeviceMessage =\n | ConnectionMessageType\n | Message<ConnectionMessageType>;\nexport function createClientDeviceMessage(...messages: ClientDeviceMessage[]) {\n _console.log(\"createClientDeviceMessage\", ...messages);\n return createMessage(ConnectionMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const isScanningAvailableRequestMessage = createServerMessage(\n \"isScanningAvailable\"\n);\nexport const isScanningRequestMessage = createServerMessage(\"isScanning\");\nexport const startScanRequestMessage = createServerMessage(\"startScan\");\nexport const stopScanRequestMessage = createServerMessage(\"stopScan\");\nexport const discoveredDevicesMessage =\n createServerMessage(\"discoveredDevices\");\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createMessage, Message } from \"../ServerUtils.ts\";\n\nconst _console = createConsole(\"WebSocketUtils\", { log: false });\n\nexport const webSocketPingTimeout = 30_000;\nexport const webSocketReconnectTimeout = 3_000;\n\nexport const WebSocketMessageTypes = [\"ping\", \"pong\", \"serverMessage\"] as const;\nexport type WebSocketMessageType = (typeof WebSocketMessageTypes)[number];\n\nexport type WebSocketMessage =\n | WebSocketMessageType\n | Message<WebSocketMessageType>;\nexport function createWebSocketMessage(...messages: WebSocketMessage[]) {\n _console.log(\"createWebSocketMessage\", ...messages);\n return createMessage(WebSocketMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const webSocketPingMessage = createWebSocketMessage(\"ping\");\nexport const webSocketPongMessage = createWebSocketMessage(\"pong\");\n","import { DeviceInformationTypes } from \"../../DeviceInformationManager.ts\";\nimport {\n createMessage,\n Message,\n MessageLike,\n} from \"../../server/ServerUtils.ts\";\nimport { webSocketPingTimeout } from \"../../server/websocket/WebSocketUtils.ts\";\nimport { createConsole } from \"../../utils/Console.ts\";\nimport {\n addEventListeners,\n removeEventListeners,\n} from \"../../utils/EventUtils.ts\";\nimport { parseMessage } from \"../../utils/ParseUtils.ts\";\nimport Timer from \"../../utils/Timer.ts\";\nimport BaseConnectionManager, {\n ConnectionType,\n} from \"../BaseConnectionManager.ts\";\nimport type * as ws from \"ws\";\n\nconst _console = createConsole(\"WebSocketConnectionManager\", { log: false });\n\nconst WebSocketMessageTypes = [\n \"ping\",\n \"pong\",\n \"batteryLevel\",\n \"deviceInformation\",\n \"message\",\n] as const;\ntype WebSocketMessageType = (typeof WebSocketMessageTypes)[number];\n\ntype WebSocketMessage = WebSocketMessageType | Message<WebSocketMessageType>;\nfunction createWebSocketMessage(...messages: WebSocketMessage[]) {\n _console.log(\"createWebSocketMessage\", ...messages);\n return createMessage(WebSocketMessageTypes, ...messages);\n}\n\nconst WebSocketDeviceInformationMessageTypes: WebSocketMessageType[] = [\n \"deviceInformation\",\n \"batteryLevel\",\n];\n\nclass WebSocketConnectionManager extends BaseConnectionManager {\n #bluetoothId?: string;\n get bluetoothId() {\n return this.#bluetoothId ?? \"\";\n }\n\n defaultMtu = 2 ** 10;\n\n constructor(\n ipAddress: string,\n isSecure: boolean = false,\n bluetoothId?: string\n ) {\n super();\n this.ipAddress = ipAddress;\n this.isSecure = isSecure;\n this.mtu = this.defaultMtu;\n this.#bluetoothId = bluetoothId;\n }\n\n get isAvailable() {\n return true;\n }\n\n static get isSupported() {\n return true;\n }\n static get type(): ConnectionType {\n return \"webSocket\";\n }\n\n // WEBSOCKET\n #webSocket?: WebSocket;\n get webSocket() {\n return this.#webSocket;\n }\n set webSocket(newWebSocket) {\n if (this.#webSocket == newWebSocket) {\n _console.log(\"redundant webSocket assignment\");\n return;\n }\n\n _console.log(\"assigning webSocket\", newWebSocket);\n\n if (this.#webSocket) {\n removeEventListeners(this.#webSocket, this.#boundWebSocketEventListeners);\n if (this.#webSocket.readyState == this.#webSocket.OPEN) {\n this.#webSocket.close();\n }\n }\n\n if (newWebSocket) {\n addEventListeners(newWebSocket, this.#boundWebSocketEventListeners);\n }\n this.#webSocket = newWebSocket;\n\n _console.log(\"assigned webSocket\");\n }\n\n // IP ADDRESS\n #ipAddress!: string;\n get ipAddress() {\n return this.#ipAddress;\n }\n set ipAddress(newIpAddress) {\n this.assertIsNotConnected();\n if (this.#ipAddress == newIpAddress) {\n _console.log(`redundnant ipAddress assignment \"${newIpAddress}\"`);\n return;\n }\n this.#ipAddress = newIpAddress;\n _console.log(`updated ipAddress to \"${this.ipAddress}\"`);\n }\n\n // IS SECURE\n #isSecure = false;\n get isSecure() {\n return this.#isSecure;\n }\n set isSecure(newIsSecure) {\n this.assertIsNotConnected();\n if (this.#isSecure == newIsSecure) {\n _console.log(`redundant isSecure assignment ${newIsSecure}`);\n return;\n }\n this.#isSecure = newIsSecure;\n _console.log(`updated isSecure to \"${this.isSecure}\"`);\n }\n\n // URL\n get url() {\n return `${this.isSecure ? \"wss\" : \"ws\"}://${this.ipAddress}/ws`;\n }\n\n // CONNECTION\n async connect() {\n await super.connect();\n try {\n this.webSocket = new WebSocket(this.url);\n } catch (error) {\n _console.error(\"error connecting to webSocket\", error);\n this.status = \"notConnected\";\n }\n }\n async disconnect() {\n await super.disconnect();\n _console.log(\"closing websocket\");\n this.#pingTimer.stop();\n this.#webSocket?.close();\n }\n\n get canReconnect() {\n return Boolean(this.webSocket);\n }\n async reconnect() {\n await super.reconnect();\n this.webSocket = new WebSocket(this.url);\n }\n\n // BASE CONNECTION MANAGER\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n _console.error(\"smp not supported on webSockets\");\n }\n\n async sendTxData(data: ArrayBuffer) {\n await super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n this.#sendWebSocketMessage({ type: \"message\", data });\n }\n\n // WEBSOCKET MESSAGING\n #sendMessage(message: MessageLike) {\n this.assertIsConnected();\n _console.log(\"sending webSocket message\", message);\n this.#webSocket!.send(message);\n this.#pingTimer.restart();\n }\n\n #sendWebSocketMessage(...messages: WebSocketMessage[]) {\n this.#sendMessage(createWebSocketMessage(...messages));\n }\n\n // WEBSOCKET EVENTS\n #boundWebSocketEventListeners: { [eventType: string]: Function } = {\n open: this.#onWebSocketOpen.bind(this),\n message: this.#onWebSocketMessage.bind(this),\n close: this.#onWebSocketClose.bind(this),\n error: this.#onWebSocketError.bind(this),\n };\n\n #onWebSocketOpen(event: ws.Event) {\n _console.log(\"webSocket.open\", event);\n this.#pingTimer.start();\n this.status = \"connected\";\n this.#requestDeviceInformation();\n }\n async #onWebSocketMessage(event: ws.MessageEvent) {\n // this.#pingTimer.restart();\n //@ts-expect-error\n const arrayBuffer = await event.data.arrayBuffer();\n const dataView = new DataView(arrayBuffer);\n _console.log(`webSocket.message (${dataView.byteLength} bytes)`);\n this.#parseWebSocketMessage(dataView);\n }\n #onWebSocketClose(event: ws.CloseEvent) {\n _console.log(\"webSocket.close\", event);\n this.status = \"notConnected\";\n this.#pingTimer.stop();\n }\n #onWebSocketError(event: ws.ErrorEvent) {\n _console.error(\"webSocket.error\", event);\n }\n\n // PARSING\n #parseWebSocketMessage(dataView: DataView) {\n parseMessage(\n dataView,\n WebSocketMessageTypes,\n this.#onMessage.bind(this),\n null,\n true\n );\n }\n\n #onMessage(messageType: WebSocketMessageType, dataView: DataView) {\n _console.log(\n `received \"${messageType}\" message (${dataView.byteLength} bytes)`\n );\n switch (messageType) {\n case \"ping\":\n this.#pong();\n break;\n case \"pong\":\n break;\n case \"batteryLevel\":\n this.onMessageReceived?.(\"batteryLevel\", dataView);\n break;\n case \"deviceInformation\":\n parseMessage(\n dataView,\n DeviceInformationTypes,\n (deviceInformationType, dataView) => {\n this.onMessageReceived!(deviceInformationType, dataView);\n }\n );\n break;\n case \"message\":\n this.parseRxMessage(dataView);\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // PING\n #pingTimer = new Timer(this.#ping.bind(this), webSocketPingTimeout - 1_000);\n #ping() {\n _console.log(\"pinging\");\n this.#sendWebSocketMessage(\"ping\");\n }\n #pong() {\n _console.log(\"ponging\");\n this.#sendWebSocketMessage(\"pong\");\n }\n\n // DEVICE INFORMATION\n #requestDeviceInformation() {\n this.#sendWebSocketMessage(...WebSocketDeviceInformationMessageTypes);\n }\n\n remove() {\n super.remove();\n this.webSocket = undefined;\n }\n}\n\nexport default WebSocketConnectionManager;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher, {\n BoundEventListeners,\n Event,\n EventListenerMap,\n EventMap,\n} from \"./utils/EventDispatcher.ts\";\nimport BaseConnectionManager, {\n TxMessage,\n TxRxMessageType,\n ConnectionStatus,\n ConnectionMessageType,\n MetaConnectionMessageTypes,\n BatteryLevelMessageTypes,\n ConnectionEventTypes,\n ConnectionStatusEventMessages,\n ConnectionTypes,\n ConnectionType,\n ConnectOptions,\n} from \"./connection/BaseConnectionManager.ts\";\nimport { isInBrowser, isInNode } from \"./utils/environment.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport SensorConfigurationManager, {\n SendSensorConfigurationMessageCallback,\n SensorConfiguration,\n SensorConfigurationEventDispatcher,\n SensorConfigurationEventMessages,\n SensorConfigurationEventTypes,\n SensorConfigurationMessageType,\n SensorConfigurationMessageTypes,\n} from \"./sensor/SensorConfigurationManager.ts\";\nimport SensorDataManager, {\n SensorDataEventMessages,\n SensorDataEventTypes,\n SensorDataMessageType,\n SensorDataMessageTypes,\n SensorType,\n ContinuousSensorTypes,\n SensorDataEventDispatcher,\n RequiredPressureMessageTypes,\n} from \"./sensor/SensorDataManager.ts\";\nimport VibrationManager, {\n SendVibrationMessageCallback,\n VibrationConfiguration,\n VibrationEventDispatcher,\n VibrationEventTypes,\n VibrationMessageType,\n VibrationMessageTypes,\n} from \"./vibration/VibrationManager.ts\";\nimport FileTransferManager, {\n FileTransferEventTypes,\n FileTransferEventMessages,\n FileTransferEventDispatcher,\n SendFileTransferMessageCallback,\n FileTransferMessageTypes,\n FileTransferMessageType,\n FileType,\n FileTypes,\n RequiredFileTransferMessageTypes,\n} from \"./FileTransferManager.ts\";\nimport TfliteManager, {\n TfliteEventTypes,\n TfliteEventMessages,\n TfliteEventDispatcher,\n SendTfliteMessageCallback,\n TfliteMessageTypes,\n TfliteMessageType,\n TfliteSensorTypes,\n TfliteFileConfiguration,\n TfliteSensorType,\n RequiredTfliteMessageTypes,\n} from \"./TfliteManager.ts\";\nimport FirmwareManager, {\n FirmwareEventDispatcher,\n FirmwareEventMessages,\n FirmwareEventTypes,\n FirmwareMessageType,\n FirmwareMessageTypes,\n} from \"./FirmwareManager.ts\";\nimport DeviceInformationManager, {\n DeviceInformationEventDispatcher,\n DeviceInformationEventTypes,\n DeviceInformationType,\n DeviceInformationTypes,\n DeviceInformationEventMessages,\n} from \"./DeviceInformationManager.ts\";\nimport InformationManager, {\n DeviceType,\n InformationEventDispatcher,\n InformationEventTypes,\n InformationMessageType,\n InformationMessageTypes,\n InformationEventMessages,\n SendInformationMessageCallback,\n} from \"./InformationManager.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport DeviceManager from \"./DeviceManager.ts\";\nimport CameraManager, {\n CameraEventDispatcher,\n CameraEventMessages,\n CameraEventTypes,\n CameraMessageType,\n CameraMessageTypes,\n RequiredCameraMessageTypes,\n SendCameraMessageCallback,\n} from \"./CameraManager.ts\";\nimport WifiManager, {\n RequiredWifiMessageTypes,\n SendWifiMessageCallback,\n WifiEventDispatcher,\n WifiEventMessages,\n WifiEventTypes,\n WifiMessageType,\n WifiMessageTypes,\n} from \"./WifiManager.ts\";\nimport WebSocketConnectionManager from \"./connection/websocket/WebSocketConnectionManager.ts\";\nimport ClientConnectionManager from \"./connection/ClientConnectionManager.ts\";\n\n/** NODE_START */\nimport UDPConnectionManager from \"./connection/udp/UDPConnectionManager.ts\";\n/** NODE_END */\n\nconst _console = createConsole(\"Device\", { log: false });\n\nexport const DeviceEventTypes = [\n \"connectionMessage\",\n ...ConnectionEventTypes,\n ...MetaConnectionMessageTypes,\n ...BatteryLevelMessageTypes,\n ...InformationEventTypes,\n ...DeviceInformationEventTypes,\n ...SensorConfigurationEventTypes,\n ...SensorDataEventTypes,\n ...VibrationEventTypes,\n ...FileTransferEventTypes,\n ...TfliteEventTypes,\n ...WifiEventTypes,\n ...CameraEventTypes,\n ...FirmwareEventTypes,\n] as const;\nexport type DeviceEventType = (typeof DeviceEventTypes)[number];\n\nexport interface DeviceEventMessages\n extends ConnectionStatusEventMessages,\n DeviceInformationEventMessages,\n InformationEventMessages,\n SensorDataEventMessages,\n SensorConfigurationEventMessages,\n TfliteEventMessages,\n FileTransferEventMessages,\n WifiEventMessages,\n CameraEventMessages,\n FirmwareEventMessages {\n batteryLevel: { batteryLevel: number };\n connectionMessage: { messageType: ConnectionMessageType; dataView: DataView };\n}\n\nexport type SendMessageCallback<MessageType extends string> = (\n messages?: { type: MessageType; data?: ArrayBuffer }[],\n sendImmediately?: boolean\n) => Promise<void>;\n\nexport type SendSmpMessageCallback = (data: ArrayBuffer) => Promise<void>;\n\nexport type DeviceEventDispatcher = EventDispatcher<\n Device,\n DeviceEventType,\n DeviceEventMessages\n>;\nexport type DeviceEvent = Event<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventMap = EventMap<\n Device,\n DeviceEventType,\n DeviceEventMessages\n>;\nexport type DeviceEventListenerMap = EventListenerMap<\n Device,\n DeviceEventType,\n DeviceEventMessages\n>;\nexport type BoundDeviceEventListeners = BoundEventListeners<\n Device,\n DeviceEventType,\n DeviceEventMessages\n>;\n\nexport const RequiredInformationConnectionMessages: TxRxMessageType[] = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getId\",\n \"getMtu\",\n\n \"getName\",\n \"getType\",\n \"getCurrentTime\",\n \"getSensorConfiguration\",\n \"getSensorScalars\",\n\n \"getVibrationLocations\",\n\n \"getFileTypes\",\n\n \"isWifiAvailable\",\n];\n\nclass Device {\n get bluetoothId() {\n return this.#connectionManager?.bluetoothId;\n }\n\n get isAvailable() {\n return this.#connectionManager?.isAvailable;\n }\n\n constructor() {\n this.#deviceInformationManager.eventDispatcher = this\n .#eventDispatcher as DeviceInformationEventDispatcher;\n\n this._informationManager.sendMessage = this\n .sendTxMessages as SendInformationMessageCallback;\n this._informationManager.eventDispatcher = this\n .#eventDispatcher as InformationEventDispatcher;\n\n this.#sensorConfigurationManager.sendMessage = this\n .sendTxMessages as SendSensorConfigurationMessageCallback;\n this.#sensorConfigurationManager.eventDispatcher = this\n .#eventDispatcher as SensorConfigurationEventDispatcher;\n\n this.#sensorDataManager.eventDispatcher = this\n .#eventDispatcher as SensorDataEventDispatcher;\n\n this.#vibrationManager.sendMessage = this\n .sendTxMessages as SendVibrationMessageCallback;\n this.#vibrationManager.eventDispatcher = this\n .#eventDispatcher as VibrationEventDispatcher;\n\n this.#tfliteManager.sendMessage = this\n .sendTxMessages as SendTfliteMessageCallback;\n this.#tfliteManager.eventDispatcher = this\n .#eventDispatcher as TfliteEventDispatcher;\n\n this.#fileTransferManager.sendMessage = this\n .sendTxMessages as SendFileTransferMessageCallback;\n this.#fileTransferManager.eventDispatcher = this\n .#eventDispatcher as FileTransferEventDispatcher;\n\n this.#wifiManager.sendMessage = this\n .sendTxMessages as SendWifiMessageCallback;\n this.#wifiManager.eventDispatcher = this\n .#eventDispatcher as WifiEventDispatcher;\n\n this.#cameraManager.sendMessage = this\n .sendTxMessages as SendCameraMessageCallback;\n this.#cameraManager.eventDispatcher = this\n .#eventDispatcher as CameraEventDispatcher;\n\n this.#firmwareManager.sendMessage = this\n .sendSmpMessage as SendSmpMessageCallback;\n this.#firmwareManager.eventDispatcher = this\n .#eventDispatcher as FirmwareEventDispatcher;\n\n this.addEventListener(\"getMtu\", () => {\n this.#firmwareManager.mtu = this.mtu;\n this.#fileTransferManager.mtu = this.mtu;\n this.connectionManager!.mtu = this.mtu;\n });\n this.addEventListener(\"getSensorConfiguration\", () => {\n if (this.connectionStatus != \"connecting\") {\n return;\n }\n if (this.sensorTypes.includes(\"pressure\")) {\n _console.log(\"requesting required pressure information\");\n const messages = RequiredPressureMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendTxMessages(messages, false);\n } else {\n _console.log(\"don't need to request pressure infomration\");\n }\n\n if (this.sensorTypes.includes(\"camera\")) {\n _console.log(\"requesting required camera information\");\n const messages = RequiredCameraMessageTypes.map((messageType) => ({\n type: messageType,\n }));\n this.sendTxMessages(messages, false);\n } else {\n _console.log(\"don't need to request camera infomration\");\n }\n });\n this.addEventListener(\"getFileTypes\", () => {\n if (this.connectionStatus != \"connecting\") {\n return;\n }\n if (this.fileTypes.length > 0) {\n this.#fileTransferManager.requestRequiredInformation();\n }\n if (this.fileTypes.includes(\"tflite\")) {\n this.#tfliteManager.requestRequiredInformation();\n }\n });\n this.addEventListener(\"isWifiAvailable\", () => {\n if (this.connectionStatus != \"connecting\") {\n return;\n }\n if (this.connectionType == \"client\" && !isInNode) {\n return;\n }\n if (this.isWifiAvailable) {\n if (this.connectionType != \"client\") {\n this.#wifiManager.requestRequiredInformation();\n }\n }\n });\n DeviceManager.onDevice(this);\n if (isInBrowser) {\n window.addEventListener(\"beforeunload\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n if (isInNode) {\n /** can add more node leave handlers https://gist.github.com/hyrious/30a878f6e6a057f09db87638567cb11a */\n process.on(\"exit\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n }\n\n static #DefaultConnectionManager(): BaseConnectionManager {\n return new WebBluetoothConnectionManager();\n }\n\n #eventDispatcher: DeviceEventDispatcher = new EventDispatcher(\n this as Device,\n DeviceEventTypes\n );\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // CONNECTION MANAGER\n\n #connectionManager?: BaseConnectionManager;\n get connectionManager() {\n return this.#connectionManager;\n }\n set connectionManager(newConnectionManager) {\n if (this.connectionManager == newConnectionManager) {\n _console.log(\"same connectionManager is already assigned\");\n return;\n }\n\n if (this.connectionManager) {\n this.connectionManager.remove();\n }\n if (newConnectionManager) {\n newConnectionManager.onStatusUpdated =\n this.#onConnectionStatusUpdated.bind(this);\n newConnectionManager.onMessageReceived =\n this.#onConnectionMessageReceived.bind(this);\n newConnectionManager.onMessagesReceived =\n this.#onConnectionMessagesReceived.bind(this);\n }\n\n this.#connectionManager = newConnectionManager;\n _console.log(\"assigned new connectionManager\", this.#connectionManager);\n\n this._informationManager.connectionType = this.connectionType;\n }\n async #sendTxMessages(messages?: TxMessage[], sendImmediately?: boolean) {\n await this.#connectionManager?.sendTxMessages(messages, sendImmediately);\n }\n private sendTxMessages = this.#sendTxMessages.bind(this);\n\n async connect(options?: ConnectOptions) {\n _console.log(\"connect options\", options);\n if (options) {\n switch (options.type) {\n case \"webBluetooth\":\n if (this.connectionType != \"webBluetooth\") {\n this.connectionManager = new WebBluetoothConnectionManager();\n }\n break;\n case \"webSocket\":\n {\n let createConnectionManager = false;\n if (this.connectionType == \"webSocket\") {\n const connectionManager = this\n .connectionManager as WebSocketConnectionManager;\n if (\n connectionManager.ipAddress != options.ipAddress ||\n connectionManager.isSecure != options.isWifiSecure\n ) {\n createConnectionManager = true;\n }\n } else {\n createConnectionManager = true;\n }\n if (createConnectionManager) {\n this.connectionManager = new WebSocketConnectionManager(\n options.ipAddress,\n options.isWifiSecure,\n this.bluetoothId\n );\n }\n }\n\n break;\n case \"udp\":\n {\n let createConnectionManager = false;\n if (this.connectionType == \"udp\") {\n const connectionManager = this\n .connectionManager as UDPConnectionManager;\n if (connectionManager.ipAddress != options.ipAddress) {\n createConnectionManager = true;\n }\n this.reconnectOnDisconnection = true;\n } else {\n createConnectionManager = true;\n }\n if (createConnectionManager) {\n this.connectionManager = new UDPConnectionManager(\n options.ipAddress,\n this.bluetoothId\n );\n }\n }\n break;\n }\n }\n if (!this.connectionManager) {\n this.connectionManager = Device.#DefaultConnectionManager();\n }\n this.#clear();\n\n if (options?.type == \"client\") {\n _console.assertWithError(\n this.connectionType == \"client\",\n \"expected clientConnectionManager\"\n );\n const clientConnectionManager = this\n .connectionManager as ClientConnectionManager;\n clientConnectionManager.subType = options.subType;\n return clientConnectionManager.connect();\n }\n _console.log(\"connectionManager type\", this.connectionManager.type);\n return this.connectionManager.connect();\n }\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n #didReceiveMessageTypes(messageTypes: ConnectionMessageType[]) {\n return messageTypes.every((messageType) => {\n const hasConnectionMessage =\n this.latestConnectionMessages.has(messageType);\n if (!hasConnectionMessage) {\n _console.log(`didn't receive \"${messageType}\" message`);\n }\n return hasConnectionMessage;\n });\n }\n get #hasRequiredInformation() {\n let hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredInformationConnectionMessages\n );\n if (hasRequiredInformation && this.sensorTypes.includes(\"pressure\")) {\n hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredPressureMessageTypes\n );\n }\n if (hasRequiredInformation && this.isWifiAvailable) {\n hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredWifiMessageTypes\n );\n }\n if (hasRequiredInformation && this.fileTypes.length > 0) {\n hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredFileTransferMessageTypes\n );\n }\n if (hasRequiredInformation && this.fileTypes.includes(\"tflite\")) {\n hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredTfliteMessageTypes\n );\n }\n if (hasRequiredInformation && this.hasCamera) {\n hasRequiredInformation = this.#didReceiveMessageTypes(\n RequiredCameraMessageTypes\n );\n }\n return hasRequiredInformation;\n }\n #requestRequiredInformation() {\n _console.log(\"requesting required information\");\n const messages: TxMessage[] = RequiredInformationConnectionMessages.map(\n (messageType) => ({\n type: messageType,\n })\n );\n this.#sendTxMessages(messages);\n }\n\n get canReconnect() {\n return this.connectionManager?.canReconnect;\n }\n #assertCanReconnect() {\n _console.assertWithError(this.canReconnect, \"cannot reconnect to device\");\n }\n async reconnect() {\n this.#assertCanReconnect();\n this.#clear();\n return this.connectionManager?.reconnect();\n }\n\n static async Connect() {\n const device = new Device();\n await device.connect();\n return device;\n }\n\n static #ReconnectOnDisconnection = false;\n static get ReconnectOnDisconnection() {\n return this.#ReconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#ReconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n #reconnectOnDisconnection = Device.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this.#reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n #reconnectIntervalId?: NodeJS.Timeout | number;\n\n get connectionType() {\n return this.connectionManager?.type;\n }\n async disconnect() {\n this.#assertIsConnected();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.addEventListener(\n \"isConnected\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n\n return this.connectionManager!.disconnect();\n }\n\n toggleConnection() {\n if (this.isConnected) {\n this.disconnect();\n } else if (this.canReconnect) {\n try {\n this.reconnect();\n } catch (error) {\n _console.error(\"error trying to reconnect\", error);\n this.connect();\n }\n } else {\n this.connect();\n }\n }\n\n get connectionStatus(): ConnectionStatus {\n switch (this.#connectionManager?.status) {\n case \"connected\":\n return this.isConnected ? \"connected\" : \"connecting\";\n case \"notConnected\":\n case \"connecting\":\n case \"disconnecting\":\n return this.#connectionManager.status;\n default:\n return \"notConnected\";\n }\n }\n get isConnectionBusy() {\n return (\n this.connectionStatus == \"connecting\" ||\n this.connectionStatus == \"disconnecting\"\n );\n }\n\n #onConnectionStatusUpdated(connectionStatus: ConnectionStatus) {\n _console.log({ connectionStatus });\n\n if (connectionStatus == \"notConnected\") {\n this.#clearConnection();\n\n if (this.canReconnect && this.reconnectOnDisconnection) {\n _console.log(\"starting reconnect interval...\");\n this.#reconnectIntervalId = setInterval(() => {\n _console.log(\"attempting reconnect...\");\n this.reconnect();\n }, 1000);\n }\n } else {\n if (this.#reconnectIntervalId != undefined) {\n _console.log(\"clearing reconnect interval\");\n clearInterval(this.#reconnectIntervalId);\n this.#reconnectIntervalId = undefined;\n }\n }\n\n this.#checkConnection();\n\n if (connectionStatus == \"connected\" && !this.#isConnected) {\n if (this.connectionType != \"client\") {\n this.#requestRequiredInformation();\n }\n }\n\n DeviceManager.OnDeviceConnectionStatusUpdated(this, connectionStatus);\n }\n\n #dispatchConnectionEvents(includeIsConnected: boolean = false) {\n this.#dispatchEvent(\"connectionStatus\", {\n connectionStatus: this.connectionStatus,\n });\n this.#dispatchEvent(this.connectionStatus, {});\n if (includeIsConnected) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n }\n #checkConnection() {\n this.#isConnected =\n Boolean(this.connectionManager?.isConnected) &&\n this.#hasRequiredInformation &&\n this._informationManager.isCurrentTimeSet;\n\n switch (this.connectionStatus) {\n case \"connected\":\n if (this.#isConnected) {\n this.#dispatchConnectionEvents(true);\n }\n break;\n case \"notConnected\":\n this.#dispatchConnectionEvents(true);\n break;\n default:\n this.#dispatchConnectionEvents(false);\n break;\n }\n }\n\n #clear() {\n this.#clearConnection();\n this._informationManager.clear();\n this.#deviceInformationManager.clear();\n this.#tfliteManager.clear();\n this.#wifiManager.clear();\n this.#cameraManager.clear();\n }\n #clearConnection() {\n this.connectionManager?.clear();\n this.latestConnectionMessages.clear();\n }\n\n #onConnectionMessageReceived(\n messageType: ConnectionMessageType,\n dataView: DataView\n ) {\n _console.log({ messageType, dataView });\n switch (messageType) {\n case \"batteryLevel\":\n const batteryLevel = dataView.getUint8(0);\n _console.log(\"received battery level\", { batteryLevel });\n this.#updateBatteryLevel(batteryLevel);\n break;\n\n default:\n if (\n FileTransferMessageTypes.includes(\n messageType as FileTransferMessageType\n )\n ) {\n this.#fileTransferManager.parseMessage(\n messageType as FileTransferMessageType,\n dataView\n );\n } else if (\n TfliteMessageTypes.includes(messageType as TfliteMessageType)\n ) {\n this.#tfliteManager.parseMessage(\n messageType as TfliteMessageType,\n dataView\n );\n } else if (\n SensorDataMessageTypes.includes(messageType as SensorDataMessageType)\n ) {\n this.#sensorDataManager.parseMessage(\n messageType as SensorDataMessageType,\n dataView\n );\n } else if (\n FirmwareMessageTypes.includes(messageType as FirmwareMessageType)\n ) {\n this.#firmwareManager.parseMessage(\n messageType as FirmwareMessageType,\n dataView\n );\n } else if (\n DeviceInformationTypes.includes(messageType as DeviceInformationType)\n ) {\n this.#deviceInformationManager.parseMessage(\n messageType as DeviceInformationType,\n dataView\n );\n } else if (\n InformationMessageTypes.includes(\n messageType as InformationMessageType\n )\n ) {\n this._informationManager.parseMessage(\n messageType as InformationMessageType,\n dataView\n );\n } else if (\n SensorConfigurationMessageTypes.includes(\n messageType as SensorConfigurationMessageType\n )\n ) {\n this.#sensorConfigurationManager.parseMessage(\n messageType as SensorConfigurationMessageType,\n dataView\n );\n } else if (\n VibrationMessageTypes.includes(messageType as VibrationMessageType)\n ) {\n this.#vibrationManager.parseMessage(\n messageType as VibrationMessageType,\n dataView\n );\n } else if (WifiMessageTypes.includes(messageType as WifiMessageType)) {\n this.#wifiManager.parseMessage(\n messageType as WifiMessageType,\n dataView\n );\n } else if (\n CameraMessageTypes.includes(messageType as CameraMessageType)\n ) {\n this.#cameraManager.parseMessage(\n messageType as CameraMessageType,\n dataView\n );\n } else {\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n this.latestConnectionMessages.set(messageType, dataView);\n if (messageType.startsWith(\"set\")) {\n this.latestConnectionMessages.set(\n // @ts-expect-error\n messageType.replace(\"set\", \"get\"),\n dataView\n );\n }\n this.#dispatchEvent(\"connectionMessage\", { messageType, dataView });\n }\n #onConnectionMessagesReceived() {\n if (!this.isConnected && this.#hasRequiredInformation) {\n this.#checkConnection();\n }\n if (this.connectionStatus == \"notConnected\") {\n return;\n }\n this.#sendTxMessages();\n }\n\n latestConnectionMessages: Map<ConnectionMessageType, DataView> = new Map();\n\n // DEVICE INFORMATION\n #deviceInformationManager = new DeviceInformationManager();\n get deviceInformation() {\n return this.#deviceInformationManager.information;\n }\n\n // BATTERY LEVEL\n #batteryLevel = 0;\n get batteryLevel() {\n return this.#batteryLevel;\n }\n #updateBatteryLevel(updatedBatteryLevel: number) {\n _console.assertTypeWithError(updatedBatteryLevel, \"number\");\n if (this.#batteryLevel == updatedBatteryLevel) {\n _console.log(`duplicate batteryLevel assignment ${updatedBatteryLevel}`);\n return;\n }\n this.#batteryLevel = updatedBatteryLevel;\n _console.log({ updatedBatteryLevel: this.#batteryLevel });\n this.#dispatchEvent(\"batteryLevel\", { batteryLevel: this.#batteryLevel });\n }\n\n // INFORMATION\n /** @private */\n _informationManager = new InformationManager();\n\n get id() {\n return this._informationManager.id;\n }\n\n get isCharging() {\n return this._informationManager.isCharging;\n }\n get batteryCurrent() {\n return this._informationManager.batteryCurrent;\n }\n get getBatteryCurrent() {\n return this._informationManager.getBatteryCurrent;\n }\n\n get name() {\n return this._informationManager.name;\n }\n get setName() {\n return this._informationManager.setName;\n }\n\n get type() {\n return this._informationManager.type;\n }\n get setType() {\n return this._informationManager.setType;\n }\n\n get isInsole() {\n return this._informationManager.isInsole;\n }\n get isGlove() {\n return this._informationManager.isGlove;\n }\n get side() {\n return this._informationManager.side;\n }\n\n get mtu() {\n return this._informationManager.mtu;\n }\n\n // SENSOR TYPES\n get sensorTypes() {\n return Object.keys(this.sensorConfiguration) as SensorType[];\n }\n get continuousSensorTypes() {\n return ContinuousSensorTypes.filter((sensorType) =>\n this.sensorTypes.includes(sensorType)\n );\n }\n\n // SENSOR CONFIGURATION\n\n #sensorConfigurationManager = new SensorConfigurationManager();\n\n get sensorConfiguration() {\n return this.#sensorConfigurationManager.configuration;\n }\n\n async setSensorConfiguration(\n newSensorConfiguration: SensorConfiguration,\n clearRest?: boolean\n ) {\n await this.#sensorConfigurationManager.setConfiguration(\n newSensorConfiguration,\n clearRest\n );\n }\n\n async clearSensorConfiguration() {\n return this.#sensorConfigurationManager.clearSensorConfiguration();\n }\n\n static #ClearSensorConfigurationOnLeave = true;\n static get ClearSensorConfigurationOnLeave() {\n return this.#ClearSensorConfigurationOnLeave;\n }\n static set ClearSensorConfigurationOnLeave(\n newClearSensorConfigurationOnLeave\n ) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#ClearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n #clearSensorConfigurationOnLeave = Device.ClearSensorConfigurationOnLeave;\n get clearSensorConfigurationOnLeave() {\n return this.#clearSensorConfigurationOnLeave;\n }\n set clearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#clearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n // PRESSURE\n get numberOfPressureSensors() {\n return this.#sensorDataManager.pressureSensorDataManager.numberOfSensors;\n }\n\n // SENSOR DATA\n #sensorDataManager = new SensorDataManager();\n resetPressureRange() {\n this.#sensorDataManager.pressureSensorDataManager.resetRange();\n }\n\n // VIBRATION\n get vibrationLocations() {\n return this.#vibrationManager.vibrationLocations;\n }\n\n #vibrationManager = new VibrationManager();\n async triggerVibration(\n vibrationConfigurations: VibrationConfiguration[],\n sendImmediately?: boolean\n ) {\n this.#vibrationManager.triggerVibration(\n vibrationConfigurations,\n sendImmediately\n );\n }\n\n // FILE TRANSFER\n #fileTransferManager = new FileTransferManager();\n\n get fileTypes() {\n return this.#fileTransferManager.fileTypes;\n }\n get maxFileLength() {\n return this.#fileTransferManager.maxLength;\n }\n get validFileTypes() {\n return FileTypes.filter((fileType) => {\n if (fileType.includes(\"wifi\") && !this.isWifiAvailable) {\n return false;\n }\n return true;\n });\n }\n\n async sendFile(fileType: FileType, file: FileLike) {\n _console.assertWithError(\n this.validFileTypes.includes(fileType),\n `invalid fileType ${fileType}`\n );\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.send(fileType, file);\n await promise;\n }\n async receiveFile(fileType: FileType) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.receive(fileType);\n await promise;\n }\n\n get fileTransferStatus() {\n return this.#fileTransferManager.status;\n }\n\n cancelFileTransfer() {\n this.#fileTransferManager.cancel();\n }\n\n // TFLITE\n #tfliteManager = new TfliteManager();\n\n get tfliteName() {\n return this.#tfliteManager.name;\n }\n get setTfliteName() {\n return this.#tfliteManager.setName;\n }\n\n async sendTfliteConfiguration(configuration: TfliteFileConfiguration) {\n configuration.type = \"tflite\";\n this.#tfliteManager.sendConfiguration(configuration, false);\n const didSendFile = await this.#fileTransferManager.send(\n configuration.type,\n configuration.file\n );\n if (!didSendFile) {\n this.#sendTxMessages();\n }\n }\n\n // TFLITE MODEL CONFIG\n get tfliteTask() {\n return this.#tfliteManager.task;\n }\n get setTfliteTask() {\n return this.#tfliteManager.setTask;\n }\n get tfliteSampleRate() {\n return this.#tfliteManager.sampleRate;\n }\n get setTfliteSampleRate() {\n return this.#tfliteManager.setSampleRate;\n }\n get tfliteSensorTypes() {\n return this.#tfliteManager.sensorTypes;\n }\n get allowedTfliteSensorTypes() {\n return this.sensorTypes.filter((sensorType) =>\n TfliteSensorTypes.includes(sensorType as TfliteSensorType)\n );\n }\n get setTfliteSensorTypes() {\n return this.#tfliteManager.setSensorTypes;\n }\n get tfliteIsReady() {\n return this.#tfliteManager.isReady;\n }\n\n // TFLITE INFERENCING\n\n get tfliteInferencingEnabled() {\n return this.#tfliteManager.inferencingEnabled;\n }\n get setTfliteInferencingEnabled() {\n return this.#tfliteManager.setInferencingEnabled;\n }\n async enableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(true);\n }\n async disableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(false);\n }\n get toggleTfliteInferencing() {\n return this.#tfliteManager.toggleInferencingEnabled;\n }\n\n // TFLITE INFERENCE CONFIG\n\n get tfliteCaptureDelay() {\n return this.#tfliteManager.captureDelay;\n }\n get setTfliteCaptureDelay() {\n return this.#tfliteManager.setCaptureDelay;\n }\n get tfliteThreshold() {\n return this.#tfliteManager.threshold;\n }\n get setTfliteThreshold() {\n return this.#tfliteManager.setThreshold;\n }\n\n // FIRMWARE MANAGER\n\n #firmwareManager = new FirmwareManager();\n\n get canUpdateFirmware() {\n return this.#connectionManager?.canUpdateFirmware;\n }\n #assertCanUpdateFirmware() {\n _console.assertWithError(this.canUpdateFirmware, \"can't update firmware\");\n }\n\n #sendSmpMessage(data: ArrayBuffer) {\n this.#assertCanUpdateFirmware();\n return this.#connectionManager!.sendSmpMessage(data);\n }\n private sendSmpMessage = this.#sendSmpMessage.bind(this);\n\n get uploadFirmware() {\n this.#assertCanUpdateFirmware();\n return this.#firmwareManager.uploadFirmware;\n }\n get canReset() {\n return this.canUpdateFirmware;\n }\n async reset() {\n _console.assertWithError(\n this.canReset,\n \"reset is not enabled for this device\"\n );\n await this.#firmwareManager.reset();\n return this.#connectionManager!.disconnect();\n }\n get firmwareStatus() {\n return this.#firmwareManager.status;\n }\n get getFirmwareImages() {\n this.#assertCanUpdateFirmware();\n return this.#firmwareManager.getImages;\n }\n get firmwareImages() {\n return this.#firmwareManager.images;\n }\n get eraseFirmwareImage() {\n this.#assertCanUpdateFirmware();\n return this.#firmwareManager.eraseImage;\n }\n get confirmFirmwareImage() {\n this.#assertCanUpdateFirmware();\n return this.#firmwareManager.confirmImage;\n }\n get testFirmwareImage() {\n this.#assertCanUpdateFirmware();\n return this.#firmwareManager.testImage;\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n\n this.#fileTransferManager.isServerSide = this.isServerSide;\n }\n\n // UKATON\n get isUkaton() {\n return this.deviceInformation.modelNumber.includes(\"Ukaton\");\n }\n\n // WIFI MANAGER\n #wifiManager = new WifiManager();\n get isWifiAvailable() {\n return this.#wifiManager.isWifiAvailable;\n }\n get wifiSSID() {\n return this.#wifiManager.wifiSSID;\n }\n async setWifiSSID(newWifiSSID: string) {\n return this.#wifiManager.setWifiSSID(newWifiSSID);\n }\n get wifiPassword() {\n return this.#wifiManager.wifiPassword;\n }\n async setWifiPassword(newWifiPassword: string) {\n return this.#wifiManager.setWifiPassword(newWifiPassword);\n }\n get isWifiConnected() {\n return this.#wifiManager.isWifiConnected;\n }\n get ipAddress() {\n return this.#wifiManager.ipAddress;\n }\n get wifiConnectionEnabled() {\n return this.#wifiManager.wifiConnectionEnabled;\n }\n get enableWifiConnection() {\n return this.#wifiManager.enableWifiConnection;\n }\n get setWifiConnectionEnabled() {\n return this.#wifiManager.setWifiConnectionEnabled;\n }\n get disableWifiConnection() {\n return this.#wifiManager.disableWifiConnection;\n }\n get toggleWifiConnection() {\n return this.#wifiManager.toggleWifiConnection;\n }\n get isWifiSecure() {\n return this.#wifiManager.isWifiSecure;\n }\n\n async reconnectViaWebSockets() {\n _console.assertWithError(this.isWifiConnected, \"wifi is not connected\");\n _console.assertWithError(\n this.connectionType != \"webSocket\",\n \"already connected via webSockets\"\n );\n _console.assertTypeWithError(this.ipAddress, \"string\");\n _console.log(\"reconnecting via websockets...\");\n await this.disconnect();\n await this.connect({\n type: \"webSocket\",\n ipAddress: this.ipAddress!,\n isWifiSecure: this.isWifiSecure,\n });\n }\n\n async reconnectViaUDP() {\n _console.assertWithError(isInNode, \"udp is only available in node\");\n _console.assertWithError(this.isWifiConnected, \"wifi is not connected\");\n _console.assertWithError(\n this.connectionType != \"udp\",\n \"already connected via udp\"\n );\n _console.assertTypeWithError(this.ipAddress, \"string\");\n _console.log(\"reconnecting via udp...\");\n await this.disconnect();\n await this.connect({\n type: \"udp\",\n ipAddress: this.ipAddress!,\n });\n }\n\n // CAMERA MANAGER\n #cameraManager = new CameraManager();\n get hasCamera() {\n return this.sensorTypes.includes(\"camera\");\n }\n get cameraStatus() {\n return this.#cameraManager.cameraStatus;\n }\n #assertHasCamera() {\n _console.assertWithError(this.hasCamera, \"camera not available\");\n }\n async takePicture() {\n this.#assertHasCamera();\n await this.#cameraManager.takePicture();\n }\n async focusCamera() {\n this.#assertHasCamera();\n await this.#cameraManager.focus();\n }\n async stopCamera() {\n this.#assertHasCamera();\n await this.#cameraManager.stop();\n }\n async wakeCamera() {\n this.#assertHasCamera();\n await this.#cameraManager.wake();\n }\n async sleepCamera() {\n this.#assertHasCamera();\n await this.#cameraManager.sleep();\n }\n\n get cameraConfiguration() {\n return this.#cameraManager.cameraConfiguration;\n }\n get availableCameraConfigurationTypes() {\n return this.#cameraManager.availableCameraConfigurationTypes;\n }\n get cameraConfigurationRanges() {\n return this.#cameraManager.cameraConfigurationRanges;\n }\n\n get setCameraConfiguration() {\n return this.#cameraManager.setCameraConfiguration;\n }\n}\n\nexport default Device;\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport {\n PressureData,\n PressureSensorPosition,\n PressureSensorValue,\n} from \"../sensor/PressureSensorDataManager.ts\";\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\nimport { Side, Sides } from \"../InformationManager.ts\";\nimport { DeviceEventMap } from \"../Device.ts\";\nimport { RangeHelper } from \"../BS.ts\";\n\nconst _console = createConsole(\"DevicePairPressureSensorDataManager\", {\n log: false,\n});\n\nexport type DevicePairRawPressureData = { [side in Side]: PressureData };\n\nexport interface DevicePairPressureData {\n sensors: { [key in Side]: PressureSensorValue[] };\n scaledSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface DevicePairPressureDataEventMessage {\n pressure: DevicePairPressureData;\n}\n\nexport interface DevicePairPressureDataEventMessages {\n pressure: DevicePairPressureDataEventMessage;\n}\n\nclass DevicePairPressureSensorDataManager {\n #rawPressure: Partial<DevicePairRawPressureData> = {};\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n #normalizedSumRangeHelper = new RangeHelper();\n\n constructor() {\n this.resetPressureRange();\n }\n\n resetPressureRange() {\n this.#centerOfPressureHelper.reset();\n this.#normalizedSumRangeHelper.reset();\n }\n\n onDevicePressureData(event: DeviceEventMap[\"pressure\"]) {\n const { pressure } = event.message;\n const { side } = event.target;\n _console.log({ pressure, side });\n this.#rawPressure[side] = pressure;\n if (this.#hasAllPressureData) {\n return this.#updatePressureData();\n } else {\n _console.log(\"doesn't have all pressure data yet...\");\n }\n }\n\n get #hasAllPressureData() {\n return Sides.every((side) => side in this.#rawPressure);\n }\n\n #updatePressureData() {\n const pressure: DevicePairPressureData = {\n scaledSum: 0,\n normalizedSum: 0,\n sensors: { left: [], right: [] },\n };\n\n Sides.forEach((side) => {\n const sidePressure = this.#rawPressure[side]!;\n pressure.scaledSum += sidePressure.scaledSum;\n //pressure.normalizedSum += this.#rawPressure[side]!.normalizedSum;\n });\n pressure.normalizedSum +=\n this.#normalizedSumRangeHelper.updateAndGetNormalization(\n pressure.scaledSum,\n false\n );\n\n if (pressure.scaledSum > 0) {\n pressure.center = { x: 0, y: 0 };\n Sides.forEach((side) => {\n const sidePressure = this.#rawPressure[side]!;\n\n if (false) {\n const sidePressureWeight =\n sidePressure.scaledSum / pressure.scaledSum;\n if (sidePressureWeight > 0) {\n if (sidePressure.normalizedCenter?.y != undefined) {\n pressure.center!.y +=\n sidePressure.normalizedCenter!.y * sidePressureWeight;\n }\n if (side == \"right\") {\n pressure.center!.x = sidePressureWeight;\n }\n }\n } else {\n sidePressure.sensors.forEach((sensor) => {\n const _sensor: PressureSensorValue = { ...sensor };\n _sensor.weightedValue = sensor.scaledValue / pressure.scaledSum;\n let { x, y } = sensor.position;\n x /= 2;\n if (side == \"right\") {\n x += 0.5;\n }\n _sensor.position = { x, y };\n pressure.center!.x += _sensor.position.x * _sensor.weightedValue;\n pressure.center!.y += _sensor.position.y * _sensor.weightedValue;\n pressure.sensors[side].push(_sensor);\n });\n }\n });\n\n pressure.normalizedCenter =\n this.#centerOfPressureHelper.updateAndGetNormalization(\n pressure.center,\n false\n );\n }\n\n _console.log({ devicePairPressure: pressure });\n\n return pressure;\n }\n}\n\nexport default DevicePairPressureSensorDataManager;\n","import DevicePairPressureSensorDataManager, {\n DevicePairPressureDataEventMessages,\n} from \"./DevicePairPressureSensorDataManager.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { Side } from \"../InformationManager.ts\";\nimport { SensorType } from \"../sensor/SensorDataManager.ts\";\nimport { DeviceEventMap } from \"../Device.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport DevicePair from \"./DevicePair.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"DevicePairSensorDataManager\", { log: false });\n\nexport const DevicePairSensorTypes = [\"pressure\", \"sensorData\"] as const;\nexport type DevicePairSensorType = (typeof DevicePairSensorTypes)[number];\n\nexport const DevicePairSensorDataEventTypes = DevicePairSensorTypes;\nexport type DevicePairSensorDataEventType = (typeof DevicePairSensorDataEventTypes)[number];\n\nexport type DevicePairSensorDataTimestamps = { [side in Side]: number };\n\ninterface BaseDevicePairSensorDataEventMessage {\n timestamps: DevicePairSensorDataTimestamps;\n}\n\ntype BaseDevicePairSensorDataEventMessages = DevicePairPressureDataEventMessages;\ntype _DevicePairSensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseDevicePairSensorDataEventMessages, \"sensorType\">,\n BaseDevicePairSensorDataEventMessage\n>;\n\nexport type DevicePairSensorDataEventMessage = ValueOf<_DevicePairSensorDataEventMessages>;\ninterface AnyDevicePairSensorDataEventMessages {\n sensorData: DevicePairSensorDataEventMessage;\n}\nexport type DevicePairSensorDataEventMessages = _DevicePairSensorDataEventMessages &\n AnyDevicePairSensorDataEventMessages;\n\nexport type DevicePairSensorDataEventDispatcher = EventDispatcher<\n DevicePair,\n DevicePairSensorDataEventType,\n DevicePairSensorDataEventMessages\n>;\n\nclass DevicePairSensorDataManager {\n eventDispatcher!: DevicePairSensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #timestamps: { [sensorType in SensorType]?: Partial<DevicePairSensorDataTimestamps> } = {};\n\n pressureSensorDataManager = new DevicePairPressureSensorDataManager();\n resetPressureRange() {\n this.pressureSensorDataManager.resetPressureRange();\n }\n\n onDeviceSensorData(event: DeviceEventMap[\"sensorData\"]) {\n const { timestamp, sensorType } = event.message;\n\n _console.log({ sensorType, timestamp, event });\n\n if (!this.#timestamps[sensorType]) {\n this.#timestamps[sensorType] = {};\n }\n this.#timestamps[sensorType]![event.target.side] = timestamp;\n\n let value;\n switch (sensorType) {\n case \"pressure\":\n value = this.pressureSensorDataManager.onDevicePressureData(event as unknown as DeviceEventMap[\"pressure\"]);\n break;\n default:\n _console.log(`uncaught sensorType \"${sensorType}\"`);\n break;\n }\n\n if (value) {\n const timestamps = Object.assign({}, this.#timestamps[sensorType]) as DevicePairSensorDataTimestamps;\n // @ts-expect-error\n this.dispatchEvent(sensorType as DevicePairSensorDataEventType, { sensorType, timestamps, [sensorType]: value });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, timestamps, [sensorType]: value });\n } else {\n _console.log(\"no value received\");\n }\n }\n}\n\nexport default DevicePairSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport EventDispatcher, {\n BoundEventListeners,\n Event,\n EventListenerMap,\n EventMap,\n} from \"../utils/EventDispatcher.ts\";\nimport {\n addEventListeners,\n removeEventListeners,\n} from \"../utils/EventUtils.ts\";\nimport Device, {\n DeviceEvent,\n DeviceEventType,\n DeviceEventMessages,\n DeviceEventTypes,\n BoundDeviceEventListeners,\n DeviceEventMap,\n} from \"../Device.ts\";\nimport DevicePairSensorDataManager, {\n DevicePairSensorDataEventDispatcher,\n} from \"./DevicePairSensorDataManager.ts\";\nimport { capitalizeFirstCharacter } from \"../utils/stringUtils.ts\";\nimport { Side, Sides } from \"../InformationManager.ts\";\nimport { VibrationConfiguration } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfiguration } from \"../sensor/SensorConfigurationManager.ts\";\nimport {\n DevicePairSensorDataEventMessages,\n DevicePairSensorDataEventTypes,\n} from \"./DevicePairSensorDataManager.ts\";\nimport {\n AddPrefixToInterfaceKeys,\n ExtendInterfaceValues,\n KeyOf,\n} from \"../utils/TypeScriptUtils.ts\";\nimport DeviceManager from \"../DeviceManager.ts\";\n\nconst _console = createConsole(\"DevicePair\", { log: false });\n\ninterface BaseDevicePairDeviceEventMessage {\n device: Device;\n side: Side;\n}\ntype DevicePairDeviceEventMessages = ExtendInterfaceValues<\n AddPrefixToInterfaceKeys<DeviceEventMessages, \"device\">,\n BaseDevicePairDeviceEventMessage\n>;\ntype DevicePairDeviceEventType = KeyOf<DevicePairDeviceEventMessages>;\nfunction getDevicePairDeviceEventType(deviceEventType: DeviceEventType) {\n return `device${capitalizeFirstCharacter(\n deviceEventType\n )}` as DevicePairDeviceEventType;\n}\nconst DevicePairDeviceEventTypes = DeviceEventTypes.map((eventType) =>\n getDevicePairDeviceEventType(eventType)\n) as DevicePairDeviceEventType[];\n\nexport const DevicePairConnectionEventTypes = [\"isConnected\"] as const;\nexport type DevicePairConnectionEventType =\n (typeof DevicePairConnectionEventTypes)[number];\n\nexport interface DevicePairConnectionEventMessages {\n isConnected: { isConnected: boolean };\n}\n\nexport const DevicePairEventTypes = [\n ...DevicePairConnectionEventTypes,\n ...DevicePairSensorDataEventTypes,\n ...DevicePairDeviceEventTypes,\n] as const;\nexport type DevicePairEventType = (typeof DevicePairEventTypes)[number];\n\nexport type DevicePairEventMessages = DevicePairConnectionEventMessages &\n DevicePairSensorDataEventMessages &\n DevicePairDeviceEventMessages;\n\nexport type DevicePairEventDispatcher = EventDispatcher<\n DevicePair,\n DevicePairEventType,\n DevicePairEventMessages\n>;\nexport type DevicePairEventMap = EventMap<\n DevicePair,\n DeviceEventType,\n DevicePairEventMessages\n>;\nexport type DevicePairEventListenerMap = EventListenerMap<\n DevicePair,\n DeviceEventType,\n DevicePairEventMessages\n>;\nexport type DevicePairEvent = Event<\n DevicePair,\n DeviceEventType,\n DevicePairEventMessages\n>;\nexport type BoundDevicePairEventListeners = BoundEventListeners<\n DevicePair,\n DeviceEventType,\n DevicePairEventMessages\n>;\n\nexport const DevicePairTypes = [\"insoles\", \"gloves\"] as const;\nexport type DevicePairType = (typeof DevicePairTypes)[number];\n\nclass DevicePair {\n constructor(type: DevicePairType) {\n this.#type = type;\n this.#sensorDataManager.eventDispatcher = this\n .#eventDispatcher as DevicePairSensorDataEventDispatcher;\n }\n\n get sides() {\n return Sides;\n }\n\n #type: DevicePairType;\n get type() {\n return this.#type;\n }\n\n #eventDispatcher: DevicePairEventDispatcher = new EventDispatcher(\n this as DevicePair,\n DevicePairEventTypes\n );\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // SIDES\n #left?: Device;\n get left() {\n return this.#left;\n }\n\n #right?: Device;\n get right() {\n return this.#right;\n }\n\n get isConnected() {\n return Sides.every((side) => this[side]?.isConnected);\n }\n get isPartiallyConnected() {\n return Sides.some((side) => this[side]?.isConnected);\n }\n get isHalfConnected() {\n return this.isPartiallyConnected && !this.isConnected;\n }\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"devicePair must be connected\");\n }\n\n #isDeviceCorrectType(device: Device) {\n switch (this.type) {\n case \"insoles\":\n return device.isInsole;\n case \"gloves\":\n return device.isGlove;\n }\n }\n\n assignDevice(device: Device) {\n if (!this.#isDeviceCorrectType(device)) {\n _console.log(\n `device is incorrect type ${device.type} for ${this.type} devicePair`\n );\n return;\n }\n const side = device.side;\n\n const currentDevice = this[side];\n\n if (device == currentDevice) {\n _console.log(\"device already assigned\");\n return;\n }\n\n if (currentDevice) {\n this.#removeDeviceEventListeners(currentDevice);\n }\n this.#addDeviceEventListeners(device);\n\n switch (side) {\n case \"left\":\n this.#left = device;\n break;\n case \"right\":\n this.#right = device;\n break;\n }\n\n _console.log(`assigned ${side} ${this.type} device`, device);\n\n this.resetPressureRange();\n\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n this.#dispatchEvent(\"deviceIsConnected\", {\n device,\n isConnected: device.isConnected,\n side,\n });\n\n return currentDevice;\n }\n\n #addDeviceEventListeners(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n device.addEventListener(\n // @ts-expect-error\n deviceEventType,\n this.#redispatchDeviceEvent.bind(this)\n );\n });\n }\n #removeDeviceEventListeners(device: Device) {\n removeEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n device.removeEventListener(\n // @ts-expect-error\n deviceEventType,\n this.#redispatchDeviceEvent.bind(this)\n );\n });\n }\n\n #removeDevice(device: Device) {\n const foundDevice = Sides.some((side) => {\n if (this[side] != device) {\n return false;\n }\n\n _console.log(`removing ${side} ${this.type} device`, device);\n removeEventListeners(device, this.#boundDeviceEventListeners);\n\n switch (side) {\n case \"left\":\n this.#left = undefined;\n break;\n case \"right\":\n this.#right = undefined;\n break;\n }\n\n return true;\n });\n if (foundDevice) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n return foundDevice;\n }\n\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n isConnected: this.#onDeviceIsConnected.bind(this),\n sensorData: this.#onDeviceSensorData.bind(this),\n getType: this.#onDeviceType.bind(this),\n };\n\n #redispatchDeviceEvent(deviceEvent: DeviceEvent) {\n const { type, target: device, message } = deviceEvent;\n this.#dispatchEvent(getDevicePairDeviceEventType(type), {\n ...message,\n device,\n side: device.side,\n });\n }\n\n #onDeviceIsConnected(deviceEvent: DeviceEventMap[\"isConnected\"]) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n\n #onDeviceType(deviceEvent: DeviceEventMap[\"getType\"]) {\n const { target: device } = deviceEvent;\n if (this[device.side] == device) {\n return;\n }\n const foundDevice = this.#removeDevice(device);\n if (!foundDevice) {\n return;\n }\n this.assignDevice(device);\n }\n\n // SENSOR CONFIGURATION\n async setSensorConfiguration(sensorConfiguration: SensorConfiguration) {\n for (let i = 0; i < Sides.length; i++) {\n const side = Sides[i];\n if (this[side]?.isConnected) {\n await this[side].setSensorConfiguration(sensorConfiguration);\n }\n }\n }\n\n // SENSOR DATA\n #sensorDataManager = new DevicePairSensorDataManager();\n #onDeviceSensorData(deviceEvent: DeviceEventMap[\"sensorData\"]) {\n if (this.isConnected) {\n this.#sensorDataManager.onDeviceSensorData(deviceEvent);\n }\n }\n resetPressureRange() {\n Sides.forEach((side) => this[side]?.resetPressureRange());\n this.#sensorDataManager.resetPressureRange();\n }\n\n // VIBRATION\n async triggerVibration(\n vibrationConfigurations: VibrationConfiguration[],\n sendImmediately?: boolean\n ) {\n const promises = Sides.map((side) => {\n return this[side]?.triggerVibration(\n vibrationConfigurations,\n sendImmediately\n );\n }).filter(Boolean);\n return Promise.allSettled(promises);\n }\n\n // SHARED INSTANCES\n static #insoles = new DevicePair(\"insoles\");\n static get insoles() {\n return this.#insoles;\n }\n static #gloves = new DevicePair(\"gloves\");\n static get gloves() {\n return this.#gloves;\n }\n static {\n DeviceManager.AddEventListener(\"deviceConnected\", (event) => {\n const { device } = event.message;\n if (device.isInsole) {\n this.#insoles.assignDevice(device);\n }\n if (device.isGlove) {\n this.#gloves.assignDevice(device);\n }\n });\n }\n}\n\nexport default DevicePair;\n","export function throttle<T extends (...args: any[]) => void>(\n fn: T,\n interval: number,\n trailing = false\n): (...args: Parameters<T>) => void {\n let lastTime = 0;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<T> | null = null;\n\n return function (...args: Parameters<T>) {\n const now = Date.now();\n const remaining = interval - (now - lastTime);\n\n if (remaining <= 0) {\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n lastTime = now;\n fn(...args);\n } else if (trailing) {\n lastArgs = args;\n if (!timeout) {\n timeout = setTimeout(() => {\n lastTime = Date.now();\n timeout = null;\n if (lastArgs) {\n fn(...lastArgs);\n lastArgs = null;\n }\n }, remaining);\n }\n }\n };\n}\n\nexport function debounce<T extends (...args: any[]) => void>(\n fn: T,\n interval: number,\n callImmediately = false\n): (...args: Parameters<T>) => void {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return function (...args: Parameters<T>) {\n const callNow = callImmediately && !timeout;\n\n if (timeout) {\n clearTimeout(timeout);\n }\n\n timeout = setTimeout(() => {\n timeout = null;\n if (!callImmediately) {\n fn(...args);\n }\n }, interval);\n\n if (callNow) {\n fn(...args);\n }\n };\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport { isInBrowser } from \"../utils/environment.ts\";\nimport BaseConnectionManager, {\n ConnectionType,\n ConnectionMessageType,\n ClientConnectionType,\n} from \"./BaseConnectionManager.ts\";\nimport { DeviceEventTypes } from \"../Device.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationTypes } from \"../DeviceInformationManager.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\nimport { ClientDeviceMessage } from \"../server/ServerUtils.ts\";\nimport BaseClient from \"../server/BaseClient.ts\";\nimport { DiscoveredDevice } from \"../BS.ts\";\n\nconst _console = createConsole(\"ClientConnectionManager\", { log: false });\n\nexport type SendClientMessageCallback = (\n ...messages: ClientDeviceMessage[]\n) => void;\n\nexport type SendClientConnectMessageCallback = (\n connectionType?: ClientConnectionType\n) => void;\n\nconst ClientDeviceInformationMessageTypes: ConnectionMessageType[] = [\n ...DeviceInformationTypes,\n \"batteryLevel\",\n];\n\nclass ClientConnectionManager extends BaseConnectionManager {\n static get isSupported() {\n return isInBrowser;\n }\n static get type(): ConnectionType {\n return \"client\";\n }\n\n subType?: ClientConnectionType;\n\n get canUpdateFirmware() {\n // FIX - how to know if it has an smp characteristic?\n return false;\n }\n\n client!: BaseClient;\n discoveredDevice!: DiscoveredDevice;\n\n #bluetoothId!: string;\n get bluetoothId() {\n return this.#bluetoothId!;\n }\n set bluetoothId(newBluetoothId) {\n _console.assertTypeWithError(newBluetoothId, \"string\");\n if (this.#bluetoothId == newBluetoothId) {\n _console.log(\"redundant bluetoothId assignment\");\n return;\n }\n this.#bluetoothId = newBluetoothId;\n }\n\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n set isConnected(newIsConnected) {\n _console.assertTypeWithError(newIsConnected, \"boolean\");\n if (this.#isConnected == newIsConnected) {\n _console.log(\"redundant newIsConnected assignment\", newIsConnected);\n return;\n }\n this.#isConnected = newIsConnected;\n\n this.status = this.#isConnected ? \"connected\" : \"notConnected\";\n\n if (this.isConnected) {\n this.#requestDeviceInformation();\n }\n }\n\n get isAvailable() {\n return this.client.isConnected;\n }\n\n async connect() {\n await super.connect();\n this.sendClientConnectMessage(this.subType);\n }\n async disconnect() {\n await super.disconnect();\n this.sendClientDisconnectMessage();\n }\n\n get canReconnect() {\n return true;\n }\n async reconnect() {\n await super.reconnect();\n this.sendClientConnectMessage();\n }\n\n sendClientMessage!: SendClientMessageCallback;\n sendClientConnectMessage!: SendClientConnectMessageCallback;\n sendClientDisconnectMessage!: Function;\n sendRequiredDeviceInformationMessage!: Function;\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n this.sendClientMessage({ type: \"smp\", data });\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n this.sendClientMessage({ type: \"tx\", data });\n }\n\n #requestDeviceInformation() {\n //this.sendClientMessage(...ClientDeviceInformationMessageTypes);\n this.sendRequiredDeviceInformationMessage();\n }\n\n onClientMessage(dataView: DataView) {\n _console.log({ dataView });\n parseMessage(\n dataView,\n DeviceEventTypes,\n this.#onClientMessageCallback.bind(this),\n null,\n true\n );\n this.onMessagesReceived!();\n }\n\n #onClientMessageCallback(messageType: DeviceEventType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isConnected\":\n const isConnected = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isConnected });\n this.isConnected = isConnected;\n break;\n\n case \"rx\":\n this.parseRxMessage(dataView);\n break;\n\n default:\n this.onMessageReceived!(messageType as ConnectionMessageType, dataView);\n break;\n }\n }\n}\n\nexport default ClientConnectionManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport {\n ServerMessageTypes,\n discoveredDevicesMessage,\n ServerMessage,\n MessageLike,\n ClientDeviceMessage,\n createClientDeviceMessage,\n ServerMessageType,\n} from \"./ServerUtils.ts\";\nimport { parseMessage, parseStringFromDataView } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher, {\n BoundEventListeners,\n Event,\n} from \"../utils/EventDispatcher.ts\";\nimport Device from \"../Device.ts\";\nimport {\n concatenateArrayBuffers,\n sliceDataView,\n stringToArrayBuffer,\n} from \"../utils/ArrayBufferUtils.ts\";\nimport {\n DiscoveredDevice,\n DiscoveredDevicesMap,\n ScannerEventMessages,\n} from \"../scanner/BaseScanner.ts\";\nimport ClientConnectionManager from \"../connection/ClientConnectionManager.ts\";\nimport { DeviceManager } from \"../BS.ts\";\nimport {\n ClientConnectionType,\n ConnectionTypes,\n} from \"../connection/BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"BaseClient\", { log: false });\n\nexport const ClientConnectionStatuses = [\n \"notConnected\",\n \"connecting\",\n \"connected\",\n \"disconnecting\",\n] as const;\nexport type ClientConnectionStatus = (typeof ClientConnectionStatuses)[number];\n\nexport const ClientEventTypes = [\n ...ClientConnectionStatuses,\n \"connectionStatus\",\n \"isConnected\",\n \"isScanningAvailable\",\n \"isScanning\",\n \"discoveredDevice\",\n \"expiredDiscoveredDevice\",\n] as const;\nexport type ClientEventType = (typeof ClientEventTypes)[number];\n\ninterface ClientConnectionEventMessages {\n connectionStatus: { connectionStatus: ClientConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport type ClientEventMessages = ClientConnectionEventMessages &\n ScannerEventMessages;\n\nexport type ClientEventDispatcher = EventDispatcher<\n BaseClient,\n ClientEventType,\n ClientEventMessages\n>;\nexport type ClientEvent = Event<\n BaseClient,\n ClientEventType,\n ClientEventMessages\n>;\nexport type BoundClientEventListeners = BoundEventListeners<\n BaseClient,\n ClientEventType,\n ClientEventMessages\n>;\n\nexport type ServerURL = string | URL;\n\ntype DevicesMap = { [deviceId: string]: Device };\n\nabstract class BaseClient {\n protected get baseConstructor() {\n return this.constructor as typeof BaseClient;\n }\n\n #reset() {\n this.#isScanningAvailable = false;\n this.#isScanning = false;\n for (const id in this.#devices) {\n const device = this.#devices[id];\n const connectionManager =\n device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n //device.removeAllEventListeners();\n }\n this.#receivedMessageTypes.length = 0;\n //this.#devices = {};\n }\n\n // DEVICES\n #devices: DevicesMap = {};\n get devices(): Readonly<DevicesMap> {\n return this.#devices;\n }\n\n #eventDispatcher: ClientEventDispatcher = new EventDispatcher(\n this as BaseClient,\n ClientEventTypes\n );\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n protected get dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n\n abstract isConnected: boolean;\n protected assertConnection() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n abstract isDisconnected: boolean;\n protected assertDisconnection() {\n _console.assertWithError(this.isDisconnected, \"not disconnected\");\n }\n\n abstract connect(): void;\n abstract disconnect(): void;\n abstract reconnect(): void;\n abstract toggleConnection(url?: ServerURL): void;\n\n static _reconnectOnDisconnection = true;\n static get ReconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n protected _reconnectOnDisconnection =\n this.baseConstructor.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n abstract sendServerMessage(...messages: ServerMessage[]): void;\n\n // CONNECTION STATUS\n #_connectionStatus: ClientConnectionStatus = \"notConnected\";\n protected get _connectionStatus() {\n return this.#_connectionStatus;\n }\n protected set _connectionStatus(newConnectionStatus) {\n _console.assertTypeWithError(newConnectionStatus, \"string\");\n _console.log({ newConnectionStatus });\n this.#_connectionStatus = newConnectionStatus;\n\n this.dispatchEvent(\"connectionStatus\", {\n connectionStatus: this.connectionStatus,\n });\n this.dispatchEvent(this.connectionStatus, {});\n\n switch (newConnectionStatus) {\n case \"connected\":\n case \"notConnected\":\n this.dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n if (this.isConnected) {\n // this._sendRequiredMessages();\n } else {\n this.#reset();\n }\n break;\n }\n }\n get connectionStatus() {\n return this._connectionStatus;\n }\n\n static #RequiredMessageTypes: ServerMessage[] = [\n \"isScanningAvailable\",\n \"discoveredDevices\",\n \"connectedDevices\",\n ];\n get #requiredMessageTypes(): ServerMessage[] {\n return BaseClient.#RequiredMessageTypes;\n }\n protected _sendRequiredMessages() {\n _console.log(\"sending required messages\", this.#receivedMessageTypes);\n this.sendServerMessage(...this.#requiredMessageTypes);\n }\n\n #receivedMessageTypes: ServerMessage[] = [];\n #checkIfFullyConnected() {\n if (this.connectionStatus != \"connecting\") {\n return;\n }\n _console.log(\"checking if fully connected...\");\n\n if (!this.#receivedMessageTypes.includes(\"isScanningAvailable\")) {\n _console.log(\"not fully connected - didn't receive isScanningAvailable\");\n return;\n }\n\n if (this.isScanningAvailable) {\n if (!this.#receivedMessageTypes.includes(\"isScanning\")) {\n _console.log(\"not fully connected - didn't receive isScanning\");\n return;\n }\n }\n\n _console.log(\"fully connected\");\n this._connectionStatus = \"connected\";\n }\n\n protected parseMessage(dataView: DataView) {\n _console.log(\"parseMessage\", { dataView });\n parseMessage(\n dataView,\n ServerMessageTypes,\n this.#parseMessageCallback.bind(this),\n null,\n true\n );\n this.#checkIfFullyConnected();\n }\n\n #parseMessageCallback(messageType: ServerMessageType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isScanningAvailable\":\n {\n const isScanningAvailable = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanningAvailable });\n this.#isScanningAvailable = isScanningAvailable;\n }\n break;\n case \"isScanning\":\n {\n const isScanning = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanning });\n this.#isScanning = isScanning;\n }\n break;\n case \"discoveredDevice\":\n {\n const { string: discoveredDeviceString } = parseStringFromDataView(\n dataView,\n byteOffset\n );\n _console.log({ discoveredDeviceString });\n\n const discoveredDevice: DiscoveredDevice = JSON.parse(\n discoveredDeviceString\n );\n _console.log({ discoveredDevice });\n\n this.onDiscoveredDevice(discoveredDevice);\n }\n break;\n case \"expiredDiscoveredDevice\":\n {\n const { string: bluetoothId } = parseStringFromDataView(\n dataView,\n byteOffset\n );\n this.#onExpiredDiscoveredDevice(bluetoothId);\n }\n break;\n case \"connectedDevices\":\n {\n if (dataView.byteLength == 0) {\n break;\n }\n const { string: connectedBluetoothDeviceIdStrings } =\n parseStringFromDataView(dataView, byteOffset);\n _console.log({ connectedBluetoothDeviceIdStrings });\n const connectedBluetoothDeviceIds = JSON.parse(\n connectedBluetoothDeviceIdStrings\n ).connectedDevices;\n _console.log({ connectedBluetoothDeviceIds });\n this.onConnectedBluetoothDeviceIds(connectedBluetoothDeviceIds);\n }\n break;\n case \"deviceMessage\":\n {\n const { string: bluetoothId, byteOffset: _byteOffset } =\n parseStringFromDataView(dataView, byteOffset);\n byteOffset = _byteOffset;\n const device = this.#devices[bluetoothId];\n _console.assertWithError(\n device,\n `no device found for id ${bluetoothId}`\n );\n const connectionManager =\n device.connectionManager! as ClientConnectionManager;\n const _dataView = sliceDataView(dataView, byteOffset);\n connectionManager.onClientMessage(_dataView);\n }\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n\n if (this.connectionStatus == \"connecting\") {\n this.#receivedMessageTypes.push(messageType);\n }\n }\n\n // SCANNING\n #_isScanningAvailable = false;\n get #isScanningAvailable() {\n return this.#_isScanningAvailable;\n }\n set #isScanningAvailable(newIsAvailable) {\n _console.assertTypeWithError(newIsAvailable, \"boolean\");\n this.#_isScanningAvailable = newIsAvailable;\n this.dispatchEvent(\"isScanningAvailable\", {\n isScanningAvailable: this.isScanningAvailable,\n });\n if (this.isScanningAvailable) {\n this.#requestIsScanning();\n }\n }\n get isScanningAvailable() {\n return this.#isScanningAvailable;\n }\n #assertIsScanningAvailable() {\n this.assertConnection();\n _console.assertWithError(\n this.isScanningAvailable,\n \"scanning is not available\"\n );\n }\n protected requestIsScanningAvailable() {\n this.sendServerMessage(\"isScanningAvailable\");\n }\n\n #_isScanning = false;\n get #isScanning() {\n return this.#_isScanning;\n }\n set #isScanning(newIsScanning) {\n _console.assertTypeWithError(newIsScanning, \"boolean\");\n this.#_isScanning = newIsScanning;\n this.dispatchEvent(\"isScanning\", { isScanning: this.isScanning });\n }\n get isScanning() {\n return this.#isScanning;\n }\n #requestIsScanning() {\n this.sendServerMessage(\"isScanning\");\n }\n\n #assertIsScanning() {\n _console.assertWithError(this.isScanning, \"is not scanning\");\n }\n #assertIsNotScanning() {\n _console.assertWithError(!this.isScanning, \"is already scanning\");\n }\n\n startScan() {\n this.#assertIsNotScanning();\n this.sendServerMessage(\"startScan\");\n }\n stopScan() {\n this.#assertIsScanning();\n this.sendServerMessage(\"stopScan\");\n }\n toggleScan() {\n this.#assertIsScanningAvailable();\n\n if (this.isScanning) {\n this.stopScan();\n } else {\n this.startScan();\n }\n }\n\n // PERIPHERALS\n #discoveredDevices: DiscoveredDevicesMap = {};\n get discoveredDevices(): Readonly<DiscoveredDevicesMap> {\n return this.#discoveredDevices;\n }\n\n protected onDiscoveredDevice(discoveredDevice: DiscoveredDevice) {\n _console.log({ discoveredDevice });\n this.#discoveredDevices[discoveredDevice.bluetoothId] = discoveredDevice;\n this.dispatchEvent(\"discoveredDevice\", { discoveredDevice });\n }\n requestDiscoveredDevices() {\n this.sendServerMessage({ type: \"discoveredDevices\" });\n }\n #onExpiredDiscoveredDevice(bluetoothId: string) {\n _console.log({ expiredBluetoothDeviceId: bluetoothId });\n const discoveredDevice = this.#discoveredDevices[bluetoothId];\n if (!discoveredDevice) {\n _console.warn(`no discoveredDevice found with id \"${bluetoothId}\"`);\n return;\n }\n _console.log({ expiredDiscoveredDevice: discoveredDevice });\n delete this.#discoveredDevices[bluetoothId];\n this.dispatchEvent(\"expiredDiscoveredDevice\", { discoveredDevice });\n }\n\n // DEVICE CONNECTION\n connectToDevice(bluetoothId: string, connectionType?: ClientConnectionType) {\n return this.requestConnectionToDevice(bluetoothId, connectionType);\n }\n protected requestConnectionToDevice(\n bluetoothId: string,\n connectionType?: ClientConnectionType\n ) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.#getOrCreateDevice(bluetoothId);\n if (connectionType) {\n device.connect({ type: \"client\", subType: connectionType });\n } else {\n device.connect();\n }\n return device;\n }\n protected sendConnectToDeviceMessage(\n bluetoothId: string,\n connectionType?: ClientConnectionType\n ) {\n if (connectionType) {\n this.sendServerMessage({\n type: \"connectToDevice\",\n data: concatenateArrayBuffers(\n stringToArrayBuffer(bluetoothId),\n ConnectionTypes.indexOf(connectionType)\n ),\n });\n } else {\n this.sendServerMessage({ type: \"connectToDevice\", data: bluetoothId });\n }\n }\n\n // DEVICE CONNECTION\n createDevice(bluetoothId: string) {\n const device = new Device();\n const discoveredDevice = this.#discoveredDevices[bluetoothId];\n const clientConnectionManager = new ClientConnectionManager();\n clientConnectionManager.discoveredDevice = Object.assign(\n {},\n discoveredDevice\n );\n clientConnectionManager.client = this;\n clientConnectionManager.bluetoothId = bluetoothId;\n clientConnectionManager.sendClientMessage = this.sendDeviceMessage.bind(\n this,\n bluetoothId\n );\n clientConnectionManager.sendRequiredDeviceInformationMessage =\n this.sendRequiredDeviceInformationMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientConnectMessage =\n this.sendConnectToDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientDisconnectMessage =\n this.sendDisconnectFromDeviceMessage.bind(this, bluetoothId);\n device.connectionManager = clientConnectionManager;\n return device;\n }\n\n #getOrCreateDevice(bluetoothId: string) {\n let device = this.#devices[bluetoothId];\n if (!device) {\n device = this.createDevice(bluetoothId);\n this.#devices[bluetoothId] = device;\n }\n return device;\n }\n protected onConnectedBluetoothDeviceIds(bluetoothIds: string[]) {\n _console.log({ bluetoothIds });\n bluetoothIds.forEach((bluetoothId) => {\n const device = this.#getOrCreateDevice(bluetoothId);\n const connectionManager =\n device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = true;\n DeviceManager._CheckDeviceAvailability(device);\n });\n }\n\n disconnectFromDevice(bluetoothId: string) {\n this.requestDisconnectionFromDevice(bluetoothId);\n }\n protected requestDisconnectionFromDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.devices[bluetoothId];\n _console.assertWithError(device, `no device found with id ${bluetoothId}`);\n device.disconnect();\n return device;\n }\n protected sendDisconnectFromDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"disconnectFromDevice\", data: bluetoothId });\n }\n\n protected sendDeviceMessage(\n bluetoothId: string,\n ...messages: ClientDeviceMessage[]\n ) {\n this.sendServerMessage({\n type: \"deviceMessage\",\n data: [bluetoothId, createClientDeviceMessage(...messages)],\n });\n }\n\n protected sendRequiredDeviceInformationMessage(bluetoothId: string) {\n this.sendServerMessage({\n type: \"requiredDeviceInformation\",\n data: [bluetoothId],\n });\n }\n}\n\nexport default BaseClient;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport {\n createServerMessage,\n MessageLike,\n ServerMessage,\n} from \"../ServerUtils.ts\";\nimport {\n addEventListeners,\n removeEventListeners,\n} from \"../../utils/EventUtils.ts\";\nimport ClientConnectionManager from \"../../connection/ClientConnectionManager.ts\";\nimport BaseClient, { ServerURL } from \"../BaseClient.ts\";\nimport type * as ws from \"ws\";\nimport Timer from \"../../utils/Timer.ts\";\nimport {\n createWebSocketMessage,\n WebSocketMessageType,\n WebSocketMessageTypes,\n webSocketPingTimeout,\n webSocketReconnectTimeout,\n WebSocketMessage,\n} from \"./WebSocketUtils.ts\";\nimport { parseMessage } from \"../../utils/ParseUtils.ts\";\n\nconst _console = createConsole(\"WebSocketClient\", { log: false });\n\nclass WebSocketClient extends BaseClient {\n // WEBSOCKET\n #webSocket?: WebSocket;\n get webSocket() {\n return this.#webSocket;\n }\n set webSocket(newWebSocket) {\n if (this.#webSocket == newWebSocket) {\n _console.log(\"redundant webSocket assignment\");\n return;\n }\n\n _console.log(\"assigning webSocket\", newWebSocket);\n\n if (this.#webSocket) {\n removeEventListeners(this.#webSocket, this.#boundWebSocketEventListeners);\n }\n\n addEventListeners(newWebSocket, this.#boundWebSocketEventListeners);\n this.#webSocket = newWebSocket;\n\n _console.log(\"assigned webSocket\");\n }\n get readyState() {\n return this.webSocket?.readyState;\n }\n get isConnected() {\n return this.readyState == WebSocket.OPEN;\n }\n get isDisconnected() {\n return this.readyState == WebSocket.CLOSED;\n }\n\n connect(url: string | URL = `wss://${location.host}`) {\n if (this.webSocket) {\n this.assertDisconnection();\n }\n this._connectionStatus = \"connecting\";\n this.webSocket = new WebSocket(url);\n }\n\n disconnect() {\n this.assertConnection();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.webSocket!.addEventListener(\n \"close\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n this._connectionStatus = \"disconnecting\";\n this.webSocket!.close();\n }\n\n reconnect() {\n this.assertDisconnection();\n this.connect(this.webSocket!.url);\n }\n\n toggleConnection(url?: ServerURL) {\n if (this.isConnected) {\n this.disconnect();\n } else if (url && this.webSocket?.url == url) {\n this.reconnect();\n } else {\n this.connect(url);\n }\n }\n\n // WEBSOCKET MESSAGING\n sendMessage(message: MessageLike) {\n this.assertConnection();\n this.#webSocket!.send(message);\n this.#pingTimer.restart();\n }\n\n sendServerMessage(...messages: ServerMessage[]) {\n this.sendMessage(\n createWebSocketMessage({\n type: \"serverMessage\",\n data: createServerMessage(...messages),\n })\n );\n }\n\n #sendWebSocketMessage(...messages: WebSocketMessage[]) {\n this.sendMessage(createWebSocketMessage(...messages));\n }\n\n // WEBSOCKET EVENTS\n #boundWebSocketEventListeners: { [eventType: string]: Function } = {\n open: this.#onWebSocketOpen.bind(this),\n message: this.#onWebSocketMessage.bind(this),\n close: this.#onWebSocketClose.bind(this),\n error: this.#onWebSocketError.bind(this),\n };\n\n #onWebSocketOpen(event: ws.Event) {\n _console.log(\"webSocket.open\", event);\n this.#pingTimer.start();\n //this._connectionStatus = \"connected\";\n this._sendRequiredMessages();\n }\n async #onWebSocketMessage(event: ws.MessageEvent) {\n _console.log(\"webSocket.message\", event);\n //this.#pingTimer.restart();\n //@ts-expect-error\n const arrayBuffer = await event.data.arrayBuffer();\n const dataView = new DataView(arrayBuffer);\n this.#parseWebSocketMessage(dataView);\n }\n #onWebSocketClose(event: ws.CloseEvent) {\n _console.log(\"webSocket.close\", event);\n\n this._connectionStatus = \"notConnected\";\n\n Object.entries(this.devices).forEach(([id, device]) => {\n const connectionManager =\n device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n });\n\n this.#pingTimer.stop();\n if (this.reconnectOnDisconnection) {\n setTimeout(() => {\n this.reconnect();\n }, webSocketReconnectTimeout);\n }\n }\n #onWebSocketError(event: ws.ErrorEvent) {\n _console.error(\"webSocket.error\", event);\n }\n\n // PARSING\n #parseWebSocketMessage(dataView: DataView) {\n parseMessage(\n dataView,\n WebSocketMessageTypes,\n this.#onServerMessage.bind(this),\n null,\n true\n );\n }\n\n #onServerMessage(messageType: WebSocketMessageType, dataView: DataView) {\n switch (messageType) {\n case \"ping\":\n this.#pong();\n break;\n case \"pong\":\n break;\n case \"serverMessage\":\n this.parseMessage(dataView);\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // PING\n #pingTimer = new Timer(this.#ping.bind(this), webSocketPingTimeout);\n #ping() {\n this.#sendWebSocketMessage(\"ping\");\n }\n #pong() {\n this.#sendWebSocketMessage(\"pong\");\n }\n}\n\nexport default WebSocketClient;\n","export {\n setAllConsoleLevelFlags,\n setConsoleLevelFlagsForType,\n} from \"./utils/Console.ts\";\nexport * as Environment from \"./utils/environment.ts\";\nexport { Vector2, Vector3, Quaternion, Euler } from \"./utils/MathUtils.ts\";\n\nexport {\n default as Device,\n DeviceEvent,\n DeviceEventMap,\n DeviceEventListenerMap,\n BoundDeviceEventListeners,\n} from \"./Device.ts\";\nexport {\n default as DeviceManager,\n DeviceManagerEvent,\n DeviceManagerEventMap,\n DeviceManagerEventListenerMap,\n BoundDeviceManagerEventListeners,\n} from \"./DeviceManager.ts\";\n\nexport { DeviceInformation } from \"./DeviceInformationManager.ts\";\nexport {\n DeviceType,\n DeviceTypes,\n MinNameLength,\n MaxNameLength,\n Sides,\n Side,\n} from \"./InformationManager.ts\";\nexport {\n MinWifiSSIDLength,\n MaxWifiSSIDLength,\n MinWifiPasswordLength,\n MaxWifiPasswordLength,\n} from \"./WifiManager.ts\";\nexport {\n SensorType,\n SensorTypes,\n ContinuousSensorType,\n ContinuousSensorTypes,\n} from \"./sensor/SensorDataManager.ts\";\nexport {\n MaxSensorRate,\n SensorRateStep,\n SensorConfiguration,\n} from \"./sensor/SensorConfigurationManager.ts\";\n\nexport {\n DefaultNumberOfPressureSensors,\n PressureData,\n} from \"./sensor/PressureSensorDataManager.ts\";\nexport { CenterOfPressure } from \"./utils/CenterOfPressureHelper.ts\";\nexport {\n VibrationConfiguration,\n VibrationLocation,\n VibrationLocations,\n VibrationType,\n VibrationTypes,\n MaxNumberOfVibrationWaveformEffectSegments,\n MaxVibrationWaveformSegmentDuration,\n MaxVibrationWaveformEffectSegmentDelay,\n MaxVibrationWaveformEffectSegmentLoopCount,\n MaxNumberOfVibrationWaveformSegments,\n MaxVibrationWaveformEffectSequenceLoopCount,\n} from \"./vibration/VibrationManager.ts\";\nexport {\n VibrationWaveformEffect,\n VibrationWaveformEffects,\n} from \"./vibration/VibrationWaveformEffects.ts\";\n\nexport {\n FileType,\n FileTypes,\n FileTransferDirection,\n FileTransferDirections,\n} from \"./FileTransferManager.ts\";\nexport {\n TfliteSensorType,\n TfliteSensorTypes,\n TfliteTask,\n TfliteTasks,\n TfliteFileConfiguration as TfliteFileConfiguration,\n} from \"./TfliteManager.ts\";\n\nexport {\n CameraConfiguration,\n CameraCommand,\n CameraCommands,\n CameraConfigurationType,\n CameraConfigurationTypes,\n} from \"./CameraManager.ts\";\n\nexport {\n default as DevicePair,\n DevicePairEvent,\n DevicePairEventMap,\n DevicePairEventListenerMap,\n BoundDevicePairEventListeners,\n DevicePairType,\n DevicePairTypes,\n} from \"./devicePair/DevicePair.ts\";\n\nimport { addEventListeners, removeEventListeners } from \"./utils/EventUtils.ts\";\nexport const EventUtils = {\n addEventListeners,\n removeEventListeners,\n};\n\nimport { throttle, debounce } from \"./utils/ThrottleUtils.ts\";\nexport const ThrottleUtils = {\n throttle,\n debounce,\n};\n\nexport { DiscoveredDevice } from \"./scanner/BaseScanner.ts\";\n/** NODE_START */\nexport { default as Scanner } from \"./scanner/Scanner.ts\";\nexport { default as WebSocketServer } from \"./server/websocket/WebSocketServer.ts\";\nexport { default as UDPServer } from \"./server/udp/UDPServer.ts\";\n/** NODE_END */\n/** BROWSER_START */\nexport { default as WebSocketClient } from \"./server/websocket/WebSocketClient.ts\";\n/** BROWSER_END */\n\nexport { default as RangeHelper } from \"./utils/RangeHelper.ts\";\n"],"names":["createWebSocketMessage"],"mappings":";;;;;;;;;;;AA+RA;;AAEA;AACA;;;AAIA;;AAEA;AACA;AA+BuB;AACvB;AACA;AACA;;ACvUA;AACA;AAGA;AACA;;;AAKA;AACE;AACF;;;AAEA;;;;AAMA;;;AAMA;;;;;;;;;;;;;;;;;;;ACPA;AACA;AACE;;;;AAIA;;;AAGF;;;AAEA;AAEA;AACE;;AACY;;;AAKZ;;AACY;;;AAId;;AAGE;;AAEI;AACA;;;AAEA;;;AAGN;AAGA;;;AAGM;;;AAGJ;AACF;AAGA;AACE;AACE;;AAEF;AACF;AAEA;AAEA;;;AAGA;;;AAGA;;;AAGA;;;;AAKA;;;;;;;;;AAII;;;AAGA;;;;;AAgBF;;;;;;;AAQE;;;;AAKF;;AAKE;;;AAIA;;;AAIA;;;AAIA;;;AAIA;;;AAIA;;;AAKA;AACE;;;;;;;AAcF;;;;;;;AAhFK;AA+FO;;AAKhB;AAGgB;AAId;AACF;;AAGE;AACF;;;ACpKA;;;;;;;;;;;;;;;;AA2BI;;AACA;AACE;;;AAGA;AACF;;;;;;;AAaE;AACA;;AAEF;AACE;AACF;;AAEE;;;;AAIF;;;;;;;AAaA;;;;AAIE;;;AAGE;;AAEJ;AAEA;;;;;;AAQA;;;AAGA;;;AAIA;AACA;;;;;;AAQA;;;AAGE;;;;AAKA;AAEA;;AAEE;;AAEJ;AACA;;;AAIA;AACE;;;AAIA;AACF;;AAEH;;;;ACrKD;;;;;AAMI;AACA;;AAEA;;;;;;;;AAUA;;AAEA;;AAEA;;;;;;;;AAMA;AACA;;;;;;AASA;AACE;;;;AAIF;;AAEE;;;;AAIF;AACE;;;AAGF;;;;;;AAMA;;AAEH;;;ACvEgB;;AAMf;;;;AAIF;;AAGA;;;AAGA;;AAGE;;AAEA;AACE;AACA;AACA;AAEA;;AAEF;AACF;;AC/BA;AACA;;;;AAIM;;;AAGN;;;AAEA;AAEA;AACA;;;AAGM;AACA;AACG;AACC;;;;;AAKV;;;AAEA;AAEO;AACA;;;ACxBS;AACd;;AAEE;;AAEE;;AACK;;AAEL;;AACK;;AAEL;;AACK;;AAEL;;AACK;AACL;;;;;;AAIK;;;;AAGA;;AAEL;;;AAEA;;AAEJ;AACA;;AAEA;;AAEA;;AAEE;AACF;;AAEF;;;;AASA;;;AAIA;;AAGE;AACA;;;AAGA;AACA;AACF;;AAKE;AACA;AACE;;AACK;AACL;;;AAEA;AACA;;AACK;AACL;;AACK;;;;AAGL;;AAEF;AACF;;;ACrFA;AAEA;;;AAGA;AACA;AAEA;;;AAIA;AACA;;AAGA;AACA;;AAGA;AACA;AAEA;;AAGA;;;AAGA;;;AAIA;AACA;AACA;AAEA;AACA;;;;AC9BO;;;;;;;;;;;;;;;AAiBM;;AAMN;;;;;;AAUA;AACL;;;;;AAOK;;;;;;;AAkCP;AACE;;AA0CA;;;AA4EA;AAmCA;AAoDA;AA2BA;;AA0JA;AA4EA;AASA;;;;AAhdE;;;AAMA;;;AAGA;;;;;;;;;;;;;;;;;;;;;;;;AAgSA;;AAGE;AACE;;AAEF;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;AACE;;;;;;AAON;;AAEI;AACA;;AAYF;AACA;AACA;AAEA;AACE;;AACK;AACL;;AACK;AACL;;;AAEA;AACA;;;;;;;;AAYF;;AAIA;;;AAwEA;AAEA;;;;;AAQA;AACA;;;;;;;;;AAYE;;;AAGF;;;;AAKA;;;AAGC;AACD;;;;AA/dA;AACF;AASE;AACF;;AASA;;AAOA;AAEE;AACF;AAOE;;;;AAIA;AACA;;AAEC;AACH;AAYE;;;AAGA;AACF;AAEE;;;AAGF;;AAMA;AAOE;;AAEA;AACA;AACA;AACF;;;;;AAOE;AACA;;;;;;;AAaA;AACF;AAOE;;AAGA;AACF;;;;;AAOE;AACA;AACA;;;;;;;AASA;AAKA;AACF;AAOE;;AAEA;AACF;AAEE;;;;AAKA;AACA;;;;;;;AASA;AAKA;;AAIA;;;;;;;;;;AAeA;AACF;AAOE;;AAEA;AACA;AACA;AACF;AAEE;;;;;AAKF;;AAGA;;AAGA;AAOE;;;AAOA;AAEA;;;;;AAUE;;;;;AAKC;;;AAIH;;;AAIE;;;AAGA;;;AAGA;;;;AAKF;AACA;;;;;;AAMA;AACA;AACA;;;;;AASA;;;;AAKF;;;AAyFE;AACF;AAIE;;;;AAIE;;;;AAIA;AACE;;;;;;;AASJ;AACA;;;;AASA;AACE;;;;AAGA;;;AAIJ;AAGE;;AAEA;AACA;AACE;;;;;;;;AAYF;;AAzYK;;;;ACzIP;AACE;;AAEF;AACF;AAEO;;AAGL;;AAEF;;AAIgB;AACd;AACA;;AAEA;;AAEE;;;AAGF;AACF;;;ACvBA;AAEA;AAAA;;;;;;;;;;;;;AAYI;;;;;AAKA;;;;;;;;AAcA;;;;;;;;;;AAYA;;;AAGH;;AAzBG;;;;ACpBJ;AAAA;;;;;;;AAMI;AACA;;;;;;;;;;;;;AAeA;;;AAGH;;;ACpCe;AACd;AACE;;AAEE;;;;;;AAKJ;AACF;;;AAIA;;;;ACPO;;AAgCA;AAEP;AAAA;AACE;;;;;;;;;AAME;;;;;;;AAaI;AACD;;AAGH;;AAIA;;;;AAcA;;;;;;;;;;;;;;;;AA0BE;;;;;;;AAQA;;;;AASF;AACE;;;AAGE;AACA;AACF;;;;AAQF;AACA;;AAEH;;;;AC3IM;;;;;;;;;;;;;;;AAiBA;;;;;;;;;;AAmBA;;;;;;;;AAmBA;;;;;;;AA4BP;;;AAGM;AACA;AACA;;;AAKF;AACA;;;;AAKE;AACA;AACA;AACA;;;AAKF;AACA;;;;AAKE;AACA;AACA;;;;AAKF;;;;AAMA;AACA;;;AAIA;;AAEA;AACA;;;AAIA;;;AAIA;;AAEE;AACF;AAEA;AAEA;;;AAIA;;AAEA;AACA;AACA;AACA;;AAEH;;;AClKM;;;AAeP;AAAA;;;;AAgBI;;;;;AAKH;;;;;;;;;;AATG;;;;;;;;ACXF;AACF;AAEgB;;AAYd;;;AAME;AAEA;;;;;;;;;;;;;;AAcC;;AAGD;AAEA;;;AAIJ;;;;ACtDO;AAGM;;;;;;;AASN;;;;;;AAQA;;;;;;;;AAUM;;;;;;;;;AAWN;;;;;;;AAmBA;;;;AAKA;AACL;;;;AAuBF;AACE;;;AA6LA;;AAEA;AAEA;;AAEA;AAEA;;AAEA;AAEA;AAwBA;;;;;;;;;;;;;;AAtNE;;;AAIA;;;AAGC;AACD;;;;;;AAqEA;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA;;;;;;;;;;;;;AAwLA;;AAEE;;;;AAIF;;;;;;;AAQC;AACD;;;AAwBA;;;AAQA;;;;;;AAuCE;AACE;;AAEF;AACA;AACE;;AAEF;AACE;;;;;;;;;;;;AAcP;;AApYG;AACF;;AAoBE;AACA;AACF;AAEE;;;;;;;;AAQA;;;AAGC;;;;AAOC;;AAEJ;AAGA;AACE;;;;;;;;;;;AAgBA;AACF;AAEE;AAIF;AAEE;AAIF;AAwBE;;;;;AAYE;;;;;;AAMA;;;;;AAKE;;;AAGC;;AAEC;;;AAGJ;;;;;;;AAOA;;;;;;AAMI;;AAEE;;;AAGJ;;;AAGC;;AAEH;;;;;;AAMA;;;;;AAKE;;;AAGC;;AAEC;;AAEE;;;;;AAKV;AAgBE;AACA;AAKA;AAEA;AACA;;AAGA;;;AAOF;;;AA6BE;;AAEE;;AAMA;;;AAOF;;;AAKA;;AAEC;AACH;;AAME;AACE;;AAIF;AACF;;;;AAmCE;AACF;;;AA6BE;;AAIE;;;AAMA;AAEA;AACF;;AAEA;;;;;AC/aS;AACX;AACA;AACA;AACA;;AAIW;AACX;AACA;AACA;;AAIK;;;;;AAOA;;;AAIA;AACL;AACA;;AA4BF;AAAA;AACE;AACA;AACA;;;;AAKE;;;AAGA;;;;AASA;;;AAIA;;AAGE;AACE;;AAEF;AACE;;AAEF;AACE;;;;;;;AAQJ;;AAME;;;;;AAKA;;;;;;AAOF;;;;;AAQA;;AAEC;;;AAQD;;;AAIE;;;AAGA;AACA;AACA;AACA;AACA;;;AAMA;AACA;;;AAMA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AAEI;;AAEJ;;;AAGA;;;AAMA;;;;;;;AAcF;;;;AAIC;AAED;;;;AAIC;;AAEJ;;;;;;ACtOM;AAEA;;;;;AAwBP;AACE;;;AAgCA;;;;AAxBE;;;AAMA;;;;;AAsCF;;AAKI;;AAKF;;AAEE;;;;AAIF;;;;;;;AAQC;AACD;;;;;;;;AAkFE;AACF;AACA;;;;;;AAWA;;AAGE;AACA;;AAEE;;;;;;;;AAvKJ;AACF;;;;AAiBE;AACF;;;AAUE;;AAEC;AACH;;AAIE;;AAEA;AACF;;AAgCE;;AAME;AAEA;;;;;;AAOA;;AAEF;;AAIA;AACF;AAGE;;;AASA;AAIF;AAGE;AACF;;;AAQE;;AAEE;;;AAIA;AACA;AACA;AACF;;AAEA;;AAIK;AAIP;AACE;AACE;AACF;AACF;;;;ACtLK;;;;;;;;;;;;;;;;;;;AAuBA;;;;;;;;;;;AA0CM;;;;;;AAmBb;AACE;;;;;AAmJA;;;;;;;;;AAjIE;;;AAMA;;;AAGA;;;;;AAmBF;AACE;AACA;;;;;;AAQA;AAKA;;;;;AAmBF;AACE;AACA;;;;;;;AAaA;;;;;AAmBF;AACE;AACA;AACA;;;;;;;;AAaA;AAKA;;;AAIA;;AAEA;;;;;AAmCF;AAIE;AACE;AACF;;AAIA;;;AAGG;AACH;;;;;;;AAWA;;;;;;;;AAqCF;AACE;;;;;;;;AAUA;AAKA;;;;;AAiBF;AACE;;;;;;;;;AAcA;AAKA;;;;;;AAuBA;;;;AAIA;;;;;;;;;AAcM;;;AAMN;;;;;;AAOA;;;AAGA;;;AAGA;;;AAGA;;;AAoDA;;AAGE;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACA;AACE;;AAEF;AACE;;;;;;;;;;;AAgBF;;;;;AAKF;;;AAGA;AAEA;AACA;AACA;AACE;;AAEF;AACA;AACE;;AAEF;;;;;;;;;;AAYA;;;AAGC;AACD;;AAEH;;AAveG;AACF;;AAMA;AAOE;AACF;AAeE;;AAEA;AACF;AAEE;;;AAGF;AAwBE;;AAEA;AACA;AACA;AACF;AAEE;;;AAGF;AAwBE;;AAEA;AACF;AAEE;;AAEA;;AAEC;AACH;AAuCE;;AAEA;;AAEE;;AAEE;AACE;;;;;;;;;;AAQN;AACF;AAEE;;AAEA;;AAEC;AACH;AAkCE;;AAEA;AACF;AAEE;;;AAGF;;AAGA;AAOE;;AAEA;AACF;AAEE;;AAEA;;AAEC;AACH;AAyBE;;AAEA;AACF;AAEE;;;AAGF;AA6BE;;AAEA;AACF;AAEE;;AAEA;;AAEC;AACH;AAiDE;;AAGA;;;;AASE;;AAEF;;;;;AAOA;;;;AAII;;;;AAIF;;AAEA;AACA;;AAEE;AACA;AACA;;AAEE;AACA;AACF;;;;;;;;ACzdD;;;;;;;;;AAWA;AACL;;;AAuBF;AAAA;;AAME;;;;;;;;;AAqCE;;AAGE;;AAEE;;;AAGF;;AAEE;;;AAGF;;AAEE;;;AAGF;;AAEE;;;AAGF;;AAEE;;;AAGF;;AAEI;;;;;AAKF;;;AAKA;;;AAGF;;AAEE;;;;;;AAOP;;AA5FG;AACF;;AAaA;AAGE;;AAIA;AAEE;AACE;AAED;AACH;;;;AAKE;AACA;;AAEC;;;;;;ACtFM;;;;;;;;;AAaN;AACA;AAEA;;;;;;;;;;;;;AAmCP;AACE;;AAgBA;;;AAyCA;;AAuGA;AAeA;;;;AApKE;;;;;;;;;AAqBA;;;AAGA;;;;;;;;;AA4BA;;;AAGA;;;AAGA;AACA;;AAOA;;AAGA;AACA;;;;;;;;;AAsBA;AACA;AACE;;;;;AAMF;;;AAWA;;AAEA;;;;AAKE;AACA;AACE;;AAEA;;;;;AAMF;AACA;AACE;;AAEA;;;;;AAMF;AACA;AACE;AACF;AACA;AACE;;AAEA;;;;;;;;;;AA8CJ;;AAGE;;AAEE;AACA;;AAEF;;AAEE;AACA;;AAEF;;AAEE;AACA;;AAEF;AACA;;AAEE;AACA;;AAEF;AACA;;AAEE;;AAEA;;AAEF;;AAEE;AAEE;;;AAIF;AACA;;AAEF;AACA;AACE;AACA;;;;;;;;;AAYP;;AA3PG;AACF;AAYE;;;AAGA;AACF;AAaE;;;AAGA;;AAEC;AACH;AAOE;;;AAGA;AACF;AAsCE;AACF;AAEE;;AAKF;AAaE;;AAEA;;AAEA;AACA;AACF;AA6CE;;AAEE;;;;AAKF;AACF;AAQE;;;AAIE;;AAEJ;AAEE;;AAEA;;AAEA;AAIA;;;ACpQS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6BN;;;;;AAaA;AACA;AACA;AACA;AACA;AACA;AAiCP;AACE;;AAmUA;;;;AAzTE;;;AA6QA;AACA;AACE;AAEA;;AAEA;AAIA;;AAGE;;AAEI;;;;AAQJ;;AAEI;;;;;;;;AAQN;AAIF;AACA;;;;;;AAoBA;;AAGE;AACE;;;AAGA;;;;;;AAMP;;AAvVG;AACF;AAME;AACA;AAIF;AAEE;AACA;AACE;AACF;AACF;AAEE;;AAGA;;AAEE;AACF;AACA;;AAKA;AACF;AAGE;;AAEF;AAGE;AAIF;AAKE;AACE;AACA;;AACK;AACL;;AAKA;;;AAKA;;AAGF;AACE;AACA;;AAEJ;AAKE;;;AAOI;AAGN;AAKE;AACA;;AAKA;AACE;AACF;AACF;AAKE;;;AAOI;AAGN;;AAIE;AAIA;;AAMA;;AAQF;AAGE;;AAKA;AACE;AACF;AACF;AAOE;AACA;;;;AASI;AACA;AACF;;;;AASA;AACE;AAGF;;;AAGA;AACE;;AAEE;;AACG;AACL;;;;AAGA;;;AAIJ;;AAKE;AACE;;;AAMA;;;AAGF;;AAEE;;;AAIJ;AACE;;AAEF;;;;AAQA;AACA;;AAEE;;AAKF;AACA;;AAEF;AAGE;AACA;;;;AAaA;;;AAGA;AAMA;AACA;AACF;;AAwDE;AACA;;AAEC;;;;;ACpZE;AACA;AAEA;AACA;AAEA;;;;;;;;;;;;AAcA;;;;;;;;;AA4BP;AACE;;AAwBA;AAkBA;AAkCA;;AAuFA;;AA4BA;;;;AApLE;;;AAIA;;;AAGC;AACD;;;;;;;;;AAmCA;;AAEE;;;AAGF;AACA;;AAQA;;AAGA;AACA;;;;;;AAkBA;;AAEE;;;AAGF;AACA;AACE;;;AASF;;;;AAKC;AACD;;;;;;AAmBA;AACA;;;;;;;;;AAaM;;;AAKN;;;;;;AAMA;;;AAGA;;;;;;;;;;;;AA+CA;;AAGE;;AAEE;AACA;;AAEF;AACA;;AAEE;AACA;;AAEF;AACA;;AAEE;AACA;;AAEF;AACA;;AAEE;AACA;;AAEF;;AAEE;AACA;;AAEF;;AAEE;;;AAGA;AACA;;AAEF;;AAEE;AACA;;;;;;;;;;;;;AAcP;;AA9PG;AACF;AAoBE;;;AAGA;;AAEC;AACH;;AAIA;AASE;;;AAGA;AACF;AA8BE;;;AAGA;;AAEC;AACH;AAiCE;;AAEA;;AAEC;AACH;AA0CE;;;AAGA;;AAEC;AACH;;;AAWE;;AAEC;AACH;AAQE;;;AAGA;;AAEC;;;;;AClPE;;;;;;;AAwCA;;;;;;AAQA;AACL;;;;AAoBK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIK;AAGA;AAGA;AAIA;AACL;AACA;AACA;AACA;AACA;;AAWF;;;;;AAgBI;;;AAGA;;;AAIA;;;AAKA;;AAWF;;AAIA;AA0FA;AACA;;AAoFA;;AAlLE;;;;;;AAQA;;;;;;;AASA;AAEA;;;;;;;AAOE;;;;AAKF;;;AAIA;;;;;;;;;;AA4BA;;;;AAKA;AACA;;;AAGA;;;;AAIA;;AAEA;AACA;;;;AAIA;AACA;AACA;;;;AAKA;;;;;;;;;AAiBE;;;;AAKA;;;;AAIA;;;;AAKF;;;;;AAME;;AAEF;;AAGA;AACE;;AAMI;;;;;;AAMF;AACE;;AAIE;;;AAKF;AACA;AACF;;;AAIA;AACA;AACA;;;;AAGF;AACA;AACA;;;AAKF;;;AAQA;;;;;;;;;;;;AAoCA;AACA;AACA;;AAEH;;AAnQG;AACF;AA8BE;AAIF;;AAmDA;;;;AAqJE;AACF;AAKE;AACE;AACA;;;;;ACxWJ;AACF;;;ACcgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;AAEgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;;;AC9BA;AACE;AACF;;AAIE;;;AAMF;;AAKE;AACF;;AAGE;AACF;AA6BA;AACE;AACE;AACE;AACA;AACE;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;AACE;;;;AAIN;AACE;AACA;AACE;AACE;;;;AAIN;AACE;AACA;;;;;AAKF;;AAEE;AACE;;;;AAIP;;AAGM;AACL;AACA;AACA;;AAE6B;;;;AAS7B;;;AAGE;;;;;;;AAOF;AACF;;;AAQA;AACE;;;;AAMA;;;AAGI;AACA;;AAEF;AAEF;;;;AAUA;AACA;;;;;AAQI;;;;;;;AAOF;AACA;AACF;AACA;AACF;;;;;;;;;;;;;;AAmBI;AACA;AACA;AACE;;;;AAMF;AACA;AACA;AACE;;;;AAMF;AACE;;;;AAMF;AACE;;;AAIJ;AACF;;;;AC3OA;;;;;AAGI;;;AASA;AACE;;;;;;AAMM;;;;AAQR;;;;AAKA;AACA;;;;;AAKH;;;;ACTD;AASA;AACE;AACF;;AAGA;;;;;;;;;;;;;;AAEI;;;;;;AAkBA;;;AAGA;;;;;;;AASE;;;;;;;AAUA;;;;;;;;AASF;;;AAQA;AAEA;AACE;;;AAGC;AAED;AACA;AAEA;;;AAIA;AAEA;AAEA;;;AAEA;AACA;AACA;AACA;;;;AAgGF;AACA;AACA;AACA;;AAuCF;AAIE;;;;AAQA;;AAGA;AACE;AACA;;;AAEA;AACA;;AAEF;;AAGE;AACA;AACA;AACE;;;;;AAWJ;;;AAGA;AACA;AACE;;;AAEA;AACA;;AAGF;AACE;AACA;AACA;;;AAEA;AACA;;;;;AAMF;;AAEH;;AAtMG;AAEA;;;AAKA;AACA;AACE;AACA;;;;AAOA;;;AAGA;;AAEA;AACE;AAGA;;;;AAWA;;AAEA;AAIA;;AAGA;;AAIE;;AAEF;;AAEE;AACA;AACE;;;;;AAKV;AAEE;;;;;AAUI;AAIA;;AAGA;;AAIE;;AAEJ;AAGF;AACF;AASE;AAEA;AACA;AACF;AAGE;AAEA;AACA;;AAQA;;;AAUA;AACE;;;AAEA;;AAEJ;AAoCE;AACA;;;;;;;ACrRJ;AACA;AACA;;AAGA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;AACA;AAEA;AACA;;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;;;AAGA;AACA;;;AAGA;AACA;;AAEA;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;;AAEA;;AAEA;AACA;;;;AAKA;AACA;;AAEA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAIA;;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;;AAGA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAGA;AACA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AAEA;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;;AAEA;AACA;AAEA;AACA;AACA;;;;AAIA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AAEA;AACA;AACA;AACA;;AAGA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;AAMA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;;;;ACtYO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAIA;AACA;;AAGA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;;;AAGA;;AAEA;AACA;AACA;AACA;;AAE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","x_google_ignoreList":[0,8]}