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,736 @@
1
+ import * as BS from "../../build/brilliantsole.module.js";
2
+ window.BS = BS;
3
+ console.log({ BS });
4
+ //BS.setAllConsoleLevelFlags({ log: false });
5
+
6
+ // GET DEVICES
7
+
8
+ /** @type {HTMLTemplateElement} */
9
+ const availableDeviceTemplate = document.getElementById("availableDeviceTemplate");
10
+ const availableDevicesContainer = document.getElementById("availableDevices");
11
+ /** @param {BS.Device[]} availableDevices */
12
+ function onAvailableDevices(availableDevices) {
13
+ availableDevicesContainer.innerHTML = "";
14
+ if (availableDevices.length == 0) {
15
+ availableDevicesContainer.innerText = "no devices available";
16
+ } else {
17
+ availableDevices.forEach((availableDevice) => {
18
+ let availableDeviceContainer = availableDeviceTemplate.content.cloneNode(true).querySelector(".availableDevice");
19
+ availableDeviceContainer.querySelector(".name").innerText = availableDevice.name;
20
+ availableDeviceContainer.querySelector(".type").innerText = availableDevice.type;
21
+
22
+ /** @type {HTMLButtonElement} */
23
+ const toggleConnectionButton = availableDeviceContainer.querySelector(".toggleConnection");
24
+ toggleConnectionButton.addEventListener("click", () => {
25
+ availableDevice.toggleConnection();
26
+ });
27
+ const onConnectionStatusUpdate = () => {
28
+ switch (availableDevice.connectionStatus) {
29
+ case "connected":
30
+ case "notConnected":
31
+ toggleConnectionButton.disabled = false;
32
+ toggleConnectionButton.innerText = availableDevice.isConnected ? "disconnect" : "connect";
33
+ break;
34
+ case "connecting":
35
+ case "disconnecting":
36
+ toggleConnectionButton.disabled = true;
37
+ toggleConnectionButton.innerText = availableDevice.connectionStatus;
38
+ break;
39
+ }
40
+ };
41
+ availableDevice.addEventListener("connectionStatus", () => onConnectionStatusUpdate());
42
+ onConnectionStatusUpdate();
43
+ availableDevicesContainer.appendChild(availableDeviceContainer);
44
+ });
45
+ }
46
+ }
47
+ async function getDevices() {
48
+ const availableDevices = await BS.DeviceManager.GetDevices();
49
+ if (!availableDevices) {
50
+ return;
51
+ }
52
+ onAvailableDevices(availableDevices);
53
+ }
54
+
55
+ BS.DeviceManager.AddEventListener("availableDevices", (event) => {
56
+ const devices = event.message.availableDevices;
57
+ onAvailableDevices(devices);
58
+ });
59
+ getDevices();
60
+
61
+ // ADD DEVICE
62
+
63
+ /** @type {HTMLButtonElement} */
64
+ const addDeviceButton = document.getElementById("addDevice");
65
+ addDeviceButton.addEventListener("click", () => {
66
+ const device = new BS.Device();
67
+ device.connect();
68
+ });
69
+ window.addEventListener("isSensorDataEnabled", () => {
70
+ addDeviceButton.disabled = isSensorDataEnabled;
71
+ });
72
+
73
+ // CONNECTED DEVICES
74
+
75
+ const connectedDevicesContainer = document.getElementById("connectedDevices");
76
+ /** @type {HTMLTemplateElement} */
77
+ const connectedDeviceTemplate = document.getElementById("connectedDeviceTemplate");
78
+
79
+ BS.DeviceManager.AddEventListener("deviceConnected", (event) => {
80
+ /** @type {BS.Device} */
81
+ const device = event.message.device;
82
+ console.log("deviceConnected", device);
83
+ const connectedDeviceContainer = connectedDeviceTemplate.content.cloneNode(true).querySelector(".connectedDevice");
84
+ connectedDeviceContainer.querySelector(".name").innerText = device.name;
85
+ connectedDeviceContainer.querySelector(".type").innerText = device.type;
86
+
87
+ /** @type {HTMLButtonElement} */
88
+ const disconnectButton = connectedDeviceContainer.querySelector(".disconnect");
89
+ disconnectButton.addEventListener("click", () => {
90
+ disconnectButton.innerText = "disconnecting...";
91
+ disconnectButton.disabled = true;
92
+ device.disconnect();
93
+ });
94
+ device.addEventListener("notConnected", () => {
95
+ connectedDeviceContainer.remove();
96
+ });
97
+
98
+ window.addEventListener("isSensorDataEnabled", () => {
99
+ disconnectButton.disabled = isSensorDataEnabled;
100
+ });
101
+
102
+ /** @type {HTMLPreElement} */
103
+ const sensorConfigurationPre = connectedDeviceContainer.querySelector("pre.sensorConfiguration");
104
+ const updateSensorConfigurationPre = () => {
105
+ sensorConfigurationPre.textContent = JSON.stringify(device.sensorConfiguration, null, 2);
106
+ };
107
+ device.addEventListener("getSensorConfiguration", () => {
108
+ updateSensorConfigurationPre();
109
+ });
110
+ updateSensorConfigurationPre();
111
+
112
+ device.addEventListener("sensorData", (event) => {
113
+ const { sensorType, timestamp } = event.message;
114
+
115
+ const { [sensorType]: data } = event.message;
116
+ //console.log({ name: device.name, sensorType, timestamp, data });
117
+ if (isRecording && currentRecording) {
118
+ let deviceRecording = currentRecording.devices.find((_deviceRecording) => _deviceRecording.id == device.id);
119
+ if (!deviceRecording) {
120
+ deviceRecording = { id: device.id, type: device.type, name: device.name, sensorData: [] };
121
+ currentRecording.devices.push(deviceRecording);
122
+ }
123
+ let sensorTypeData = deviceRecording.sensorData.find(
124
+ (_sensorTypeData) => _sensorTypeData.sensorType == sensorType
125
+ );
126
+ if (!sensorTypeData) {
127
+ sensorTypeData = {
128
+ sensorType,
129
+ initialTimestamp: Date.now(),
130
+ data: [],
131
+ dataRate: device.sensorConfiguration[sensorType],
132
+ };
133
+ deviceRecording.sensorData.push(sensorTypeData);
134
+ }
135
+
136
+ if (sensorType == "pressure") {
137
+ /** @type {BS.PressureData} */
138
+ const pressure = data;
139
+ const pressureSensorData = pressure.sensors.map((sensor) => {
140
+ const { name, normalizedValue } = sensor;
141
+ return normalizedValue;
142
+ });
143
+ sensorTypeData.data.push(pressureSensorData);
144
+ } else {
145
+ sensorTypeData.data.push(data);
146
+ }
147
+ }
148
+ });
149
+
150
+ connectedDevicesContainer.appendChild(connectedDeviceContainer);
151
+ });
152
+
153
+ // SENSOR CONFIGURATION
154
+
155
+ /** @type {BS.SensorConfiguration} */
156
+ const sensorConfiguration = {};
157
+ const sensorConfigurationContainer = document.getElementById("sensorConfiguration");
158
+ /** @type {HTMLTemplateElement} */
159
+ const sensorTypeConfigurationTemplate = document.getElementById("sensorTypeConfigurationTemplate");
160
+ /** @type {Object.<string, HTMLElement>} */
161
+ const sensorTypeConfigurationContainers = {};
162
+ BS.ContinuousSensorTypes.forEach((sensorType) => {
163
+ sensorConfiguration[sensorType] = 0;
164
+
165
+ const sensorTypeConfigurationContainer = sensorTypeConfigurationTemplate.content
166
+ .cloneNode(true)
167
+ .querySelector(".sensorTypeConfiguration");
168
+ sensorTypeConfigurationContainer.querySelector(".sensorType").innerText = sensorType;
169
+
170
+ /** @type {HTMLInputElement} */
171
+ const sensorRateInput = sensorTypeConfigurationContainer.querySelector(".sensorRate");
172
+ sensorRateInput.value = 0;
173
+ sensorRateInput.max = BS.MaxSensorRate;
174
+ sensorRateInput.step = BS.SensorRateStep;
175
+ sensorRateInput.addEventListener("input", () => {
176
+ sensorConfiguration[sensorType] = Number(sensorRateInput.value);
177
+ console.log({ sensorConfiguration });
178
+ window.dispatchEvent(new CustomEvent("sensorConfiguration", { detail: { sensorConfiguration } }));
179
+ });
180
+
181
+ window.addEventListener("isSensorDataEnabled", () => {
182
+ sensorRateInput.disabled = isSensorDataEnabled;
183
+ });
184
+
185
+ sensorTypeConfigurationContainers[sensorType] = sensorTypeConfigurationContainer;
186
+
187
+ sensorConfigurationContainer.appendChild(sensorTypeConfigurationContainer);
188
+ });
189
+
190
+ let isSensorDataEnabled = false;
191
+ /** @param {boolean} newIsSensorDataEnabled */
192
+ function setIsSensorDataEnabled(newIsSensorDataEnabled) {
193
+ if (newIsSensorDataEnabled == isSensorDataEnabled) {
194
+ console.log("redundant isSensorDataEnabled assignment");
195
+ return;
196
+ }
197
+ isSensorDataEnabled = newIsSensorDataEnabled;
198
+
199
+ BS.DeviceManager.ConnectedDevices.forEach((device) => {
200
+ if (isSensorDataEnabled) {
201
+ console.log(device, sensorConfiguration);
202
+ device.setSensorConfiguration(sensorConfiguration);
203
+ } else {
204
+ console.log("clear", device);
205
+ device.clearSensorConfiguration();
206
+ }
207
+ });
208
+
209
+ window.dispatchEvent(new CustomEvent("isSensorDataEnabled", { detail: isSensorDataEnabled }));
210
+ }
211
+ /** @type {HTMLInputElement} */
212
+ const toggleSensorDataCheckbox = document.getElementById("toggleSensorData");
213
+ toggleSensorDataCheckbox.addEventListener("input", () => {
214
+ setIsSensorDataEnabled(toggleSensorDataCheckbox.checked);
215
+ });
216
+ function updateToggleSensorDataCheckbox() {
217
+ const isSensorConfigurationZero = Object.values(sensorConfiguration).every((sensorRate) => sensorRate == 0);
218
+ toggleSensorDataCheckbox.disabled = isSensorConfigurationZero || BS.DeviceManager.ConnectedDevices.length == 0;
219
+ }
220
+ window.addEventListener("sensorConfiguration", (event) => {
221
+ updateToggleSensorDataCheckbox();
222
+ });
223
+
224
+ BS.DeviceManager.AddEventListener("deviceIsConnected", () => {
225
+ updateToggleSensorDataCheckbox();
226
+ });
227
+
228
+ // RECORDING SETTINGS
229
+
230
+ let recordingCountdown = 0;
231
+ /** @type {HTMLInputElement} */
232
+ const recordingCountdownInput = document.getElementById("recordingCountdownInput");
233
+ recordingCountdownInput.addEventListener("input", () => {
234
+ recordingCountdown = Number(recordingCountdownInput.value);
235
+ console.log({ recordingCountdown });
236
+ });
237
+
238
+ let isRecordingFixedDuration = false;
239
+
240
+ /** @type {HTMLInputElement} */
241
+ const isRecordingFixedDurationCheckbox = document.getElementById("isRecordingFixedDuration");
242
+ isRecordingFixedDurationCheckbox.addEventListener("input", () => {
243
+ isRecordingFixedDuration = !isRecordingFixedDuration;
244
+ console.log({ isRecordingFixedDuration });
245
+ recordingDurationInput.disabled = !isRecordingFixedDuration;
246
+ });
247
+
248
+ /** (in seconds) */
249
+ let recordingDuration = 0;
250
+ /** @type {HTMLInputElement} */
251
+ const recordingDurationInput = document.getElementById("recordingDuration");
252
+ recordingDurationInput.addEventListener("input", () => {
253
+ recordingDuration = Number(recordingDurationInput.value);
254
+ console.log({ recordingDuration });
255
+ });
256
+ recordingDurationInput.dispatchEvent(new Event("input"));
257
+
258
+ // RECORDING
259
+
260
+ let isRecording = false;
261
+
262
+ /**
263
+ * @typedef DevicesSensorData
264
+ * @type {Object}
265
+ * @property {number} timestamp
266
+ * @property {number} finalTimestamp
267
+ * @property {DeviceSensorData[]} devices
268
+ */
269
+
270
+ /**
271
+ * @typedef DeviceSensorData
272
+ * @type {Object}
273
+ * @property {string} id
274
+ * @property {string} name
275
+ * @property {BS.DeviceType} type
276
+ * @property {SensorTypeData[]} sensorData
277
+ */
278
+
279
+ /**
280
+ * @typedef SensorTypeData
281
+ * @type {Object}
282
+ * @property {BS.ContinuousSensorType} sensorType
283
+ * @property {number} initialTimestamp ms
284
+ * @property {number} dataRate ms
285
+ * @property {Object[]} data
286
+ */
287
+
288
+ /** @type {DevicesSensorData[]} */
289
+ let recordings = [];
290
+
291
+ /** @type {DevicesSensorData?} */
292
+ let currentRecording;
293
+
294
+ /** @type {HTMLButtonElement} */
295
+ const toggleRecordingButton = document.getElementById("toggleRecording");
296
+ toggleRecordingButton.addEventListener("click", () => {
297
+ toggleRecording();
298
+ });
299
+
300
+ const recordingCountdownTimer = {
301
+ /** @param {number} countdown seconds */
302
+ start(countdown) {
303
+ if (this.isRunning) {
304
+ this.stop();
305
+ }
306
+ this.countdown = countdown;
307
+ this.intervalId = setInterval(() => {
308
+ this.countdown--;
309
+ this.onCountdown?.(this.countdown);
310
+ console.log({ countdown: this.countdown });
311
+ if (this.countdown == 0) {
312
+ this.stop();
313
+ this.onEnd();
314
+ }
315
+ }, 1000);
316
+ console.log("starting countdown");
317
+ this.onStart?.(this.countdown);
318
+ this.onCountdown?.(this.countdown);
319
+ },
320
+ /** @type {number} */
321
+ countdown: null,
322
+ stop() {
323
+ if (this.isRunning) {
324
+ console.log("stopping countdown");
325
+ clearInterval(this.intervalId);
326
+ this.intervalId = null;
327
+ this.onStop?.();
328
+ }
329
+ },
330
+ /** @type {(countdown: number)} */
331
+ onCountdown: null,
332
+ /** @type {(countdown: number)} */
333
+ onStart: null,
334
+ /** @type {function} */
335
+ onStop: null,
336
+ /** @type {function} */
337
+ onEnd: null,
338
+ intervalId: null,
339
+ get isRunning() {
340
+ return this.intervalId != null;
341
+ },
342
+ };
343
+ recordingCountdownTimer.onCountdown = (countdown) => updateRecordingCountdown(countdown);
344
+ recordingCountdownTimer.onStop = () => updateRecordingCountdown(0);
345
+ recordingCountdownTimer.onEnd = () => startRecording();
346
+
347
+ async function toggleRecording() {
348
+ if (recordingCountdownTimer.isRunning) {
349
+ recordingCountdownTimer.stop();
350
+ toggleRecordingButton.innerText = "record";
351
+ return;
352
+ }
353
+
354
+ if (isRecording) {
355
+ stopRecording();
356
+ } else {
357
+ if (recordingCountdown > 0) {
358
+ recordingCountdownTimer.start(recordingCountdown);
359
+ toggleRecordingButton.innerText = "cancel countdown";
360
+ } else {
361
+ startRecording();
362
+ }
363
+ }
364
+ }
365
+ /** @type {number?} */
366
+ let recordingTimeoutId = null;
367
+ function startRecordingTimeout() {
368
+ clearRecordingTimeout();
369
+ if (recordingDuration <= 0) {
370
+ console.warn("recording duration must be greater than 0");
371
+ return;
372
+ }
373
+ recordingTimeoutId = setTimeout(() => stopRecording(), recordingDuration * 1_000);
374
+ }
375
+ function clearRecordingTimeout() {
376
+ if (recordingTimeoutId != null) {
377
+ clearTimeout(recordingTimeoutId);
378
+ recordingTimeoutId = null;
379
+ }
380
+ }
381
+ function startRecording() {
382
+ if (isRecording) {
383
+ console.log("already recording");
384
+ return;
385
+ }
386
+
387
+ currentRecording = { timestamp: Date.now(), devices: [] };
388
+ isRecording = true;
389
+
390
+ vibrate("strongClick100");
391
+
392
+ toggleRecordingButton.innerText = "stop recording";
393
+
394
+ if (isRecordingFixedDuration) {
395
+ startRecordingTimeout();
396
+ }
397
+ }
398
+ function stopRecording() {
399
+ if (!isRecording) {
400
+ console.log("already not recording");
401
+ return;
402
+ }
403
+
404
+ clearRecordingTimeout();
405
+
406
+ if (currentRecording) {
407
+ console.log({ currentRecording });
408
+ currentRecording.timestamp;
409
+ currentRecording.finalTimestamp = Date.now();
410
+ onRecording(currentRecording);
411
+ currentRecording = null;
412
+ }
413
+ isRecording = false;
414
+
415
+ vibrate("tripleClick100");
416
+
417
+ toggleRecordingButton.innerText = "record";
418
+ }
419
+
420
+ window.addEventListener("isSensorDataEnabled", () => {
421
+ toggleRecordingButton.disabled = !isSensorDataEnabled;
422
+ });
423
+
424
+ /** @type {HTMLSpanElement} */
425
+ const recordingCountdownSpan = document.getElementById("recordingCountdownSpan");
426
+
427
+ /** @param {number} recordingCountdown */
428
+ function updateRecordingCountdown(recordingCountdown) {
429
+ console.log({ recordingCountdown });
430
+ if (recordingCountdown == 0) {
431
+ recordingCountdownSpan.innerText = "";
432
+ } else {
433
+ recordingCountdownSpan.innerText = recordingCountdown;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * vibrates all connected insoles with a single waveformEffect
439
+ * @param {BS.VibrationWaveformEffect} effect
440
+ */
441
+ function vibrate(effect) {
442
+ BS.DeviceManager.ConnectedDevices.forEach((device) => {
443
+ device.triggerVibration([
444
+ {
445
+ type: "waveformEffect",
446
+ segments: [{ effect }],
447
+ },
448
+ ]);
449
+ });
450
+ }
451
+
452
+ /** @type {HTMLTemplateElement} */
453
+ const recordingTemplate = document.getElementById("recordingTemplate");
454
+ /** @type {HTMLTemplateElement} */
455
+ const deviceRecordingTemplate = document.getElementById("deviceRecordingTemplate");
456
+ /** @type {HTMLTemplateElement} */
457
+ const sensorTypeRecordingTemplate = document.getElementById("sensorTypeRecordingTemplate");
458
+ const recordingsContainer = document.getElementById("recordings");
459
+
460
+ /**
461
+ * @param {DevicesSensorData} recording
462
+ * @param {boolean} saveRecordings
463
+ */
464
+ function onRecording(recording, saveRecordings = true) {
465
+ recordings.push(recording);
466
+ console.log({ recordings });
467
+
468
+ const recordingContainer = recordingTemplate.content.cloneNode(true).querySelector(".recording");
469
+ const deviceRecordingsContainer = recordingContainer.querySelector(".devices");
470
+ const initialDate = new Date(recording.timestamp);
471
+ const duration = recording.finalTimestamp - recording.timestamp;
472
+ recordingContainer.querySelector(".timestamp").innerText = dateToString(initialDate);
473
+ recordingContainer.querySelector(".duration").innerText = (duration / 1000).toFixed(2);
474
+ recording.devices.forEach((deviceRecording) => {
475
+ const deviceRecordingContainer = deviceRecordingTemplate.content.cloneNode(true).querySelector(".deviceRecording");
476
+
477
+ deviceRecordingContainer.querySelector(".name").innerText = deviceRecording.name;
478
+ deviceRecordingContainer.querySelector(".id").innerText = deviceRecording.id;
479
+ deviceRecordingContainer.querySelector(".type").innerText = deviceRecording.type;
480
+ const sensorTypesContainer = deviceRecordingContainer.querySelector(".sensorTypes");
481
+ deviceRecording.sensorData.forEach((sensorTypeData) => {
482
+ const sensorTypeRecordingContainer = sensorTypeRecordingTemplate.content
483
+ .cloneNode(true)
484
+ .querySelector(".sensorTypeRecording");
485
+ sensorTypeRecordingContainer.querySelector(".sensorType").innerText = sensorTypeData.sensorType;
486
+ sensorTypeRecordingContainer.querySelector(".dataRate").innerText = sensorTypeData.dataRate;
487
+ const date = new Date(sensorTypeData.initialTimestamp);
488
+ sensorTypeRecordingContainer.querySelector(".initialTimestamp").innerText = dateToString(date);
489
+
490
+ /** @type {HTMLCanvasElement} */
491
+ const visualizationCanvas = sensorTypeRecordingContainer.querySelector(".visualization canvas");
492
+ const visualizationContainer = visualizationCanvas.closest(".visualization");
493
+
494
+ /** @type {HTMLButtonElement} */
495
+ const toggleVisualizationButton = sensorTypeRecordingContainer.querySelector(".toggleVisualization");
496
+ toggleVisualizationButton.addEventListener("click", () => {
497
+ const showVisualization = visualizationContainer.classList.contains("hidden");
498
+ visualizationContainer.classList.toggle("hidden");
499
+ console.log({ showVisualization, visualizationCanvas });
500
+ if (showVisualization) {
501
+ visualizeSensorTypeData(sensorTypeData, visualizationCanvas);
502
+ }
503
+ toggleVisualizationButton.innerText = showVisualization ? "hide visualization" : "show visualization";
504
+ });
505
+
506
+ sensorTypesContainer.appendChild(sensorTypeRecordingContainer);
507
+ });
508
+
509
+ deviceRecordingsContainer.appendChild(deviceRecordingContainer);
510
+ });
511
+
512
+ /** @type {HTMLButtonElement} */
513
+ const deleteButton = recordingContainer.querySelector(".delete");
514
+ deleteButton.addEventListener("click", () => {
515
+ const confirmDeletion = window.confirm("are you sure you want to delete this recording?");
516
+ if (!confirmDeletion) {
517
+ return;
518
+ }
519
+ recordingContainer.remove();
520
+ recordings.splice(recordings.indexOf(recording), 1);
521
+ saveRecordingsToLocalStorage();
522
+ window.dispatchEvent(new CustomEvent("recordingsUpdate"));
523
+ });
524
+
525
+ /** @type {HTMLButtonElement} */
526
+ const saveAsJSONButton = recordingContainer.querySelector(".saveAsJSON");
527
+ saveAsJSONButton.addEventListener("click", () => {
528
+ saveRecordingAsJSON(recording);
529
+ });
530
+
531
+ /** @type {HTMLButtonElement} */
532
+ const saveAsCSVButton = recordingContainer.querySelector(".saveAsCSV");
533
+ saveAsCSVButton.addEventListener("click", () => {
534
+ console.log("save as CSV");
535
+ });
536
+
537
+ recordingsContainer.appendChild(recordingContainer);
538
+
539
+ if (saveRecordings) {
540
+ saveRecordingsToLocalStorage();
541
+ }
542
+ window.dispatchEvent(new CustomEvent("recordingsUpdate"));
543
+ }
544
+
545
+ /** @type {HTMLButtonElement} */
546
+ const deleteAllRecordingsButton = document.getElementById("deleteAllRecordings");
547
+ deleteAllRecordingsButton.addEventListener("click", () => {
548
+ const confirmDeletion = window.confirm("are you sure you want to delete all recordings?");
549
+ if (!confirmDeletion) {
550
+ return;
551
+ }
552
+ recordings.length = 0;
553
+ recordingsContainer.querySelectorAll(".recording").forEach((recordingContainer) => recordingContainer.remove());
554
+ saveRecordingsToLocalStorage();
555
+ window.dispatchEvent(new CustomEvent("recordingsUpdate"));
556
+ });
557
+ /** @type {HTMLButtonElement} */
558
+ const saveAllAsCSVButton = document.getElementById("saveAllAsCSV");
559
+ saveAllAsCSVButton.addEventListener("click", () => {
560
+ console.log("saveAllAsCSV");
561
+ });
562
+ /** @type {HTMLButtonElement} */
563
+ const saveAllAsJSONButton = document.getElementById("saveAllAsJSON");
564
+ saveAllAsJSONButton.addEventListener("click", () => {
565
+ console.log("saveAllAsJSONButton");
566
+ recordings.forEach((recording) => {
567
+ saveRecordingAsJSON(recording);
568
+ });
569
+ });
570
+
571
+ window.addEventListener("recordingsUpdate", () => {
572
+ console.log("recordingsUpdate");
573
+ const disabled = recordings.length == 0;
574
+ deleteAllRecordingsButton.disabled = disabled;
575
+ saveAllAsJSONButton.disabled = disabled;
576
+ saveAllAsCSVButton.disabled = disabled;
577
+ });
578
+
579
+ // LOCAL STORAGE
580
+
581
+ const localStorageKey = "BS.Recordings";
582
+
583
+ function loadRecordingsFromLocalStorage() {
584
+ const recordingsString = localStorage.getItem(localStorageKey);
585
+ if (!recordingsString) {
586
+ return;
587
+ }
588
+ const loadedRecordings = JSON.parse(recordingsString);
589
+ console.log("loaded recordings", loadedRecordings);
590
+ loadedRecordings.forEach((recording) => {
591
+ onRecording(recording, false);
592
+ });
593
+ }
594
+ loadRecordingsFromLocalStorage();
595
+
596
+ function saveRecordingsToLocalStorage() {
597
+ console.log("saving recordings", recordings);
598
+ localStorage.setItem(localStorageKey, JSON.stringify(recordings));
599
+ }
600
+
601
+ // SAVE
602
+
603
+ /** @param {DevicesSensorData} recording */
604
+ function saveRecordingAsJSON(recording) {
605
+ console.log("saveRecordingAsJSON", recording);
606
+
607
+ const json = JSON.stringify(recording, null, 2);
608
+ const blob = new Blob([json], { type: "application/json" });
609
+ const url = URL.createObjectURL(blob);
610
+ const a = document.createElement("a");
611
+ a.href = url;
612
+ const date = new Date(recording.timestamp);
613
+ a.download = `${dateToString(date).replaceAll(":", "")}.json`;
614
+ a.style.display = "none";
615
+ document.body.appendChild(a);
616
+ a.click();
617
+
618
+ setTimeout(() => {
619
+ URL.revokeObjectURL(url);
620
+ document.body.removeChild(a);
621
+ }, 0);
622
+ }
623
+
624
+ /** @param {Date} date */
625
+ function dateToString(date) {
626
+ return date.toISOString();
627
+ }
628
+
629
+ // LOAD
630
+
631
+ const textDecoder = new TextDecoder();
632
+
633
+ /** @type {HTMLInputElement} */
634
+ const loadAsJSONInput = document.getElementById("loadAsJSON");
635
+ loadAsJSONInput.addEventListener("input", async () => {
636
+ const files = loadAsJSONInput.files;
637
+ console.log({ files });
638
+ if (!files) {
639
+ return;
640
+ }
641
+
642
+ for (let index = 0; index < files.length; index++) {
643
+ const file = files[index];
644
+ const arrayBuffer = await file.arrayBuffer();
645
+ const json = textDecoder.decode(arrayBuffer);
646
+ console.log({ json });
647
+ try {
648
+ const recording = JSON.parse(json);
649
+ console.log({ recording });
650
+ onRecording(recording, false);
651
+ } catch (error) {
652
+ console.error("unable to parse json", error);
653
+ }
654
+ }
655
+ saveRecordingsToLocalStorage();
656
+ });
657
+
658
+ // VISUALIZATION
659
+
660
+ /**
661
+ * @param {SensorTypeData} sensorTypeData
662
+ * @param {HTMLCanvasElement} canvas
663
+ */
664
+ function visualizeSensorTypeData(sensorTypeData, canvas) {
665
+ console.log("visualize");
666
+ if (canvas.chart) {
667
+ console.log("already visualized");
668
+ return;
669
+ }
670
+ console.log({ sensorTypeData, canvas });
671
+
672
+ const { sensorType } = sensorTypeData;
673
+
674
+ const scales = {
675
+ y: {
676
+ display: false,
677
+ },
678
+ x: {
679
+ display: false,
680
+ },
681
+ };
682
+ if (sensorType == "pressure") {
683
+ Object.assign(scales.y, {
684
+ min: 0,
685
+ max: 1,
686
+ });
687
+ }
688
+
689
+ const config = {
690
+ type: "line",
691
+ data: {
692
+ labels: sensorTypeData.data.map((_, index) => index * sensorTypeData.dataRate),
693
+ datasets: Object.keys(sensorTypeData.data[0]).map((key) => {
694
+ let label = key;
695
+ let data = sensorTypeData.data.map((value) => {
696
+ return value[key];
697
+ });
698
+ return {
699
+ label,
700
+ data,
701
+ radius: 1,
702
+ borderWidth: 2,
703
+ };
704
+ }),
705
+ },
706
+ options: {
707
+ responsive: true,
708
+ maintainAspectRatio: false,
709
+ plugins: {
710
+ legend: {
711
+ align: "start",
712
+ position: "top",
713
+ labels: {
714
+ font: {
715
+ //size: 20,
716
+ },
717
+ },
718
+ },
719
+ title: {
720
+ display: true,
721
+ align: "start",
722
+ text: sensorTypeData.sensorType,
723
+ font: {
724
+ //size: 20,
725
+ },
726
+ },
727
+ },
728
+ scales,
729
+ },
730
+ };
731
+
732
+ console.log({ config });
733
+
734
+ const chart = new Chart(canvas, config);
735
+ canvas.chart = chart;
736
+ }