brilliantsole 0.0.1

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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/build/brilliantsole.cjs +5957 -0
  4. package/build/brilliantsole.cjs.map +1 -0
  5. package/build/brilliantsole.js +5448 -0
  6. package/build/brilliantsole.js.map +1 -0
  7. package/build/brilliantsole.ls.js +4872 -0
  8. package/build/brilliantsole.ls.js.map +1 -0
  9. package/build/brilliantsole.min.js +6 -0
  10. package/build/brilliantsole.min.js.map +1 -0
  11. package/build/brilliantsole.module.d.ts +908 -0
  12. package/build/brilliantsole.module.js +5412 -0
  13. package/build/brilliantsole.module.js.map +1 -0
  14. package/build/brilliantsole.module.min.d.ts +908 -0
  15. package/build/brilliantsole.module.min.js +6 -0
  16. package/build/brilliantsole.module.min.js.map +1 -0
  17. package/build/brilliantsole.node.module.d.ts +908 -0
  18. package/build/brilliantsole.node.module.js +5906 -0
  19. package/build/brilliantsole.node.module.js.map +1 -0
  20. package/build/dts/BS.d.ts +25 -0
  21. package/build/dts/Device.d.ts +136 -0
  22. package/build/dts/DeviceInformationManager.d.ts +56 -0
  23. package/build/dts/DeviceManager.d.ts +67 -0
  24. package/build/dts/FileTransferManager.d.ts +84 -0
  25. package/build/dts/FirmwareManager.d.ts +71 -0
  26. package/build/dts/InformationManager.d.ts +66 -0
  27. package/build/dts/TfliteManager.d.ts +92 -0
  28. package/build/dts/connection/BaseConnectionManager.d.ts +59 -0
  29. package/build/dts/connection/ClientConnectionManager.d.ts +23 -0
  30. package/build/dts/connection/WebSocketClientConnectionManager.d.ts +23 -0
  31. package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +10 -0
  32. package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +42 -0
  33. package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +20 -0
  34. package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +14 -0
  35. package/build/dts/connection/webSocket/ClientConnectionManager.d.ts +23 -0
  36. package/build/dts/connection/webSocket/WebSocketClientConnectionManager.d.ts +23 -0
  37. package/build/dts/devicePair/DevicePair.d.ts +60 -0
  38. package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +25 -0
  39. package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +33 -0
  40. package/build/dts/scanner/BaseScanner.d.ts +66 -0
  41. package/build/dts/scanner/NobleScanner.d.ts +17 -0
  42. package/build/dts/scanner/Scanner.d.ts +3 -0
  43. package/build/dts/sensor/BarometerSensorDataManager.d.ts +16 -0
  44. package/build/dts/sensor/MotionSensorDataManager.d.ts +69 -0
  45. package/build/dts/sensor/PressureSensorDataManager.d.ts +36 -0
  46. package/build/dts/sensor/SensorConfigurationManager.d.ts +44 -0
  47. package/build/dts/sensor/SensorDataManager.d.ts +40 -0
  48. package/build/dts/server/BaseClient.d.ts +85 -0
  49. package/build/dts/server/BaseServer.d.ts +48 -0
  50. package/build/dts/server/ServerUtils.d.ts +23 -0
  51. package/build/dts/server/udp/UDPServer.d.ts +11 -0
  52. package/build/dts/server/udp/UDPUtils.d.ts +9 -0
  53. package/build/dts/server/websocket/WebSocketClient.d.ts +17 -0
  54. package/build/dts/server/websocket/WebSocketServer.d.ts +13 -0
  55. package/build/dts/server/websocket/WebSocketUtils.d.ts +9 -0
  56. package/build/dts/utils/ArrayBufferUtils.d.ts +7 -0
  57. package/build/dts/utils/ArrayUtils.d.ts +2 -0
  58. package/build/dts/utils/CenterOfPressureHelper.d.ts +15 -0
  59. package/build/dts/utils/Console.d.ts +34 -0
  60. package/build/dts/utils/EventDispatcher.d.ts +50 -0
  61. package/build/dts/utils/EventUtils.d.ts +6 -0
  62. package/build/dts/utils/MathUtils.d.ts +21 -0
  63. package/build/dts/utils/ParseUtils.d.ts +5 -0
  64. package/build/dts/utils/RangeHelper.d.ts +8 -0
  65. package/build/dts/utils/Text.d.ts +6 -0
  66. package/build/dts/utils/Timer.d.ts +14 -0
  67. package/build/dts/utils/TypeScriptUtils.d.ts +19 -0
  68. package/build/dts/utils/cbor.d.ts +6 -0
  69. package/build/dts/utils/checksum.d.ts +3 -0
  70. package/build/dts/utils/environment.d.ts +13 -0
  71. package/build/dts/utils/mcumgr.d.ts +88 -0
  72. package/build/dts/utils/stringUtils.d.ts +2 -0
  73. package/build/dts/vibration/VibrationManager.d.ts +45 -0
  74. package/build/dts/vibration/VibrationWaveformEffects.d.ts +2 -0
  75. package/build/index.d.ts +908 -0
  76. package/build/index.node.d.ts +908 -0
  77. package/examples/3d/index.html +109 -0
  78. package/examples/3d/scene.html +57 -0
  79. package/examples/3d/script.js +419 -0
  80. package/examples/balance/index.html +138 -0
  81. package/examples/balance/script.js +243 -0
  82. package/examples/basic/index.html +327 -0
  83. package/examples/basic/script.js +1093 -0
  84. package/examples/center-of-pressure/index.html +132 -0
  85. package/examples/center-of-pressure/script.js +207 -0
  86. package/examples/device-pair/index.html +72 -0
  87. package/examples/device-pair/script.js +187 -0
  88. package/examples/edge-impulse/index.html +94 -0
  89. package/examples/edge-impulse/script.js +1033 -0
  90. package/examples/graph/index.html +83 -0
  91. package/examples/graph/script.js +469 -0
  92. package/examples/machine-learning/index.html +366 -0
  93. package/examples/machine-learning/script.js +1774 -0
  94. package/examples/pressure/index.html +145 -0
  95. package/examples/pressure/script.js +201 -0
  96. package/examples/recording/index.html +187 -0
  97. package/examples/recording/script.js +736 -0
  98. package/examples/server/index.html +266 -0
  99. package/examples/server/script.js +925 -0
  100. package/examples/utils/aframe/fingertip-button-component.js +201 -0
  101. package/examples/utils/aframe/fingertip-collider-target-component.js +102 -0
  102. package/examples/utils/aframe/fingertip-colliders-component.js +147 -0
  103. package/examples/utils/three/three.module.min.js +24846 -0
  104. package/examples/webxr/index.html +221 -0
  105. package/examples/webxr/script.js +1127 -0
  106. package/package.json +83 -0
  107. package/src/BS.ts +68 -0
  108. package/src/Device.ts +734 -0
  109. package/src/DeviceInformationManager.ts +146 -0
  110. package/src/DeviceManager.ts +354 -0
  111. package/src/FileTransferManager.ts +452 -0
  112. package/src/FirmwareManager.ts +357 -0
  113. package/src/InformationManager.ts +283 -0
  114. package/src/TfliteManager.ts +450 -0
  115. package/src/connection/BaseConnectionManager.ts +255 -0
  116. package/src/connection/ClientConnectionManager.ts +120 -0
  117. package/src/connection/bluetooth/BluetoothConnectionManager.ts +34 -0
  118. package/src/connection/bluetooth/NobleConnectionManager.ts +302 -0
  119. package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +269 -0
  120. package/src/connection/bluetooth/bluetoothUUIDs.ts +218 -0
  121. package/src/devicePair/DevicePair.ts +253 -0
  122. package/src/devicePair/DevicePairPressureSensorDataManager.ts +82 -0
  123. package/src/devicePair/DevicePairSensorDataManager.ts +90 -0
  124. package/src/scanner/BaseScanner.ts +189 -0
  125. package/src/scanner/NobleScanner.ts +195 -0
  126. package/src/scanner/Scanner.ts +16 -0
  127. package/src/sensor/BarometerSensorDataManager.ts +41 -0
  128. package/src/sensor/MotionSensorDataManager.ts +151 -0
  129. package/src/sensor/PressureSensorDataManager.ts +112 -0
  130. package/src/sensor/SensorConfigurationManager.ts +177 -0
  131. package/src/sensor/SensorDataManager.ts +166 -0
  132. package/src/server/BaseClient.ts +368 -0
  133. package/src/server/BaseServer.ts +344 -0
  134. package/src/server/ServerUtils.ts +93 -0
  135. package/src/server/udp/UDPServer.ts +229 -0
  136. package/src/server/udp/UDPUtils.ts +20 -0
  137. package/src/server/websocket/WebSocketClient.ts +179 -0
  138. package/src/server/websocket/WebSocketServer.ts +184 -0
  139. package/src/server/websocket/WebSocketUtils.ts +20 -0
  140. package/src/utils/ArrayBufferUtils.ts +88 -0
  141. package/src/utils/ArrayUtils.ts +15 -0
  142. package/src/utils/CenterOfPressureHelper.ts +39 -0
  143. package/src/utils/Console.ts +156 -0
  144. package/src/utils/EventDispatcher.ts +153 -0
  145. package/src/utils/EventUtils.ts +41 -0
  146. package/src/utils/MathUtils.ts +53 -0
  147. package/src/utils/ParseUtils.ts +46 -0
  148. package/src/utils/RangeHelper.ts +38 -0
  149. package/src/utils/Text.ts +30 -0
  150. package/src/utils/Timer.ts +72 -0
  151. package/src/utils/TypeScriptUtils.ts +22 -0
  152. package/src/utils/cbor.js +429 -0
  153. package/src/utils/checksum.ts +41 -0
  154. package/src/utils/environment.ts +46 -0
  155. package/src/utils/mcumgr.js +444 -0
  156. package/src/utils/stringUtils.ts +11 -0
  157. package/src/vibration/VibrationManager.ts +308 -0
  158. package/src/vibration/VibrationWaveformEffects.ts +128 -0
@@ -0,0 +1,1774 @@
1
+ import * as BS from "../../build/brilliantsole.module.js";
2
+ import * as THREE from "../utils/three/three.module.min.js";
3
+ window.BS = BS;
4
+ console.log({ BS });
5
+ BS.setAllConsoleLevelFlags({ log: false });
6
+ //BS.setConsoleLevelFlagsForType("EventDispatcher", { log: true });
7
+
8
+ // VIBRATION
9
+
10
+ /**
11
+ * vibrates all connected insoles with a single waveformEffect - use to indicate stuff
12
+ * @param {BS.VibrationWaveformEffect} effect
13
+ */
14
+ function vibrate(effect) {
15
+ BS.DeviceManager.ConnectedDevices.forEach((device) => {
16
+ device.triggerVibration([
17
+ {
18
+ type: "waveformEffect",
19
+ segments: [{ effect }],
20
+ },
21
+ ]);
22
+ });
23
+ }
24
+
25
+ // GET DEVICES
26
+
27
+ /** @type {HTMLTemplateElement} */
28
+ const availableDeviceTemplate = document.getElementById("availableDeviceTemplate");
29
+ const availableDevicesContainer = document.getElementById("availableDevices");
30
+ /** @param {BS.Device[]} availableDevices */
31
+ function onAvailableDevices(availableDevices) {
32
+ availableDevicesContainer.innerHTML = "";
33
+ if (availableDevices.length == 0) {
34
+ availableDevicesContainer.innerText = "no devices available";
35
+ } else {
36
+ availableDevices.forEach((availableDevice) => {
37
+ let availableDeviceContainer = availableDeviceTemplate.content.cloneNode(true).querySelector(".availableDevice");
38
+ availableDeviceContainer.querySelector(".name").innerText = availableDevice.name;
39
+ availableDeviceContainer.querySelector(".type").innerText = availableDevice.type;
40
+
41
+ /** @type {HTMLButtonElement} */
42
+ const toggleConnectionButton = availableDeviceContainer.querySelector(".toggleConnection");
43
+ toggleConnectionButton.addEventListener("click", () => {
44
+ availableDevice.toggleConnection();
45
+ });
46
+ window.addEventListener("createNeuralNetwork", () => {
47
+ toggleConnectionButton.disabled = true;
48
+ });
49
+ const onConnectionStatusUpdate = () => {
50
+ switch (availableDevice.connectionStatus) {
51
+ case "connected":
52
+ case "notConnected":
53
+ toggleConnectionButton.disabled = false;
54
+ toggleConnectionButton.innerText = availableDevice.isConnected ? "disconnect" : "connect";
55
+ break;
56
+ case "connecting":
57
+ case "disconnecting":
58
+ toggleConnectionButton.disabled = true;
59
+ toggleConnectionButton.innerText = availableDevice.connectionStatus;
60
+ break;
61
+ }
62
+ };
63
+ availableDevice.addEventListener("connectionStatus", () => onConnectionStatusUpdate());
64
+ onConnectionStatusUpdate();
65
+ availableDevicesContainer.appendChild(availableDeviceContainer);
66
+ });
67
+ }
68
+ }
69
+ async function getDevices() {
70
+ const availableDevices = await BS.DeviceManager.GetDevices();
71
+ if (!availableDevices) {
72
+ return;
73
+ }
74
+ onAvailableDevices(availableDevices);
75
+ }
76
+
77
+ BS.DeviceManager.AddEventListener("availableDevices", (event) => {
78
+ const { availableDevices } = event.message;
79
+ onAvailableDevices(availableDevices);
80
+ });
81
+ getDevices();
82
+
83
+ // ADD DEVICE
84
+
85
+ /** @type {HTMLButtonElement} */
86
+ const addDeviceButton = document.getElementById("addDevice");
87
+ addDeviceButton.addEventListener("click", () => {
88
+ const device = new BS.Device();
89
+ device.connect();
90
+ });
91
+ window.addEventListener("createNeuralNetwork", () => {
92
+ addDeviceButton.disabled = true;
93
+ });
94
+
95
+ // CONNECTED DEVICES
96
+
97
+ const connectedDevicesContainer = document.getElementById("connectedDevices");
98
+ /** @type {HTMLTemplateElement} */
99
+ const connectedDeviceTemplate = document.getElementById("connectedDeviceTemplate");
100
+
101
+ BS.DeviceManager.AddEventListener("deviceConnected", (event) => {
102
+ const { device } = event.message;
103
+ console.log("deviceConnected", device);
104
+ const connectedDeviceContainer = connectedDeviceTemplate.content.cloneNode(true).querySelector(".connectedDevice");
105
+ connectedDeviceContainer.querySelector(".name").innerText = device.name;
106
+ connectedDeviceContainer.querySelector(".type").innerText = device.type;
107
+
108
+ /** @type {HTMLButtonElement} */
109
+ const disconnectButton = connectedDeviceContainer.querySelector(".disconnect");
110
+ disconnectButton.addEventListener("click", () => {
111
+ disconnectButton.innerText = "disconnecting...";
112
+ disconnectButton.disabled = true;
113
+ device.disconnect();
114
+ });
115
+ device.addEventListener("notConnected", () => {
116
+ connectedDeviceContainer.remove();
117
+ });
118
+
119
+ /** @type {HTMLButtonElement} */
120
+ const toggleSelectonButton = connectedDeviceContainer.querySelector(".toggleSelection");
121
+ toggleSelectonButton.addEventListener("click", () => {
122
+ toggleSelectonButton.disabled = true;
123
+ toggleDeviceSelection(device);
124
+ });
125
+ window.addEventListener("createNeuralNetwork", () => {
126
+ toggleSelectonButton.disabled = true;
127
+ });
128
+ window.addEventListener("deviceSelection", (event) => {
129
+ if (device != event.detail.device) {
130
+ return;
131
+ }
132
+ toggleSelectonButton.innerText = isDeviceSelected(device) ? "deselect" : "select";
133
+ toggleSelectonButton.disabled = false;
134
+ });
135
+
136
+ window.addEventListener("createNeuralNetwork", () => {
137
+ disconnectButton.disabled = true;
138
+ });
139
+
140
+ connectedDevicesContainer.appendChild(connectedDeviceContainer);
141
+ });
142
+
143
+ // SELECTED DEVICES
144
+
145
+ const selectedDevicesContainer = document.getElementById("selectedDevices");
146
+ /** @type {HTMLTemplateElement} */
147
+ const selectedDeviceTemplate = document.getElementById("selectedDeviceTemplate");
148
+
149
+ /** @type {Object.<string, HTMLElement>} */
150
+ const selectedDeviceContainers = {};
151
+
152
+ /** @type {BS.Device[]} */
153
+ const selectedDevices = [];
154
+ /** @param {BS.Device} device */
155
+ function isDeviceSelected(device) {
156
+ return selectedDevices.includes(device);
157
+ }
158
+
159
+ /** @param {BS.Device} device */
160
+ function selectDevice(device) {
161
+ if (isDeviceSelected(device)) {
162
+ console.log("device already selected");
163
+ return;
164
+ }
165
+ selectedDevices.push(device);
166
+
167
+ let selectedDeviceContainer = selectedDeviceContainers[device.id];
168
+ if (!selectedDeviceContainer) {
169
+ selectedDeviceContainer = selectedDeviceTemplate.content.cloneNode(true).querySelector(".selectedDevice");
170
+
171
+ selectedDeviceContainer.querySelector(".name").innerText = device.name;
172
+ selectedDeviceContainer.querySelector(".type").innerText = device.type;
173
+ selectedDeviceContainer.querySelector(".index").innerText = selectedDevices.indexOf(device);
174
+
175
+ /** @type {HTMLPreElement} */
176
+ const sensorConfigurationPre = selectedDeviceContainer.querySelector("pre.sensorConfiguration");
177
+ const updateSensorConfigurationPre = () => {
178
+ sensorConfigurationPre.textContent = JSON.stringify(device.sensorConfiguration, null, 2);
179
+ };
180
+ device.addEventListener("getSensorConfiguration", () => {
181
+ updateSensorConfigurationPre();
182
+ });
183
+ updateSensorConfigurationPre();
184
+
185
+ /** @type {HTMLButtonElement} */
186
+ const deselectButton = selectedDeviceContainer.querySelector(".deselect");
187
+ deselectButton.addEventListener("click", () => {
188
+ deselectDevice(device);
189
+ });
190
+ window.addEventListener("createNeuralNetwork", () => {
191
+ deselectButton.disabled = true;
192
+ });
193
+
194
+ selectedDeviceContainers[device.id] = selectedDeviceContainer;
195
+ }
196
+
197
+ selectedDevicesContainer.appendChild(selectedDeviceContainer);
198
+
199
+ window.dispatchEvent(new CustomEvent("deviceSelection", { detail: { device } }));
200
+ }
201
+
202
+ /** @param {BS.Device} device */
203
+ function deselectDevice(device) {
204
+ if (!isDeviceSelected(device)) {
205
+ console.log("device already not selected");
206
+ return;
207
+ }
208
+ selectedDevices.splice(selectedDevices.indexOf(device), 1);
209
+
210
+ const selectedDeviceContainer = selectedDeviceContainers[device.id];
211
+ if (!selectedDeviceContainer) {
212
+ console.log("selectedDeviceContainer not found for device");
213
+ return;
214
+ }
215
+ selectedDeviceContainer.remove();
216
+
217
+ window.dispatchEvent(new CustomEvent("deviceSelection", { detail: { device } }));
218
+ }
219
+
220
+ /** @param {BS.Device} device */
221
+ function toggleDeviceSelection(device) {
222
+ if (isDeviceSelected(device)) {
223
+ deselectDevice(device);
224
+ } else {
225
+ selectDevice(device);
226
+ }
227
+ }
228
+
229
+ // TASK
230
+
231
+ /** @type {BS.TfliteTask} */
232
+ let task = "classification";
233
+
234
+ /** @type {HTMLSelectElement} */
235
+ const taskSelect = document.getElementById("task");
236
+ /** @type {HTMLOptGroupElement} */
237
+ const taskSelectOptgroup = taskSelect.querySelector("optgroup");
238
+ BS.TfliteTasks.forEach((task) => {
239
+ taskSelectOptgroup.appendChild(new Option(task));
240
+ });
241
+ taskSelect.addEventListener("input", () => {
242
+ task = taskSelect.value;
243
+ console.log({ task });
244
+ window.dispatchEvent(new CustomEvent("task", { detail: { task } }));
245
+ });
246
+ taskSelect.dispatchEvent(new Event("input"));
247
+
248
+ window.addEventListener("createNeuralNetwork", () => {
249
+ taskSelect.disabled = true;
250
+ });
251
+
252
+ window.addEventListener("loadConfig", () => {
253
+ taskSelect.value = config.task;
254
+ taskSelect.dispatchEvent(new Event("input"));
255
+ });
256
+
257
+ // INPUTS
258
+
259
+ /** @type {BS.ContinuousSensorType[]} */
260
+ let sensorTypes = [];
261
+ function getInputs() {
262
+ /** @type {string[]} */
263
+ const _inputs = [];
264
+ sensorTypes.forEach((sensorType) => {
265
+ switch (sensorType) {
266
+ case "pressure":
267
+ for (let index = 0; index < BS.DefaultNumberOfPressureSensors; index++) {
268
+ _inputs.push(`${sensorType}.${index}`);
269
+ }
270
+ break;
271
+ case "acceleration":
272
+ case "gravity":
273
+ case "linearAcceleration":
274
+ case "gyroscope":
275
+ case "magnetometer":
276
+ ["x", "y", "z"].forEach((component) => {
277
+ _inputs.push(`${sensorType}.${component}`);
278
+ });
279
+ break;
280
+ case "gameRotation":
281
+ case "rotation":
282
+ ["x", "y", "z", "w"].forEach((component) => {
283
+ _inputs.push(`${sensorType}.${component}`);
284
+ });
285
+ break;
286
+ case "barometer":
287
+ // FILL
288
+ break;
289
+ }
290
+ });
291
+
292
+ /** @type {string[]} */
293
+ let inputs = [];
294
+
295
+ for (let index = 0; index < selectedDevices.length; index++) {
296
+ inputs.push(..._inputs.map((input) => `${index}.${input}`));
297
+ }
298
+
299
+ console.log({ inputs });
300
+ return inputs;
301
+ }
302
+
303
+ const sensorTypesContainer = document.getElementById("sensorTypes");
304
+ /** @type {HTMLTemplateElement} */
305
+ const sensorTypeTemplate = document.getElementById("sensorTypeTemplate");
306
+ /** @type {Object.<string, HTMLElement>} */
307
+ const sensorTypeContainers = {};
308
+
309
+ BS.TfliteSensorTypes.forEach((sensorType) => {
310
+ const sensorTypeContainer = sensorTypeTemplate.content.cloneNode(true).querySelector(".sensorType");
311
+ sensorTypeContainer.querySelector(".name").innerText = sensorType;
312
+
313
+ /** @type {HTMLInputElement} */
314
+ const isSensorEnabledInput = sensorTypeContainer.querySelector(".enabled");
315
+ isSensorEnabledInput.addEventListener("input", () => {
316
+ if (isSensorEnabledInput.checked) {
317
+ sensorTypes.push(sensorType);
318
+ } else {
319
+ sensorTypes.splice(sensorTypes.indexOf(sensorType), 1);
320
+ }
321
+ sensorTypes.sort((a, b) => BS.ContinuousSensorTypes.indexOf(a) - BS.ContinuousSensorTypes.indexOf(b));
322
+ console.log("sensorTypes", sensorTypes);
323
+ window.dispatchEvent(new CustomEvent("sensorTypes", { detail: { sensorTypes } }));
324
+ });
325
+
326
+ window.addEventListener("createNeuralNetwork", () => {
327
+ isSensorEnabledInput.disabled = true;
328
+ });
329
+
330
+ window.addEventListener("loadConfig", () => {
331
+ if (config.sensorTypes.includes(sensorType)) {
332
+ isSensorEnabledInput.checked = true;
333
+ isSensorEnabledInput.dispatchEvent(new Event("input"));
334
+ }
335
+ });
336
+
337
+ sensorTypeContainers[sensorType] = sensorTypeContainer;
338
+
339
+ sensorTypesContainer.appendChild(sensorTypeContainer);
340
+ });
341
+
342
+ // OUTPUTS
343
+
344
+ /** @type {HTMLTemplateElement} */
345
+ const outputTemplate = document.getElementById("outputTemplate");
346
+ /** @type {HTMLElement[]} */
347
+ const outputContainers = [];
348
+
349
+ const outputsContainer = document.getElementById("outputs");
350
+
351
+ let numberOfOutputs = 0;
352
+ /** @type {string[]} */
353
+ let outputLabels = [];
354
+ function updateOutputLabels() {
355
+ /** @type {string[]} */
356
+ const updatedOutputsLabels = [];
357
+
358
+ outputContainers.some((container) => {
359
+ updatedOutputsLabels.push(container.querySelector(".label").value);
360
+ return updatedOutputsLabels.length == numberOfOutputs;
361
+ });
362
+
363
+ outputLabels = updatedOutputsLabels;
364
+ console.log({ outputLabels });
365
+
366
+ window.dispatchEvent(new CustomEvent("outputLabels", { detail: { outputLabels } }));
367
+ }
368
+
369
+ function getOutputValues() {
370
+ /** @type {number[]} */
371
+ let outputValues = [];
372
+ outputContainers.some((container) => {
373
+ const outputValue = Number(container.querySelector(".value").value);
374
+ outputValues.push(outputValue);
375
+ return outputValues.length == numberOfOutputs;
376
+ });
377
+
378
+ if (task == "classification") {
379
+ outputValues = outputValues.reduce((_outputValues, outputValue, index) => {
380
+ if (outputValue) {
381
+ _outputValues.push(outputLabels[index]);
382
+ }
383
+ return _outputValues;
384
+ }, []);
385
+ } else {
386
+ outputValues = outputValues.reduce((_outputValues, outputValue, index) => {
387
+ _outputValues[outputLabels[index]] = outputValue;
388
+ return _outputValues;
389
+ }, {});
390
+ }
391
+
392
+ console.log({ outputValues });
393
+ return outputValues;
394
+ }
395
+
396
+ /** @type {HTMLInputElement} */
397
+ const numberOfOutputsInput = document.getElementById("numberOfOutputs");
398
+ numberOfOutputsInput.addEventListener("input", () => {
399
+ setNumberOfOutputs(Number(numberOfOutputsInput.value));
400
+ });
401
+ numberOfOutputsInput.dispatchEvent(new Event("input"));
402
+
403
+ window.addEventListener("createNeuralNetwork", () => {
404
+ numberOfOutputsInput.disabled = true;
405
+ });
406
+
407
+ /** @param {number} newNumberOfOutputs */
408
+ function setNumberOfOutputs(newNumberOfOutputs) {
409
+ numberOfOutputs = newNumberOfOutputs;
410
+ console.log({ numberOfOutputs });
411
+
412
+ while (outputContainers.length < numberOfOutputs) {
413
+ /** @type {HTMLElement} */
414
+ const outputContainer = outputTemplate.content.cloneNode(true).querySelector(".output");
415
+
416
+ const index = outputContainers.length;
417
+ outputContainer.setAttribute("order", index);
418
+
419
+ /** @type {HTMLInputElement} */
420
+ const labelInput = outputContainer.querySelector(".label");
421
+ labelInput.value = window?.config?.outputLabels[index] || `output${index}`;
422
+ labelInput.addEventListener("input", () => updateOutputLabels());
423
+
424
+ window.addEventListener("loadConfig", () => {
425
+ labelInput.value = config.outputLabels[index];
426
+ });
427
+
428
+ /** @type {HTMLInputElement} */
429
+ const valueInput = outputContainer.querySelector(".value");
430
+
431
+ window.addEventListener("createNeuralNetwork", () => {
432
+ labelInput.disabled = true;
433
+ valueInput.disabled = false;
434
+
435
+ if (task == "classification") {
436
+ valueInput.step = 1;
437
+ } else {
438
+ valueInput.step = 0.01;
439
+ }
440
+ });
441
+
442
+ outputsContainer.appendChild(outputContainer);
443
+ outputContainers.push(outputContainer);
444
+ }
445
+
446
+ outputContainers.forEach((outputContainer, index) => {
447
+ if (index < numberOfOutputs) {
448
+ outputsContainer.appendChild(outputContainer);
449
+ } else {
450
+ outputContainer.remove();
451
+ }
452
+ });
453
+
454
+ window.dispatchEvent(new CustomEvent("numberOfOutputs", { detail: { numberOfOutputs } }));
455
+
456
+ updateOutputLabels();
457
+ }
458
+
459
+ window.addEventListener("loadConfig", () => {
460
+ numberOfOutputsInput.value = config.numberOfOutputs;
461
+ numberOfOutputsInput.dispatchEvent(new Event("input"));
462
+ });
463
+
464
+ window.addEventListener("task", () => {
465
+ if (task == "classification" && numberOfOutputs < 2) {
466
+ setNumberOfOutputs(2);
467
+ }
468
+ numberOfOutputsInput.min = task == "classification" ? 2 : 1;
469
+ });
470
+
471
+ // SAMPLING
472
+
473
+ let numberOfSamples = 0;
474
+ let samplingPeriod = 0;
475
+ let samplingRate = 0;
476
+
477
+ function updateSamplingPeriod() {
478
+ samplingPeriod = (numberOfSamples - 1) * samplingRate;
479
+ samplingPeriodInput.value = samplingPeriod;
480
+ //console.log({ samplingPeriod });
481
+ }
482
+
483
+ /** @type {HTMLInputElement} */
484
+ const numberOfSamplesInput = document.getElementById("numberOfSamples");
485
+ numberOfSamplesInput.addEventListener("input", () => {
486
+ numberOfSamples = Number(numberOfSamplesInput.value);
487
+ //console.log({ numberOfSamples });
488
+ window.dispatchEvent(new CustomEvent("numberOfSamples", { detail: { numberOfSamples } }));
489
+ updateSamplingPeriod();
490
+ });
491
+ numberOfSamples = Number(numberOfSamplesInput.value);
492
+
493
+ /** @type {HTMLInputElement} */
494
+ const samplingPeriodInput = document.getElementById("samplingPeriod");
495
+
496
+ /** @type {HTMLInputElement} */
497
+ const samplingRateInput = document.getElementById("samplingRate");
498
+
499
+ samplingRateInput.addEventListener("input", () => {
500
+ samplingRate = Number(samplingRateInput.value);
501
+ //console.log({ samplingRate });
502
+ samplingPeriodInput.step = samplingRate;
503
+ window.dispatchEvent(new CustomEvent("samplingRate", { detail: { samplingRate } }));
504
+ updateSamplingPeriod();
505
+ });
506
+ samplingRate = Number(samplingRateInput.value);
507
+
508
+ updateSamplingPeriod();
509
+
510
+ window.addEventListener("createNeuralNetwork", () => {
511
+ numberOfSamplesInput.disabled = true;
512
+ samplingRateInput.disabled = true;
513
+ });
514
+
515
+ window.addEventListener("loadConfig", () => {
516
+ numberOfSamplesInput.value = config.numberOfSamples;
517
+ numberOfSamplesInput.dispatchEvent(new Event("input"));
518
+
519
+ samplingRateInput.value = config.samplingRate;
520
+ samplingRateInput.dispatchEvent(new Event("input"));
521
+ });
522
+
523
+ // NEURAL NETWORK PARAMETERS
524
+
525
+ /** @type {number} */
526
+ let hiddenUnits;
527
+ /** @type {number} */
528
+ let learningRate;
529
+
530
+ /** @type {HTMLInputElement} */
531
+ const hiddenUnitsInput = document.getElementById("hiddenUnits");
532
+ hiddenUnitsInput.addEventListener("input", () => {
533
+ hiddenUnits = Number(hiddenUnitsInput.value);
534
+ console.log({ hiddenUnits });
535
+ window.dispatchEvent(new CustomEvent("hiddenUnits", { detail: { hiddenUnits } }));
536
+ });
537
+ hiddenUnitsInput.dispatchEvent(new Event("input"));
538
+
539
+ /** @type {HTMLInputElement} */
540
+ const learningRateInput = document.getElementById("learningRate");
541
+ learningRateInput.addEventListener("input", () => {
542
+ learningRate = Number(learningRateInput.value);
543
+ console.log({ learningRate });
544
+ window.dispatchEvent(new CustomEvent("learningRate", { detail: { learningRate } }));
545
+ });
546
+ learningRateInput.dispatchEvent(new Event("input"));
547
+
548
+ window.addEventListener("createNeuralNetwork", () => {
549
+ hiddenUnitsInput.disabled = true;
550
+ learningRateInput.disabled = true;
551
+ });
552
+
553
+ window.addEventListener("loadConfig", () => {
554
+ hiddenUnitsInput.value = config.hiddenUnits;
555
+ hiddenUnitsInput.dispatchEvent(new Event("input"));
556
+
557
+ learningRateInput.value = config.learningRate;
558
+ learningRateInput.dispatchEvent(new Event("input"));
559
+ });
560
+
561
+ // CREATE NEURAL NETWORK
562
+
563
+ let neuralNetwork;
564
+
565
+ function canCreateNeuralNetwork() {
566
+ return (
567
+ !neuralNetwork &&
568
+ selectedDevices.length > 0 &&
569
+ sensorTypes.length > 0 &&
570
+ outputLabels.length >= (task == "classification" ? 2 : 1)
571
+ );
572
+ }
573
+ function checkIfCanCreateNeuralNetwork() {
574
+ createNeuralNetworkButton.disabled = !canCreateNeuralNetwork();
575
+ }
576
+
577
+ ["task", "sensorTypes", "outputLabels", "deviceSelection", "createNeuralNetwork"].forEach((eventType) => {
578
+ window.addEventListener(eventType, () => {
579
+ checkIfCanCreateNeuralNetwork();
580
+ });
581
+ });
582
+
583
+ /** @type {HTMLButtonElement} */
584
+ const createNeuralNetworkButton = document.getElementById("createNeuralNetwork");
585
+ createNeuralNetworkButton.addEventListener("click", () => {
586
+ if (!canCreateNeuralNetwork()) {
587
+ return;
588
+ }
589
+ neuralNetwork = ml5.neuralNetwork({
590
+ task,
591
+ inputs: getInputs().length,
592
+ outputs: outputLabels,
593
+ hiddenUnits,
594
+ learningRate,
595
+ debug: true,
596
+ });
597
+ console.log({ neuralNetwork });
598
+ window.neuralNetwork = neuralNetwork;
599
+ window.dispatchEvent(new CustomEvent("createNeuralNetwork", { detail: { neuralNetwork } }));
600
+ });
601
+
602
+ window.addEventListener("createNeuralNetwork", () => {
603
+ createNeuralNetworkButton.disabled = true;
604
+ });
605
+
606
+ // ADD DATA
607
+
608
+ /** @type {BS.SensorConfiguration} */
609
+ const sensorConfiguration = {};
610
+ window.addEventListener("createNeuralNetwork", () => {
611
+ sensorTypes.forEach((sensorType) => {
612
+ sensorConfiguration[sensorType] = samplingRate;
613
+ });
614
+ console.log({ sensorConfiguration });
615
+ });
616
+
617
+ let isSensorDataEnabled = false;
618
+
619
+ /** @type {HTMLButtonElement[]} */
620
+ const toggleSensorDataInputs = document.querySelectorAll(".toggleSensorData");
621
+ toggleSensorDataInputs.forEach((toggleSensorDataInput) => {
622
+ toggleSensorDataInput.addEventListener("input", () => {
623
+ isSensorDataEnabled = toggleSensorDataInput.checked;
624
+ console.log({ isSensorDataEnabled });
625
+ if (isSensorDataEnabled) {
626
+ setSensorConfiguration(sensorConfiguration);
627
+ } else {
628
+ clearSensorConfiguration();
629
+ }
630
+ window.dispatchEvent(new CustomEvent("isSensorDataEnabled", { detail: { isSensorDataEnabled } }));
631
+ });
632
+
633
+ window.addEventListener("createNeuralNetwork", () => {
634
+ toggleSensorDataInput.disabled = false;
635
+ });
636
+ window.addEventListener("isSensorDataEnabled", () => {
637
+ toggleSensorDataInput.checked = isSensorDataEnabled;
638
+ });
639
+ });
640
+
641
+ /** @param {BS.SensorConfiguration} sensorConfiguration */
642
+ function setSensorConfiguration(sensorConfiguration) {
643
+ selectedDevices.forEach((device) => {
644
+ device.setSensorConfiguration(sensorConfiguration);
645
+ });
646
+ }
647
+ function clearSensorConfiguration() {
648
+ selectedDevices.forEach((device) => {
649
+ device.clearSensorConfiguration();
650
+ });
651
+ }
652
+
653
+ /** @type {HTMLButtonElement} */
654
+ const addDataButton = document.getElementById("addData");
655
+ addDataButton.addEventListener("click", () => {
656
+ addData();
657
+ });
658
+ const updateAddDataButton = () => {
659
+ addDataButton.disabled = !isSensorDataEnabled || isTraining || neuralNetwork.neuralNetwork.isTrained;
660
+ };
661
+ ["isSensorDataEnabled", "training"].forEach((eventType) => {
662
+ window.addEventListener(eventType, () => {
663
+ updateAddDataButton();
664
+ });
665
+ });
666
+
667
+ let addDataContinuously = false;
668
+
669
+ /** @type {HTMLButtonElement} */
670
+ const toggleAddDataContinuouslyInput = document.getElementById("toggleAddDataContinuously");
671
+ toggleAddDataContinuouslyInput.addEventListener("input", () => {
672
+ addDataContinuously = toggleAddDataContinuouslyInput.checked;
673
+ console.log({ addDataContinuously });
674
+ });
675
+ window.addEventListener("createNeuralNetwork", () => {
676
+ toggleAddDataContinuouslyInput.disabled = false;
677
+ });
678
+
679
+ async function addData() {
680
+ addDataButton.disabled = true;
681
+ addDataButton.innerText = "collecting data...";
682
+
683
+ const xs = await collectData();
684
+ const ys = getOutputValues();
685
+ console.log({ xs, ys });
686
+ neuralNetwork.addData(xs, ys);
687
+
688
+ addDataButton.innerText = "add data";
689
+ addDataButton.disabled = false;
690
+
691
+ window.dispatchEvent(new CustomEvent("addData", { detail: { xs, ys } }));
692
+
693
+ if (addDataContinuously) {
694
+ addData();
695
+ }
696
+ }
697
+
698
+ /** @typedef {Object.<string, number>} SensorData */
699
+ /** @typedef {Object.<string, SensorData[]>} DeviceData */
700
+ /** @typedef {BS.DeviceData[]} DevicesData */
701
+
702
+ /** @param {BS.DeviceData} deviceData */
703
+ function didFinishCollectingDeviceData(deviceData) {
704
+ return Object.values(deviceData).every((sensorData) => sensorData.length >= numberOfSamples);
705
+ }
706
+ /** @param {BS.DevicesData} devicesData */
707
+ function didFinishCollectingDevicesData(devicesData) {
708
+ return devicesData.every((deviceData) => {
709
+ return didFinishCollectingDeviceData(deviceData);
710
+ });
711
+ }
712
+ /** @param {BS.DevicesData} devicesData */
713
+ function flattenDevicesData(devicesData) {
714
+ /** @type {number[]} */
715
+ const flattenedDevicesData = [];
716
+ devicesData.forEach((deviceData) => {
717
+ flattenedDevicesData.push(...flattenDeviceData(deviceData));
718
+ });
719
+ console.log({ flattenedDevicesData });
720
+ return flattenedDevicesData;
721
+ }
722
+
723
+ const scalars = {
724
+ pressure: 1 / (2 ** 16 - 1),
725
+ linearAcceleration: 1 / 4,
726
+ gyroscope: 1 / 720,
727
+ magnetometer: 1 / 2500,
728
+ };
729
+ /** @param {BS.DeviceData} deviceData */
730
+ function flattenDeviceData(deviceData) {
731
+ /** @type {number[]} */
732
+ const flattenedDeviceData = [];
733
+ for (let sampleIndex = 0; sampleIndex < numberOfSamples; sampleIndex++) {
734
+ sensorTypes.forEach((sensorType) => {
735
+ const sensorData = deviceData[sensorType][sampleIndex];
736
+ const scalar = scalars[sensorType] || 1;
737
+ switch (sensorType) {
738
+ case "acceleration":
739
+ case "gravity":
740
+ case "linearAcceleration":
741
+ case "gyroscope":
742
+ case "magnetometer":
743
+ {
744
+ /** @type {BS.Vector3} */
745
+ const vector3 = sensorData;
746
+ const { x, y, z } = vector3;
747
+
748
+ flattenedDeviceData.push(...[x, y, z].map((value) => value * scalar));
749
+ }
750
+ break;
751
+ case "gameRotation":
752
+ case "rotation":
753
+ {
754
+ /** @type {BS.Quaternion} */
755
+ const quaternion = sensorData;
756
+ const { x, y, z, w } = quaternion;
757
+ flattenedDeviceData.push(...[x, y, z, w].map((value) => value * scalar));
758
+ }
759
+ break;
760
+ case "pressure":
761
+ {
762
+ /** @type {BS.PressureData} */
763
+ const pressure = sensorData;
764
+ flattenedDeviceData.push(...pressure.sensors.map((sensor) => sensor.rawValue * scalar));
765
+ }
766
+ break;
767
+ case "barometer":
768
+ {
769
+ // FILL
770
+ }
771
+ break;
772
+ }
773
+ });
774
+ }
775
+ return flattenedDeviceData.map((value) => (value == 0 ? 0.000001 * Math.random() : value));
776
+ }
777
+
778
+ /** @returns {Promise<number[]>} */
779
+ async function collectData() {
780
+ console.log("collecting data...");
781
+ return new Promise((resolve) => {
782
+ /** @type {BS.DevicesData} */
783
+ const devicesData = [];
784
+
785
+ selectedDevices.forEach((device, index) => {
786
+ /** @type {BS.DeviceData} */
787
+ const deviceData = {};
788
+ sensorTypes.forEach((sensorType) => {
789
+ deviceData[sensorType] = [];
790
+ });
791
+ devicesData[index] = deviceData;
792
+
793
+ const onDeviceSensorData = (event) => {
794
+ console.log(`received sensorData data from device #${index}`);
795
+
796
+ /** @type {BS.ContinuousSensorType} */
797
+ const sensorType = event.message.sensorType;
798
+
799
+ if (!(sensorType in deviceData)) {
800
+ return;
801
+ }
802
+
803
+ if (didFinishCollectingDeviceData(deviceData)) {
804
+ console.log(`finished collecting data for device #${index}`);
805
+ console.log("removing sensorData eventListener");
806
+ device.removeEventListener("sensorData", onDeviceSensorData);
807
+
808
+ if (didFinishCollectingDevicesData(devicesData)) {
809
+ console.log(`finished collecting devices data`);
810
+ const flattenedDevicesData = flattenDevicesData(devicesData);
811
+ resolve(flattenedDevicesData);
812
+ }
813
+ return;
814
+ }
815
+
816
+ if (deviceData[sensorType].length == numberOfSamples) {
817
+ console.log(`finished collecting ${sensorType} data for device #${index}`);
818
+ return;
819
+ }
820
+
821
+ const { [sensorType]: data } = event.message;
822
+ deviceData[sensorType].push(data);
823
+ };
824
+
825
+ console.log("adding sensorData eventListener");
826
+ device.addEventListener("sensorData", onDeviceSensorData);
827
+ });
828
+ });
829
+ }
830
+
831
+ // THROTTLED FUNCTION
832
+
833
+ class ThrottledFunction {
834
+ /**
835
+ * @param {()=>{}} callback
836
+ * @param {number} interval
837
+ */
838
+ constructor(callback, interval = 0) {
839
+ this.callback = callback;
840
+ this.interval = interval;
841
+ }
842
+
843
+ interval = 0;
844
+
845
+ /** @type {()=>{}} */
846
+ callback;
847
+
848
+ #lastTimeTriggered = 0;
849
+
850
+ trigger() {
851
+ const now = Date.now();
852
+ const timeSinceLastTimeTriggered = now - this.#lastTimeTriggered;
853
+ if (timeSinceLastTimeTriggered < this.interval) {
854
+ return;
855
+ }
856
+ this.callback();
857
+ this.#lastTimeTriggered = now;
858
+ }
859
+ }
860
+
861
+ // THRESHOLDS
862
+
863
+ const onThresholdReached = () => {
864
+ if (!neuralNetwork) {
865
+ return;
866
+ }
867
+
868
+ if (!neuralNetwork.neuralNetwork.isTrained) {
869
+ if (!isTraining) {
870
+ addData();
871
+ }
872
+ } else {
873
+ test(false);
874
+ }
875
+ };
876
+ const throttledOnThresholdReached = new ThrottledFunction(onThresholdReached);
877
+
878
+ let thresholdsEnabled = true;
879
+ /** @param {boolean} newThresholdsEnabled */
880
+ function setThresholdsEnabled(newThresholdsEnabled) {
881
+ thresholdsEnabled = newThresholdsEnabled;
882
+ console.log({ thresholdsEnabled });
883
+ window.dispatchEvent(new CustomEvent("thresholdsEnabled", { detail: { thresholdsEnabled } }));
884
+ }
885
+ window.addEventListener("loadConfig", () => {
886
+ toggleThresholdsInput.checked = config.thresholdsEnabled;
887
+ toggleThresholdsInput.dispatchEvent(new Event("input"));
888
+ });
889
+
890
+ /** @type {HTMLInputElement} */
891
+ const toggleThresholdsInput = document.getElementById("toggleThresholds");
892
+ toggleThresholdsInput.addEventListener("input", () => {
893
+ setThresholdsEnabled(toggleThresholdsInput.checked);
894
+ });
895
+
896
+ let captureDelay = 0;
897
+
898
+ /** @type {HTMLInputElement} */
899
+ const captureDelayInput = document.getElementById("captureDelay");
900
+ captureDelayInput.addEventListener("input", () => {
901
+ captureDelay = Number(captureDelayInput.value);
902
+ console.log({ captureDelay });
903
+ throttledOnThresholdReached.interval = captureDelay;
904
+ window.dispatchEvent(new CustomEvent("captureDelay", { detail: { captureDelay } }));
905
+ });
906
+ captureDelayInput.dispatchEvent(new Event("input"));
907
+ window.addEventListener("loadConfig", () => {
908
+ captureDelayInput.value = config.captureDelay;
909
+ captureDelayInput.dispatchEvent(new Event("input"));
910
+ });
911
+
912
+ /** @type {BS.ContinuousSensorType[]} */
913
+ const thresholdSensorTypes = ["linearAcceleration"];
914
+
915
+ const thresholdsContainer = document.getElementById("thresholds");
916
+ const thresholdTemplate = document.getElementById("thresholdTemplate");
917
+ /** @type {Object.<string, HTMLElement>} */
918
+ const thresholdContainers = {};
919
+
920
+ thresholdSensorTypes.forEach((sensorType) => {
921
+ const thresholdContainer = thresholdTemplate.content.cloneNode(true).querySelector(".threshold");
922
+
923
+ thresholdContainer.querySelector(".sensorType").innerText = sensorType;
924
+
925
+ /** @type {HTMLMeterElement} */
926
+ const meterElement = thresholdContainer.querySelector(".meter");
927
+
928
+ const dispatchEvent = () =>
929
+ window.dispatchEvent(new CustomEvent("threshold", { detail: { sensorType, enabled, threshold } }));
930
+
931
+ let enabled = false;
932
+ /** @type {HTMLInputElement} */
933
+ const toggleThresholdInput = thresholdContainer.querySelector(".toggle");
934
+ toggleThresholdInput.addEventListener("input", () => {
935
+ enabled = toggleThresholdInput.checked;
936
+ console.log({ sensorType, enabled });
937
+ thresholdInput.disabled = !enabled;
938
+ meterElement.disabled = !enabled;
939
+ dispatchEvent();
940
+ });
941
+
942
+ let threshold = 0;
943
+ /** @type {HTMLInputElement} */
944
+ const thresholdInput = thresholdContainer.querySelector(".threshold");
945
+ thresholdInput.addEventListener("input", () => {
946
+ threshold = Number(thresholdInput.value);
947
+ meterElement.low = threshold;
948
+ console.log({ sensorType, threshold });
949
+ dispatchEvent();
950
+ });
951
+
952
+ let min = 0;
953
+ let max = 0;
954
+ switch (sensorType) {
955
+ case "linearAcceleration":
956
+ min = 0.01;
957
+ max = 0.5;
958
+ break;
959
+ case "gyroscope":
960
+ min = 0.5;
961
+ max = 1.5;
962
+ break;
963
+ }
964
+ threshold = min;
965
+
966
+ thresholdInput.min = min;
967
+ thresholdInput.max = max;
968
+ thresholdInput.value = max;
969
+
970
+ meterElement.min = min;
971
+ meterElement.max = max;
972
+ meterElement.value = min;
973
+ meterElement.low = Number(thresholdInput.value);
974
+
975
+ window.addEventListener("loadConfig", () => {
976
+ const thresholdInfo = config.thresholds[sensorType];
977
+ if (!thresholdInfo) {
978
+ return;
979
+ }
980
+
981
+ const { enabled, threshold } = thresholdInfo;
982
+
983
+ thresholdInput.value = threshold;
984
+ thresholdInput.dispatchEvent(new Event("input"));
985
+
986
+ toggleThresholdInput.checked = enabled;
987
+ toggleThresholdInput.dispatchEvent(new Event("input"));
988
+ });
989
+
990
+ window.addEventListener("thresholdsEnabled", () => {
991
+ toggleThresholdInput.disabled = !thresholdsEnabled;
992
+
993
+ const disabled = toggleThresholdInput.disabled || !toggleThresholdInput.checked;
994
+ thresholdInput.disabled = disabled;
995
+ meterElement.disabled = disabled;
996
+ });
997
+
998
+ window.addEventListener(`threshold.${sensorType}`, (event) => {
999
+ if (!enabled) {
1000
+ return;
1001
+ }
1002
+
1003
+ /** @type {number} */
1004
+ const value = event.detail.value;
1005
+ meterElement.value = value;
1006
+
1007
+ //console.log({ sensorType, value });
1008
+
1009
+ const reachedThreshold = value >= threshold;
1010
+ if (reachedThreshold) {
1011
+ console.log(`reached ${sensorType} threshold`);
1012
+ throttledOnThresholdReached.trigger();
1013
+ }
1014
+ });
1015
+
1016
+ thresholdContainers[sensorType] = thresholdContainer;
1017
+ thresholdsContainer.appendChild(thresholdContainer);
1018
+ });
1019
+
1020
+ const thresholdVector = new THREE.Vector3();
1021
+ /** @param {BS.Vector3} vector */
1022
+ function getVectorMagnitude(vector) {
1023
+ const { x, y, z } = vector;
1024
+ thresholdVector.set(x, y, z);
1025
+ return thresholdVector.length();
1026
+ }
1027
+
1028
+ const thresholdEuler = new THREE.Euler();
1029
+ const thresholdQuaternion = new THREE.Quaternion();
1030
+ const identityQuaternion = new THREE.Quaternion();
1031
+ /** @param {BS.Vector3} euler */
1032
+ function getEulerMagnitude(euler) {
1033
+ const { x, y, z } = euler;
1034
+ thresholdEuler.set(...[x, y, z].map((value) => THREE.MathUtils.degToRad(value)));
1035
+ thresholdQuaternion.setFromEuler(thresholdEuler);
1036
+ return thresholdQuaternion.angleTo(identityQuaternion);
1037
+ }
1038
+
1039
+ window.addEventListener(
1040
+ "createNeuralNetwork",
1041
+ () => {
1042
+ if (selectedDevices.length == 1) {
1043
+ selectedDevices[0].addEventListener("sensorData", (event) => {
1044
+ if (!thresholdsEnabled) {
1045
+ return;
1046
+ }
1047
+
1048
+ /** @type {BS.ContinuousSensorType} */
1049
+ const sensorType = event.message.sensorType;
1050
+ if (!thresholdSensorTypes.includes(sensorType)) {
1051
+ return;
1052
+ }
1053
+
1054
+ const { [sensorType]: data } = event.message;
1055
+ let value = 0;
1056
+ switch (sensorType) {
1057
+ case "linearAcceleration":
1058
+ value = getVectorMagnitude(data);
1059
+ break;
1060
+ case "gyroscope":
1061
+ value = getEulerMagnitude(data);
1062
+ break;
1063
+ default:
1064
+ throw Error(`uncaught sensorType ${sensorType}`);
1065
+ }
1066
+ window.dispatchEvent(new CustomEvent(`threshold.${sensorType}`, { detail: { value } }));
1067
+ });
1068
+ }
1069
+ },
1070
+ { once: true }
1071
+ );
1072
+
1073
+ // TRAIN NEURAL NETWORK
1074
+
1075
+ /** @type {number} */
1076
+ let epochs;
1077
+ /** @type {number} */
1078
+ let batchSize;
1079
+
1080
+ /** @type {HTMLInputElement} */
1081
+ const epochsInput = document.getElementById("epochs");
1082
+ epochsInput.addEventListener("input", () => {
1083
+ epochs = Number(epochsInput.value);
1084
+ console.log({ epochs });
1085
+ window.dispatchEvent(new CustomEvent("epochs", { detail: { epochs } }));
1086
+ });
1087
+ epochsInput.dispatchEvent(new Event("input"));
1088
+
1089
+ /** @type {HTMLInputElement} */
1090
+ const batchSizeInput = document.getElementById("batchSize");
1091
+ batchSizeInput.addEventListener("input", () => {
1092
+ batchSize = Number(batchSizeInput.value);
1093
+ console.log({ batchSize });
1094
+ window.dispatchEvent(new CustomEvent("batchSize", { detail: { batchSize } }));
1095
+ });
1096
+ batchSizeInput.dispatchEvent(new Event("input"));
1097
+
1098
+ window.addEventListener("loadConfig", () => {
1099
+ batchSizeInput.value = config.batchSize;
1100
+ batchSizeInput.dispatchEvent(new Event("input"));
1101
+
1102
+ epochsInput.value = config.epochs;
1103
+ epochsInput.dispatchEvent(new Event("input"));
1104
+ });
1105
+
1106
+ let isTraining = false;
1107
+
1108
+ /** @type {HTMLButtonElement} */
1109
+ const trainButton = document.getElementById("train");
1110
+ trainButton.addEventListener("click", () => {
1111
+ train();
1112
+ });
1113
+
1114
+ let didNormalizeData = false;
1115
+ function train() {
1116
+ if (!didNormalizeData) {
1117
+ //neuralNetwork.normalizeData(); // pre-normalize data
1118
+ didNormalizeData = true;
1119
+ }
1120
+
1121
+ isTraining = true;
1122
+ window.dispatchEvent(new CustomEvent("training", { detail: { isTraining } }));
1123
+
1124
+ setTimeout(() => {
1125
+ neuralNetwork.train(
1126
+ {
1127
+ epochs,
1128
+ batchSize,
1129
+ },
1130
+ () => {
1131
+ isTraining = false;
1132
+ window.dispatchEvent(new CustomEvent("finishedTraining"));
1133
+ }
1134
+ );
1135
+ }, 0);
1136
+ }
1137
+
1138
+ window.addEventListener(
1139
+ "addData",
1140
+ () => {
1141
+ trainButton.disabled = false;
1142
+ },
1143
+ { once: true }
1144
+ );
1145
+ window.addEventListener("loadData", () => {
1146
+ trainButton.disabled = false;
1147
+ });
1148
+
1149
+ window.addEventListener("training", () => {
1150
+ batchSizeInput.disabled = true;
1151
+ epochsInput.disabled = true;
1152
+
1153
+ trainButton.disabled = true;
1154
+ trainButton.innerText = "training...";
1155
+ });
1156
+
1157
+ window.addEventListener("finishedTraining", () => {
1158
+ batchSizeInput.disabled = false;
1159
+ epochsInput.disabled = false;
1160
+
1161
+ trainButton.disabled = false;
1162
+ trainButton.innerText = "train";
1163
+ });
1164
+
1165
+ // CLASSIFY & PREDICT
1166
+
1167
+ let testContinuously = false;
1168
+ /** @type {HTMLInputElement} */
1169
+ const toggleTestContinuouslyInput = document.getElementById("toggleTestContinuously");
1170
+ toggleTestContinuouslyInput.addEventListener("input", () => {
1171
+ testContinuously = toggleTestContinuouslyInput.checked;
1172
+ console.log({ testContinuously });
1173
+ });
1174
+ window.addEventListener("training", () => {
1175
+ toggleAddDataContinuouslyInput.disabled = true;
1176
+ });
1177
+ window.addEventListener("finishedTraining", () => {
1178
+ toggleAddDataContinuouslyInput.disabled = false;
1179
+ });
1180
+
1181
+ /** @type {HTMLButtonElement} */
1182
+ const testButton = document.getElementById("test");
1183
+ testButton.addEventListener("click", () => {
1184
+ test();
1185
+ });
1186
+ const updateTestButton = () => {
1187
+ const enabled = neuralNetwork?.neuralNetwork?.isTrained && isSensorDataEnabled && !isTesting;
1188
+ testButton.disabled = !enabled;
1189
+ };
1190
+ ["training", "loadModel", "finishedTraining", "isSensorDataEnabled"].forEach((eventType) => {
1191
+ window.addEventListener(eventType, () => {
1192
+ updateTestButton();
1193
+ });
1194
+ });
1195
+
1196
+ /** @type {HTMLPreElement} */
1197
+ const resultsPreElement = document.getElementById("results");
1198
+
1199
+ /**
1200
+ * @typedef Result
1201
+ * @type {Object}
1202
+ * @property {string} label
1203
+ * @property {number} confidence
1204
+ */
1205
+
1206
+ /**
1207
+ * @param {string} error
1208
+ * @param {Result[]} results
1209
+ */
1210
+ const handleResults = (error, results) => {
1211
+ isTesting = false;
1212
+
1213
+ testButton.disabled = false;
1214
+ testButton.innerText = "test";
1215
+
1216
+ if (error) {
1217
+ console.error(error);
1218
+ return;
1219
+ }
1220
+
1221
+ console.log({ results });
1222
+ resultsPreElement.textContent = JSON.stringify(results, null, 2);
1223
+
1224
+ if (testContinuously) {
1225
+ test();
1226
+ }
1227
+ };
1228
+
1229
+ let isTesting = false;
1230
+ async function test(allowOverlapping = true) {
1231
+ if (isTesting && !allowOverlapping) {
1232
+ return;
1233
+ }
1234
+
1235
+ testButton.disabled = true;
1236
+ testButton.innerText = "testing...";
1237
+
1238
+ const data = await collectData();
1239
+ console.log({ data });
1240
+ if (task == "classification") {
1241
+ neuralNetwork.classify(data, handleResults);
1242
+ } else {
1243
+ neuralNetwork.predict(data, handleResults);
1244
+ }
1245
+ }
1246
+
1247
+ // SAVE
1248
+
1249
+ /** @type {HTMLButtonElement} */
1250
+ const saveDataButton = document.getElementById("saveData");
1251
+ saveDataButton.addEventListener("click", () => {
1252
+ console.log("saveData");
1253
+ neuralNetwork.saveData();
1254
+ });
1255
+ window.addEventListener("finishedTraining", () => {
1256
+ saveDataButton.disabled = false;
1257
+ });
1258
+
1259
+ /** @type {HTMLButtonElement} */
1260
+ const saveModelButton = document.getElementById("saveModel");
1261
+ saveModelButton.addEventListener("click", () => {
1262
+ console.log("saveModel");
1263
+ neuralNetwork.save();
1264
+ });
1265
+ window.addEventListener("finishedTraining", () => {
1266
+ saveModelButton.disabled = false;
1267
+ });
1268
+
1269
+ // LOAD
1270
+
1271
+ /** @type {HTMLInputElement} */
1272
+ const loadDataInput = document.getElementById("loadData");
1273
+ loadDataInput.addEventListener("input", () => {
1274
+ neuralNetwork.loadData(loadDataInput.files, () => {
1275
+ console.log("loaded data");
1276
+ loadDataInput.value = "";
1277
+ window.dispatchEvent(new CustomEvent("loadData"));
1278
+ });
1279
+ });
1280
+ window.addEventListener("createNeuralNetwork", () => {
1281
+ loadDataInput.disabled = false;
1282
+ });
1283
+
1284
+ /** @type {HTMLInputElement} */
1285
+ const loadModelInput = document.getElementById("loadModel");
1286
+ loadModelInput.addEventListener("input", () => {
1287
+ neuralNetwork.load(loadModelInput.files, () => {
1288
+ console.log("loaded model");
1289
+ loadModelInput.value = "";
1290
+ window.dispatchEvent(new CustomEvent("loadModel"));
1291
+ });
1292
+ });
1293
+ window.addEventListener("createNeuralNetwork", () => {
1294
+ loadModelInput.disabled = false;
1295
+ });
1296
+
1297
+ // TENSORFLOW LITE
1298
+
1299
+ /** @param {()=>{}} callback */
1300
+ function getModel(callback) {
1301
+ if (selectedDevices.length == 1 && neuralNetwork?.neuralNetwork.isTrained) {
1302
+ neuralNetwork.neuralNetwork.model.save(
1303
+ ml5.tf.io.withSaveHandler(async (data) => {
1304
+ const weightsManifest = {
1305
+ modelTopology: data.modelTopology,
1306
+ weightsManifest: [
1307
+ {
1308
+ paths: [`./model.weights.bin`],
1309
+ weights: data.weightSpecs,
1310
+ },
1311
+ ],
1312
+ };
1313
+ callback(data.weightData, weightsManifest);
1314
+ })
1315
+ );
1316
+ }
1317
+ }
1318
+
1319
+ const { seedrandom } = Math;
1320
+ const SEED = "brilliantsole";
1321
+
1322
+ /**
1323
+ * @param {number[]} inputs
1324
+ * @param {number[]} outputs
1325
+ */
1326
+ function shuffleData(inputs, outputs) {
1327
+ const rand = new seedrandom(SEED);
1328
+
1329
+ const indexes = inputs.map((_, i) => i);
1330
+ indexes.sort(() => rand() - 0.5);
1331
+
1332
+ const shuffledInputs = [];
1333
+ const shuffledOutputs = [];
1334
+
1335
+ indexes.forEach((i, j) => {
1336
+ shuffledInputs[j] = inputs[i];
1337
+ shuffledOutputs[j] = outputs[i];
1338
+ });
1339
+
1340
+ return [shuffledInputs, shuffledOutputs];
1341
+ }
1342
+
1343
+ function splitArray(data, fract) {
1344
+ const splitPoint = Math.round(data.length * fract);
1345
+ const a = data.slice(0, splitPoint);
1346
+ const b = data.slice(splitPoint, data.length);
1347
+ return [a, b];
1348
+ }
1349
+
1350
+ function shuffleAndSplitDataSet([X, Y], splitRatio = 0.8) {
1351
+ const [shuffled_X, shuffled_Y] = shuffleData(X, Y);
1352
+ const [train_X, test_X] = splitArray(shuffled_X, splitRatio);
1353
+ const [train_Y, test_Y] = splitArray(shuffled_Y, splitRatio);
1354
+
1355
+ return [train_X, train_Y, test_X, test_Y];
1356
+ }
1357
+
1358
+ function rescale(minIn, maxIn, minOut, maxOut, [X, Y]) {
1359
+ const rescaledX = [];
1360
+ const a = minOut - minIn;
1361
+ const scaleRatio = (maxOut - minOut) / (maxIn - minIn);
1362
+
1363
+ X.forEach((row) => {
1364
+ rescaledX.push(row.map((v) => (v + a) * scaleRatio));
1365
+ });
1366
+ return [rescaledX, Y];
1367
+ }
1368
+
1369
+ function prepareDataSet() {
1370
+ const inputs = [];
1371
+ const outputs = [];
1372
+
1373
+ neuralNetwork.data.training.forEach(({ xs, ys }, index) => {
1374
+ const input = [];
1375
+ for (const key in xs) {
1376
+ input.push(xs[key]);
1377
+ }
1378
+ inputs.push(input);
1379
+
1380
+ const output = [];
1381
+ for (const key in ys) {
1382
+ const value = ys[key];
1383
+ if (value instanceof Array) {
1384
+ output.push(...ys[key]);
1385
+ } else {
1386
+ output.push(ys[key]);
1387
+ }
1388
+ }
1389
+ outputs.push(output);
1390
+ });
1391
+
1392
+ console.log({ inputs, outputs });
1393
+
1394
+ return [inputs, outputs];
1395
+ }
1396
+
1397
+ function downloadBlob(blob, fileName) {
1398
+ const a = document.createElement("a");
1399
+ document.body.appendChild(a);
1400
+ a.style = "display: none";
1401
+ const url = window.URL.createObjectURL(blob);
1402
+ a.href = url;
1403
+ a.download = fileName;
1404
+ a.click();
1405
+ window.URL.revokeObjectURL(url);
1406
+ }
1407
+
1408
+ const defaultPythonServerUrl = `${location.origin}:8080`;
1409
+ /** @type {string} */
1410
+ let pythonServerUrl = defaultPythonServerUrl;
1411
+
1412
+ /** @type {HTMLInputElement} */
1413
+ const pythonServerUrlInput = document.getElementById("pythonServerUrl");
1414
+ pythonServerUrlInput.addEventListener("input", () => {
1415
+ pythonServerUrl = pythonServerUrlInput.value || defaultPythonServerUrl;
1416
+ console.log({ pythonServerUrl });
1417
+ window.dispatchEvent(new CustomEvent("pythonServerUrl", { detail: { pythonServerUrl } }));
1418
+ });
1419
+
1420
+ window.addEventListener("loadConfig", () => {
1421
+ pythonServerUrlInput.value = config.pythonServerUrl;
1422
+ pythonServerUrlInput.dispatchEvent(new Event("input"));
1423
+ });
1424
+
1425
+ let quantizeModel = false;
1426
+ /** @type {HTMLInputElement} */
1427
+ const toggleQuantizeModelInput = document.getElementById("toggleQuantizeModel");
1428
+ toggleQuantizeModelInput.addEventListener("input", () => {
1429
+ quantizeModel = toggleQuantizeModelInput.checked;
1430
+ console.log({ quantizeModel });
1431
+ });
1432
+
1433
+ let trainTestSplit = 0.2;
1434
+ let isConvertingModel = false;
1435
+ const tfLiteFiles = {
1436
+ /** @type {File?} */
1437
+ tfLite_model_cpp: null,
1438
+ /** @type {File?} */
1439
+ model_tflite: null,
1440
+ };
1441
+
1442
+ /** @type {HTMLButtonElement} */
1443
+ const convertModelToTfliteButton = document.getElementById("convertModelToTflite");
1444
+ convertModelToTfliteButton.addEventListener("click", () => {
1445
+ convertModelToTfliteButton.disabled = true;
1446
+ convertModelToTfliteButton.innerText = "converting model to tflite...";
1447
+ convertModelToTflite();
1448
+ });
1449
+ window.addEventListener("convertModelToTflite", () => {
1450
+ convertModelToTfliteButton.disabled = false;
1451
+ convertModelToTfliteButton.innerText = "convert model to tflite";
1452
+ });
1453
+
1454
+ async function convertModelToTflite() {
1455
+ if (isConvertingModel) {
1456
+ return;
1457
+ }
1458
+ isConvertingModel = true;
1459
+
1460
+ neuralNetwork.neuralNetwork.model
1461
+ .save(
1462
+ ml5.tf.io.browserHTTPRequest(`${pythonServerUrl}/convert?${quantizeModel ? "quantize=true" : ""}`, {
1463
+ fetchFunc: (url, req) => {
1464
+ if (quantizeModel) {
1465
+ const [, , test_x] = shuffleAndSplitDataSet(prepareDataSet(), 1 - trainTestSplit);
1466
+ console.log({ test_x });
1467
+ req.body.append("quantize_data", JSON.stringify(test_x));
1468
+ }
1469
+
1470
+ console.log({ url, req });
1471
+
1472
+ return fetch(url, req);
1473
+ },
1474
+ })
1475
+ )
1476
+ .then((result) => {
1477
+ console.log({ result });
1478
+ const res = result.responses[0];
1479
+ res
1480
+ .arrayBuffer() // Download gzipped tar file and get ArrayBuffer
1481
+ .then(pako.inflate) // Decompress gzip using pako
1482
+ .then((arr) => arr.buffer) // Get ArrayBuffer from the Uint8Array pako returns
1483
+ .then(untar) // Untar
1484
+ .then((files) => {
1485
+ // js-untar returns a list of files (See https://github.com/InvokIT/js-untar#file-object for details)
1486
+ console.log("received files", files);
1487
+ const tfLite_model_cpp = files.find((file) => file.name === "tfLite_model.cpp");
1488
+ const model_tflite = files.find((file) => file.name === "model.tflite");
1489
+ console.log({ tfLite_model_cpp, model_tflite });
1490
+ isConvertingModel = false;
1491
+ Object.assign(tfLiteFiles, { tfLite_model_cpp, model_tflite });
1492
+ window.dispatchEvent(new CustomEvent("convertModelToTflite", { detail: { tfLiteFiles, files } }));
1493
+ window.dispatchEvent(new CustomEvent("tflite", { detail: { tfliteModel: model_tflite } }));
1494
+ });
1495
+ })
1496
+ .catch((error) => {
1497
+ console.log(error);
1498
+ isConvertingModel = false;
1499
+
1500
+ convertModelToTfliteButton.disabled = false;
1501
+ convertModelToTfliteButton.innerText = "convert model to tflite";
1502
+ });
1503
+ }
1504
+
1505
+ /** @type {HTMLButtonElement} */
1506
+ const downloadTfliteButton = document.getElementById("downloadTflite");
1507
+ downloadTfliteButton.addEventListener("click", () => {
1508
+ downloadBlob(tfLiteFiles.model_tflite.blob, "model.tflite");
1509
+ downloadBlob(tfLiteFiles.tfLite_model_cpp.blob, "tflite_model.cpp");
1510
+ });
1511
+ window.addEventListener("convertModelToTflite", () => {
1512
+ downloadTfliteButton.disabled = false;
1513
+ });
1514
+
1515
+ /** @type {HTMLInputElement} */
1516
+ const loadTfliteInput = document.getElementById("loadTflite");
1517
+ loadTfliteInput.addEventListener("input", () => {
1518
+ const tfliteModel = loadTfliteInput.files[0];
1519
+ if (tfliteModel) {
1520
+ console.log({ tfliteModel });
1521
+ tfLiteFiles.model_tflite = tfliteModel;
1522
+ window.dispatchEvent(new CustomEvent("tflite", { detail: { tfliteModel } }));
1523
+ }
1524
+ });
1525
+
1526
+ /** @type {HTMLButtonElement} */
1527
+ const transferTfliteButton = document.getElementById("transferTflite");
1528
+ transferTfliteButton.addEventListener("click", async () => {
1529
+ const device = selectedDevices[0];
1530
+ if (device.fileTransferStatus == "idle") {
1531
+ await device.setTfliteSampleRate(samplingRate);
1532
+ await device.setTfliteTask(task);
1533
+ await device.setTfliteSensorTypes(sensorTypes);
1534
+ device.sendFile("tflite", tfLiteFiles.model_tflite);
1535
+ } else {
1536
+ device.cancelFileTransfer();
1537
+ }
1538
+ });
1539
+
1540
+ ["createNeuralNetwork", "tflite"].forEach((eventType) => {
1541
+ window.addEventListener(eventType, () => {
1542
+ updateTransferTfliteButton();
1543
+ });
1544
+ });
1545
+ const updateTransferTfliteButton = () => {
1546
+ const enabled = tfLiteFiles.model_tflite && neuralNetwork && selectedDevices.length == 1;
1547
+ transferTfliteButton.disabled = !enabled;
1548
+
1549
+ if (!enabled) {
1550
+ return;
1551
+ }
1552
+
1553
+ /** @type {String} */
1554
+ let innerText;
1555
+ switch (selectedDevices[0].fileTransferStatus) {
1556
+ case "idle":
1557
+ innerText = "transfer file";
1558
+ break;
1559
+ case "sending":
1560
+ innerText = "stop transferring file";
1561
+ break;
1562
+ }
1563
+ transferTfliteButton.innerText = innerText;
1564
+ };
1565
+
1566
+ /** @type {HTMLProgressElement} */
1567
+ const transferTfliteProgress = document.getElementById("transferTfliteProgress");
1568
+ window.addEventListener(
1569
+ "createNeuralNetwork",
1570
+ () => {
1571
+ if (selectedDevices.length == 1) {
1572
+ const device = selectedDevices[0];
1573
+ device.addEventListener("fileTransferStatus", () => {
1574
+ transferTfliteButton.innerText = device.fileTransferStatus == "idle" ? "send file" : "stop sending file";
1575
+
1576
+ switch (device.fileTransferStatus) {
1577
+ case "idle":
1578
+ transferTfliteButton.innerText = "send file";
1579
+ transferTfliteProgress.value = 0;
1580
+ break;
1581
+ case "sending":
1582
+ transferTfliteButton.innerText = "stop sending file";
1583
+ break;
1584
+ }
1585
+ });
1586
+ device.addEventListener("fileTransferProgress", (event) => {
1587
+ transferTfliteProgress.value = event.message.progress;
1588
+ });
1589
+ }
1590
+ },
1591
+ { once: true }
1592
+ );
1593
+
1594
+ /** @type {HTMLButtonElement} */
1595
+ const toggleTfliteInferencingEnabledButton = document.getElementById("toggleTfliteInferencingEnabled");
1596
+ toggleTfliteInferencingEnabledButton.addEventListener("click", async () => {
1597
+ await selectedDevices[0].setTfliteCaptureDelay(1000);
1598
+ await selectedDevices[0].toggleTfliteInferencing();
1599
+ toggleTfliteInferencingEnabledButton.disabled = true;
1600
+ });
1601
+
1602
+ /** @type {HTMLInputElement} */
1603
+ const setTfliteIsReadyInput = document.getElementById("tfliteIsReady");
1604
+
1605
+ /** @type {HTMLPreElement} */
1606
+ const tfliteInferencePre = document.getElementById("tfliteInference");
1607
+
1608
+ /** @type {HTMLElement} */
1609
+ const tfliteInferenceClassContainer = document.getElementById("tfliteInferenceClass");
1610
+
1611
+ window.addEventListener(
1612
+ "createNeuralNetwork",
1613
+ () => {
1614
+ if (selectedDevices.length == 1) {
1615
+ const device = selectedDevices[0];
1616
+
1617
+ device.addEventListener("tfliteIsReady", (e) => {
1618
+ setTfliteIsReadyInput.checked = device.tfliteIsReady;
1619
+ });
1620
+
1621
+ device.addEventListener("tfliteIsReady", () => {
1622
+ toggleTfliteInferencingEnabledButton.disabled = !device.tfliteIsReady;
1623
+ });
1624
+ device.addEventListener("getTfliteInferencingEnabled", () => {
1625
+ toggleTfliteInferencingEnabledButton.innerText = device.tfliteInferencingEnabled
1626
+ ? "disable inferencing"
1627
+ : "enable inferencing";
1628
+ toggleTfliteInferencingEnabledButton.disabled = false;
1629
+ });
1630
+
1631
+ device.addEventListener("tfliteInference", (event) => {
1632
+ tfliteInferencePre.textContent = JSON.stringify(event.message.tfliteInference, null, 2);
1633
+ let highestClassValue = 0;
1634
+ let highestClassIndex = 0;
1635
+ event.message.tfliteInference.values.forEach((classValue, classIndex) => {
1636
+ if (classValue > highestClassValue) {
1637
+ highestClassValue = classValue;
1638
+ highestClassIndex = classIndex;
1639
+ }
1640
+ });
1641
+ if (highestClassIndex == 0) {
1642
+ tfliteInferenceClassContainer.innerText = "";
1643
+ } else {
1644
+ tfliteInferenceClassContainer.innerText = outputLabels[highestClassIndex];
1645
+ }
1646
+ });
1647
+ }
1648
+ },
1649
+ { once: true }
1650
+ );
1651
+
1652
+ ["finishedTraining", "loadModel"].forEach((eventType) => {
1653
+ window.addEventListener(eventType, () => {
1654
+ if (selectedDevices.length != 1) {
1655
+ return;
1656
+ }
1657
+ convertModelToTfliteButton.disabled = false;
1658
+ });
1659
+ });
1660
+
1661
+ // CONFIG
1662
+
1663
+ const configLocalStorageKey = "BS.MachineLearning.Config";
1664
+
1665
+ const config = {
1666
+ task,
1667
+
1668
+ sensorTypes,
1669
+
1670
+ numberOfOutputs,
1671
+ outputLabels,
1672
+
1673
+ numberOfSamples,
1674
+ samplingRate,
1675
+
1676
+ learningRate,
1677
+ hiddenUnits,
1678
+
1679
+ thresholdsEnabled,
1680
+ captureDelay,
1681
+ thresholds: {},
1682
+
1683
+ epochs,
1684
+ batchSize,
1685
+
1686
+ pythonServerUrl,
1687
+ };
1688
+ window.config = config;
1689
+
1690
+ function loadConfigFromLocalStorage() {
1691
+ const configString = localStorage.getItem(configLocalStorageKey);
1692
+ if (!configString) {
1693
+ return;
1694
+ }
1695
+ const loadedConfig = JSON.parse(configString);
1696
+ console.log("loaded config", loadedConfig);
1697
+ Object.assign(config, loadedConfig);
1698
+ window.dispatchEvent(new CustomEvent("loadConfig", { detail: { config } }));
1699
+ }
1700
+ loadConfigFromLocalStorage();
1701
+
1702
+ Object.keys(config).forEach((type) => {
1703
+ let eventType = type;
1704
+
1705
+ switch (type) {
1706
+ case "thresholds":
1707
+ eventType = "threshold";
1708
+ break;
1709
+ }
1710
+
1711
+ window.addEventListener(eventType, (event) => {
1712
+ switch (type) {
1713
+ case "task":
1714
+ config.task = task;
1715
+ break;
1716
+
1717
+ case "sensorTypes":
1718
+ config.sensorTypes = sensorTypes;
1719
+ break;
1720
+
1721
+ case "numberOfOutputs":
1722
+ config.numberOfOutputs = numberOfOutputs;
1723
+ break;
1724
+ case "outputLabels":
1725
+ config.outputLabels = outputLabels;
1726
+ break;
1727
+
1728
+ case "numberOfSamples":
1729
+ config.numberOfSamples = numberOfSamples;
1730
+ break;
1731
+ case "samplingRate":
1732
+ config.samplingRate = samplingRate;
1733
+ break;
1734
+
1735
+ case "learningRate":
1736
+ config.learningRate = learningRate;
1737
+ break;
1738
+ case "hiddenUnits":
1739
+ config.hiddenUnits = hiddenUnits;
1740
+ break;
1741
+
1742
+ case "thresholdsEnabled":
1743
+ config.thresholdsEnabled = thresholdsEnabled;
1744
+ break;
1745
+ case "captureDelay":
1746
+ config.captureDelay = captureDelay;
1747
+ break;
1748
+ case "thresholds":
1749
+ {
1750
+ const { sensorType, threshold, enabled } = event.detail;
1751
+ config.thresholds[sensorType] = { threshold, enabled };
1752
+ }
1753
+ break;
1754
+
1755
+ case "batchSize":
1756
+ config.batchSize = batchSize;
1757
+ break;
1758
+ case "epochs":
1759
+ config.epochs = epochs;
1760
+ break;
1761
+
1762
+ case "pythonServerUrl":
1763
+ config.pythonServerUrl = pythonServerUrl;
1764
+ break;
1765
+ }
1766
+
1767
+ saveConfigToLocalStorage();
1768
+ });
1769
+ });
1770
+
1771
+ function saveConfigToLocalStorage() {
1772
+ console.log("saving config", config);
1773
+ localStorage.setItem(configLocalStorageKey, JSON.stringify(config));
1774
+ }