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.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/build/brilliantsole.cjs +5957 -0
- package/build/brilliantsole.cjs.map +1 -0
- package/build/brilliantsole.js +5448 -0
- package/build/brilliantsole.js.map +1 -0
- package/build/brilliantsole.ls.js +4872 -0
- package/build/brilliantsole.ls.js.map +1 -0
- package/build/brilliantsole.min.js +6 -0
- package/build/brilliantsole.min.js.map +1 -0
- package/build/brilliantsole.module.d.ts +908 -0
- package/build/brilliantsole.module.js +5412 -0
- package/build/brilliantsole.module.js.map +1 -0
- package/build/brilliantsole.module.min.d.ts +908 -0
- package/build/brilliantsole.module.min.js +6 -0
- package/build/brilliantsole.module.min.js.map +1 -0
- package/build/brilliantsole.node.module.d.ts +908 -0
- package/build/brilliantsole.node.module.js +5906 -0
- package/build/brilliantsole.node.module.js.map +1 -0
- package/build/dts/BS.d.ts +25 -0
- package/build/dts/Device.d.ts +136 -0
- package/build/dts/DeviceInformationManager.d.ts +56 -0
- package/build/dts/DeviceManager.d.ts +67 -0
- package/build/dts/FileTransferManager.d.ts +84 -0
- package/build/dts/FirmwareManager.d.ts +71 -0
- package/build/dts/InformationManager.d.ts +66 -0
- package/build/dts/TfliteManager.d.ts +92 -0
- package/build/dts/connection/BaseConnectionManager.d.ts +59 -0
- package/build/dts/connection/ClientConnectionManager.d.ts +23 -0
- package/build/dts/connection/WebSocketClientConnectionManager.d.ts +23 -0
- package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +10 -0
- package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +42 -0
- package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +20 -0
- package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +14 -0
- package/build/dts/connection/webSocket/ClientConnectionManager.d.ts +23 -0
- package/build/dts/connection/webSocket/WebSocketClientConnectionManager.d.ts +23 -0
- package/build/dts/devicePair/DevicePair.d.ts +60 -0
- package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +25 -0
- package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +33 -0
- package/build/dts/scanner/BaseScanner.d.ts +66 -0
- package/build/dts/scanner/NobleScanner.d.ts +17 -0
- package/build/dts/scanner/Scanner.d.ts +3 -0
- package/build/dts/sensor/BarometerSensorDataManager.d.ts +16 -0
- package/build/dts/sensor/MotionSensorDataManager.d.ts +69 -0
- package/build/dts/sensor/PressureSensorDataManager.d.ts +36 -0
- package/build/dts/sensor/SensorConfigurationManager.d.ts +44 -0
- package/build/dts/sensor/SensorDataManager.d.ts +40 -0
- package/build/dts/server/BaseClient.d.ts +85 -0
- package/build/dts/server/BaseServer.d.ts +48 -0
- package/build/dts/server/ServerUtils.d.ts +23 -0
- package/build/dts/server/udp/UDPServer.d.ts +11 -0
- package/build/dts/server/udp/UDPUtils.d.ts +9 -0
- package/build/dts/server/websocket/WebSocketClient.d.ts +17 -0
- package/build/dts/server/websocket/WebSocketServer.d.ts +13 -0
- package/build/dts/server/websocket/WebSocketUtils.d.ts +9 -0
- package/build/dts/utils/ArrayBufferUtils.d.ts +7 -0
- package/build/dts/utils/ArrayUtils.d.ts +2 -0
- package/build/dts/utils/CenterOfPressureHelper.d.ts +15 -0
- package/build/dts/utils/Console.d.ts +34 -0
- package/build/dts/utils/EventDispatcher.d.ts +50 -0
- package/build/dts/utils/EventUtils.d.ts +6 -0
- package/build/dts/utils/MathUtils.d.ts +21 -0
- package/build/dts/utils/ParseUtils.d.ts +5 -0
- package/build/dts/utils/RangeHelper.d.ts +8 -0
- package/build/dts/utils/Text.d.ts +6 -0
- package/build/dts/utils/Timer.d.ts +14 -0
- package/build/dts/utils/TypeScriptUtils.d.ts +19 -0
- package/build/dts/utils/cbor.d.ts +6 -0
- package/build/dts/utils/checksum.d.ts +3 -0
- package/build/dts/utils/environment.d.ts +13 -0
- package/build/dts/utils/mcumgr.d.ts +88 -0
- package/build/dts/utils/stringUtils.d.ts +2 -0
- package/build/dts/vibration/VibrationManager.d.ts +45 -0
- package/build/dts/vibration/VibrationWaveformEffects.d.ts +2 -0
- package/build/index.d.ts +908 -0
- package/build/index.node.d.ts +908 -0
- package/examples/3d/index.html +109 -0
- package/examples/3d/scene.html +57 -0
- package/examples/3d/script.js +419 -0
- package/examples/balance/index.html +138 -0
- package/examples/balance/script.js +243 -0
- package/examples/basic/index.html +327 -0
- package/examples/basic/script.js +1093 -0
- package/examples/center-of-pressure/index.html +132 -0
- package/examples/center-of-pressure/script.js +207 -0
- package/examples/device-pair/index.html +72 -0
- package/examples/device-pair/script.js +187 -0
- package/examples/edge-impulse/index.html +94 -0
- package/examples/edge-impulse/script.js +1033 -0
- package/examples/graph/index.html +83 -0
- package/examples/graph/script.js +469 -0
- package/examples/machine-learning/index.html +366 -0
- package/examples/machine-learning/script.js +1774 -0
- package/examples/pressure/index.html +145 -0
- package/examples/pressure/script.js +201 -0
- package/examples/recording/index.html +187 -0
- package/examples/recording/script.js +736 -0
- package/examples/server/index.html +266 -0
- package/examples/server/script.js +925 -0
- package/examples/utils/aframe/fingertip-button-component.js +201 -0
- package/examples/utils/aframe/fingertip-collider-target-component.js +102 -0
- package/examples/utils/aframe/fingertip-colliders-component.js +147 -0
- package/examples/utils/three/three.module.min.js +24846 -0
- package/examples/webxr/index.html +221 -0
- package/examples/webxr/script.js +1127 -0
- package/package.json +83 -0
- package/src/BS.ts +68 -0
- package/src/Device.ts +734 -0
- package/src/DeviceInformationManager.ts +146 -0
- package/src/DeviceManager.ts +354 -0
- package/src/FileTransferManager.ts +452 -0
- package/src/FirmwareManager.ts +357 -0
- package/src/InformationManager.ts +283 -0
- package/src/TfliteManager.ts +450 -0
- package/src/connection/BaseConnectionManager.ts +255 -0
- package/src/connection/ClientConnectionManager.ts +120 -0
- package/src/connection/bluetooth/BluetoothConnectionManager.ts +34 -0
- package/src/connection/bluetooth/NobleConnectionManager.ts +302 -0
- package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +269 -0
- package/src/connection/bluetooth/bluetoothUUIDs.ts +218 -0
- package/src/devicePair/DevicePair.ts +253 -0
- package/src/devicePair/DevicePairPressureSensorDataManager.ts +82 -0
- package/src/devicePair/DevicePairSensorDataManager.ts +90 -0
- package/src/scanner/BaseScanner.ts +189 -0
- package/src/scanner/NobleScanner.ts +195 -0
- package/src/scanner/Scanner.ts +16 -0
- package/src/sensor/BarometerSensorDataManager.ts +41 -0
- package/src/sensor/MotionSensorDataManager.ts +151 -0
- package/src/sensor/PressureSensorDataManager.ts +112 -0
- package/src/sensor/SensorConfigurationManager.ts +177 -0
- package/src/sensor/SensorDataManager.ts +166 -0
- package/src/server/BaseClient.ts +368 -0
- package/src/server/BaseServer.ts +344 -0
- package/src/server/ServerUtils.ts +93 -0
- package/src/server/udp/UDPServer.ts +229 -0
- package/src/server/udp/UDPUtils.ts +20 -0
- package/src/server/websocket/WebSocketClient.ts +179 -0
- package/src/server/websocket/WebSocketServer.ts +184 -0
- package/src/server/websocket/WebSocketUtils.ts +20 -0
- package/src/utils/ArrayBufferUtils.ts +88 -0
- package/src/utils/ArrayUtils.ts +15 -0
- package/src/utils/CenterOfPressureHelper.ts +39 -0
- package/src/utils/Console.ts +156 -0
- package/src/utils/EventDispatcher.ts +153 -0
- package/src/utils/EventUtils.ts +41 -0
- package/src/utils/MathUtils.ts +53 -0
- package/src/utils/ParseUtils.ts +46 -0
- package/src/utils/RangeHelper.ts +38 -0
- package/src/utils/Text.ts +30 -0
- package/src/utils/Timer.ts +72 -0
- package/src/utils/TypeScriptUtils.ts +22 -0
- package/src/utils/cbor.js +429 -0
- package/src/utils/checksum.ts +41 -0
- package/src/utils/environment.ts +46 -0
- package/src/utils/mcumgr.js +444 -0
- package/src/utils/stringUtils.ts +11 -0
- package/src/vibration/VibrationManager.ts +308 -0
- package/src/vibration/VibrationWaveformEffects.ts +128 -0
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
import * as BS from "../../build/brilliantsole.module.js";
|
|
2
|
+
//import BS.Device from "../../src/BS.Device.js";
|
|
3
|
+
window.BS = BS;
|
|
4
|
+
console.log(BS);
|
|
5
|
+
|
|
6
|
+
BS.setAllConsoleLevelFlags({ log: false });
|
|
7
|
+
|
|
8
|
+
// DEVICE
|
|
9
|
+
|
|
10
|
+
const device = new BS.Device();
|
|
11
|
+
console.log({ device });
|
|
12
|
+
window.device = device;
|
|
13
|
+
|
|
14
|
+
// GET DEVICES
|
|
15
|
+
|
|
16
|
+
/** @type {HTMLTemplateElement} */
|
|
17
|
+
const availableDeviceTemplate = document.getElementById("availableDeviceTemplate");
|
|
18
|
+
const availableDevicesContainer = document.getElementById("availableDevices");
|
|
19
|
+
/** @param {BS.Device[]} availableDevices */
|
|
20
|
+
function onAvailableDevices(availableDevices) {
|
|
21
|
+
availableDevicesContainer.innerHTML = "";
|
|
22
|
+
if (availableDevices.length == 0) {
|
|
23
|
+
availableDevicesContainer.innerText = "no devices available";
|
|
24
|
+
} else {
|
|
25
|
+
availableDevices.forEach((availableDevice) => {
|
|
26
|
+
const availableDeviceContainer = availableDeviceTemplate.content
|
|
27
|
+
.cloneNode(true)
|
|
28
|
+
.querySelector(".availableDevice");
|
|
29
|
+
availableDeviceContainer.querySelector(".name").innerText = availableDevice.name;
|
|
30
|
+
availableDeviceContainer.querySelector(".type").innerText = availableDevice.type;
|
|
31
|
+
|
|
32
|
+
/** @type {HTMLButtonElement} */
|
|
33
|
+
const toggleConnectionButton = availableDeviceContainer.querySelector(".toggleConnection");
|
|
34
|
+
toggleConnectionButton.addEventListener("click", () => {
|
|
35
|
+
device.connectionManager = availableDevice.connectionManager;
|
|
36
|
+
device.reconnect();
|
|
37
|
+
});
|
|
38
|
+
device.addEventListener("connectionStatus", () => {
|
|
39
|
+
toggleConnectionButton.disabled = device.connectionStatus != "notConnected";
|
|
40
|
+
});
|
|
41
|
+
toggleConnectionButton.disabled = device.connectionStatus != "notConnected";
|
|
42
|
+
|
|
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
|
+
// CONNECTION
|
|
62
|
+
|
|
63
|
+
/** @type {HTMLButtonElement} */
|
|
64
|
+
const toggleConnectionButton = document.getElementById("toggleConnection");
|
|
65
|
+
toggleConnectionButton.addEventListener("click", () => {
|
|
66
|
+
switch (device.connectionStatus) {
|
|
67
|
+
case "notConnected":
|
|
68
|
+
device.connect();
|
|
69
|
+
break;
|
|
70
|
+
case "connected":
|
|
71
|
+
device.disconnect();
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** @type {HTMLButtonElement} */
|
|
77
|
+
const reconnectButton = document.getElementById("reconnect");
|
|
78
|
+
reconnectButton.addEventListener("click", () => {
|
|
79
|
+
device.reconnect();
|
|
80
|
+
});
|
|
81
|
+
device.addEventListener("connectionStatus", () => {
|
|
82
|
+
reconnectButton.disabled = !device.canReconnect;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
device.addEventListener("connectionStatus", () => {
|
|
86
|
+
switch (device.connectionStatus) {
|
|
87
|
+
case "connected":
|
|
88
|
+
case "notConnected":
|
|
89
|
+
toggleConnectionButton.disabled = false;
|
|
90
|
+
toggleConnectionButton.innerText = device.isConnected ? "disconnect" : "connect";
|
|
91
|
+
break;
|
|
92
|
+
case "connecting":
|
|
93
|
+
case "disconnecting":
|
|
94
|
+
toggleConnectionButton.disabled = true;
|
|
95
|
+
toggleConnectionButton.innerText = device.connectionStatus;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** @type {HTMLInputElement} */
|
|
101
|
+
const reconnectOnDisconnectionCheckbox = document.getElementById("reconnectOnDisconnection");
|
|
102
|
+
reconnectOnDisconnectionCheckbox.addEventListener("input", () => {
|
|
103
|
+
device.reconnectOnDisconnection = reconnectOnDisconnectionCheckbox.checked;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// DEVICE INFORMATION
|
|
107
|
+
|
|
108
|
+
/** @type {HTMLPreElement} */
|
|
109
|
+
const deviceInformationPre = document.getElementById("deviceInformationPre");
|
|
110
|
+
device.addEventListener("deviceInformation", () => {
|
|
111
|
+
deviceInformationPre.textContent = JSON.stringify(device.deviceInformation, null, 2);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/** @type {HTMLPreElement} */
|
|
115
|
+
const sensorConfigurationPre = document.getElementById("sensorConfigurationPre");
|
|
116
|
+
device.addEventListener("getSensorConfiguration", () => {
|
|
117
|
+
sensorConfigurationPre.textContent = JSON.stringify(device.sensorConfiguration, null, 2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/** @type {HTMLSpanElement} */
|
|
121
|
+
const nameSpan = document.getElementById("name");
|
|
122
|
+
device.addEventListener("getName", () => {
|
|
123
|
+
nameSpan.innerText = device.name;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/** @type {HTMLSpanElement} */
|
|
127
|
+
const typeSpan = document.getElementById("type");
|
|
128
|
+
device.addEventListener("getType", () => {
|
|
129
|
+
typeSpan.innerText = device.type;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/** @type {HTMLSpanElement} */
|
|
133
|
+
const batteryLevelSpan = document.getElementById("batteryLevel");
|
|
134
|
+
device.addEventListener("batteryLevel", () => {
|
|
135
|
+
batteryLevelSpan.innerText = device.batteryLevel;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// SEARCH PARAMS
|
|
139
|
+
|
|
140
|
+
const url = new URL(location);
|
|
141
|
+
console.log({ url });
|
|
142
|
+
function setUrlParam(key, value) {
|
|
143
|
+
if (history.pushState) {
|
|
144
|
+
let searchParams = new URLSearchParams(window.location.search);
|
|
145
|
+
if (value) {
|
|
146
|
+
searchParams.set(key, value);
|
|
147
|
+
} else {
|
|
148
|
+
searchParams.delete(key);
|
|
149
|
+
}
|
|
150
|
+
let newUrl =
|
|
151
|
+
window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + searchParams.toString();
|
|
152
|
+
window.history.pushState({ path: newUrl }, "", newUrl);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// PROJECT ID
|
|
157
|
+
|
|
158
|
+
/** @type {string?} */
|
|
159
|
+
let projectId;
|
|
160
|
+
|
|
161
|
+
/** @type {HTMLInputElement} */
|
|
162
|
+
const projectIdInput = document.getElementById("projectId");
|
|
163
|
+
projectIdInput.addEventListener("input", (event) => {
|
|
164
|
+
setProjectId(event.target.value);
|
|
165
|
+
setHmacKey();
|
|
166
|
+
});
|
|
167
|
+
/** @param {string} newProjectId */
|
|
168
|
+
function setProjectId(newProjectId) {
|
|
169
|
+
projectId = newProjectId;
|
|
170
|
+
console.log({ projectId });
|
|
171
|
+
window.dispatchEvent(new Event("projectId"));
|
|
172
|
+
projectIdInput.value = projectId;
|
|
173
|
+
setUrlParam("projectId", projectId);
|
|
174
|
+
}
|
|
175
|
+
window.addEventListener("loadConfig", () => {
|
|
176
|
+
if (config.projectId) {
|
|
177
|
+
projectIdInput.value = config.projectId;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
window.addEventListener("load", () => {
|
|
181
|
+
if (url.searchParams.has("projectId")) {
|
|
182
|
+
setProjectId(url.searchParams.get("projectId"));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// EDGE IMPULSE KEYS
|
|
187
|
+
|
|
188
|
+
/** @type {string?} */
|
|
189
|
+
let apiKey;
|
|
190
|
+
|
|
191
|
+
const apiKeyInput = document.getElementById("apiKey");
|
|
192
|
+
apiKeyInput.addEventListener("input", (event) => {
|
|
193
|
+
setApiKey(event.target.value);
|
|
194
|
+
setHmacKey();
|
|
195
|
+
});
|
|
196
|
+
function setApiKey(newApiKey) {
|
|
197
|
+
apiKey = newApiKey;
|
|
198
|
+
apiKeyInput.value = apiKey;
|
|
199
|
+
console.log({ apiKey });
|
|
200
|
+
window.dispatchEvent(new Event("apiKey"));
|
|
201
|
+
setUrlParam("apiKey", apiKey);
|
|
202
|
+
}
|
|
203
|
+
window.addEventListener("loadConfig", () => {
|
|
204
|
+
if (config.apiKey) {
|
|
205
|
+
setApiKey(config.apiKey);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
window.addEventListener("load", () => {
|
|
209
|
+
if (url.searchParams.has("apiKey")) {
|
|
210
|
+
setApiKey(url.searchParams.get("apiKey"));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// HmacKey
|
|
215
|
+
|
|
216
|
+
/** @type {string?} */
|
|
217
|
+
let hmacKey;
|
|
218
|
+
|
|
219
|
+
function setHmacKey(newHmacKey) {
|
|
220
|
+
hmacKey = newHmacKey;
|
|
221
|
+
console.log({ hmacKey });
|
|
222
|
+
window.dispatchEvent(new Event("hmacKey"));
|
|
223
|
+
updateHmacKeyButton();
|
|
224
|
+
setUrlParam("hmacKey", hmacKey);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** @type {HTMLButtonElement} */
|
|
228
|
+
const getHmacKeyButton = document.getElementById("getHmacKey");
|
|
229
|
+
getHmacKeyButton.addEventListener("click", async () => {
|
|
230
|
+
getHmacKeyButton.innerText = "getting hmacKey...";
|
|
231
|
+
getHmacKeyButton.disabled = true;
|
|
232
|
+
await getHmacKey();
|
|
233
|
+
updateHmacKeyButton();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
function updateHmacKeyButton() {
|
|
237
|
+
getHmacKeyButton.disabled = Boolean(hmacKey);
|
|
238
|
+
getHmacKeyButton.innerText = Boolean(hmacKey) ? "got hmacKey" : "get hmacKey";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
["projectId", "apiKey", "hmacKey"].forEach((eventType) => {
|
|
242
|
+
window.addEventListener(eventType, () => {
|
|
243
|
+
updateHmacKeyButton();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
window.addEventListener("load", () => {
|
|
248
|
+
if (url.searchParams.has("hmacKey")) {
|
|
249
|
+
setHmacKey(url.searchParams.get("hmacKey"));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// SENSOR TYPES
|
|
254
|
+
|
|
255
|
+
/** @type {BS.ContinuousSensorType[]} */
|
|
256
|
+
let sensorTypes = [];
|
|
257
|
+
/** @param {BS.ContinuousSensorType[]} newSensorTypes */
|
|
258
|
+
function setSensorTypes(newSensorTypes) {
|
|
259
|
+
sensorTypes = newSensorTypes;
|
|
260
|
+
console.log("sensorTypes", sensorTypes);
|
|
261
|
+
window.dispatchEvent(new Event("sensorTypes"));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const sensorTypesContainer = document.getElementById("sensorTypes");
|
|
265
|
+
/** @type {HTMLTemplateElement} */
|
|
266
|
+
const sensorTypeTemplate = document.getElementById("sensorTypeTemplate");
|
|
267
|
+
/** @type {Object.<string, HTMLElement>} */
|
|
268
|
+
const sensorTypeContainers = {};
|
|
269
|
+
|
|
270
|
+
BS.TfliteSensorTypes.forEach((sensorType) => {
|
|
271
|
+
const sensorTypeContainer = sensorTypeTemplate.content.cloneNode(true).querySelector(".sensorType");
|
|
272
|
+
sensorTypeContainer.querySelector(".name").innerText = sensorType;
|
|
273
|
+
|
|
274
|
+
/** @type {HTMLInputElement} */
|
|
275
|
+
const isSensorEnabledInput = sensorTypeContainer.querySelector(".enabled");
|
|
276
|
+
isSensorEnabledInput.addEventListener("input", () => {
|
|
277
|
+
onSensorTypesInput();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
window.addEventListener("sensorTypes", () => {
|
|
281
|
+
isSensorEnabledInput.checked = sensorTypes.includes(sensorType);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
sensorTypeContainers[sensorType] = sensorTypeContainer;
|
|
285
|
+
|
|
286
|
+
sensorTypesContainer.appendChild(sensorTypeContainer);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
function onSensorTypesInput() {
|
|
290
|
+
const sensorTypes = BS.TfliteSensorTypes.filter((sensorType) => {
|
|
291
|
+
/** @type {HTMLInputElement} */
|
|
292
|
+
const input = sensorTypeContainers[sensorType].querySelector(".enabled");
|
|
293
|
+
return input.checked;
|
|
294
|
+
});
|
|
295
|
+
setSensorTypes(sensorTypes);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// SAMPLING
|
|
299
|
+
|
|
300
|
+
/** @type {number} */
|
|
301
|
+
let samplingInterval;
|
|
302
|
+
/** @param {number} newSamplingInterval */
|
|
303
|
+
function setSamplingInterval(newSamplingInterval) {
|
|
304
|
+
samplingInterval = newSamplingInterval;
|
|
305
|
+
console.log({ samplingInterval });
|
|
306
|
+
samplingIntervalInput.value = samplingInterval;
|
|
307
|
+
window.dispatchEvent(new Event("samplingInterval"));
|
|
308
|
+
}
|
|
309
|
+
/** @type {HTMLInputElement} */
|
|
310
|
+
const samplingIntervalInput = document.getElementById("samplingInterval");
|
|
311
|
+
samplingIntervalInput.addEventListener("input", (event) => {
|
|
312
|
+
setSamplingInterval(Number(event.target.value));
|
|
313
|
+
});
|
|
314
|
+
setSamplingInterval(20);
|
|
315
|
+
|
|
316
|
+
/** @type {number} */
|
|
317
|
+
let numberOfSamples;
|
|
318
|
+
/** @param {number} newNumberOfSamples */
|
|
319
|
+
function setNumberOfSamples(newNumberOfSamples) {
|
|
320
|
+
numberOfSamples = newNumberOfSamples;
|
|
321
|
+
console.log({ numberOfSamples });
|
|
322
|
+
numberOfSamplesInput.value = numberOfSamples;
|
|
323
|
+
window.dispatchEvent(new Event("numberOfSamples"));
|
|
324
|
+
}
|
|
325
|
+
/** @type {HTMLInputElement} */
|
|
326
|
+
const numberOfSamplesInput = document.getElementById("numberOfSamples");
|
|
327
|
+
numberOfSamplesInput.addEventListener("input", (event) => {
|
|
328
|
+
setNumberOfSamples(Number(event.target.value));
|
|
329
|
+
});
|
|
330
|
+
setNumberOfSamples(10);
|
|
331
|
+
|
|
332
|
+
/** @type {string} */
|
|
333
|
+
let label;
|
|
334
|
+
/** @type {HTMLInputElement} */
|
|
335
|
+
const labelInput = document.getElementById("label");
|
|
336
|
+
labelInput.addEventListener("input", (event) => {
|
|
337
|
+
setLabel(event.target.value);
|
|
338
|
+
});
|
|
339
|
+
/** @param {string} newLabel */
|
|
340
|
+
function setLabel(newLabel) {
|
|
341
|
+
label = newLabel;
|
|
342
|
+
console.log({ label });
|
|
343
|
+
labelInput.value = label;
|
|
344
|
+
window.dispatchEvent(new Event("label"));
|
|
345
|
+
}
|
|
346
|
+
setLabel("idle");
|
|
347
|
+
|
|
348
|
+
/** @type {string} */
|
|
349
|
+
let path;
|
|
350
|
+
/** @type {HTMLInputElement} */
|
|
351
|
+
const pathInput = document.getElementById("path");
|
|
352
|
+
pathInput.addEventListener("input", (event) => {
|
|
353
|
+
setPath(event.target.value);
|
|
354
|
+
});
|
|
355
|
+
/** @param {string} newPath */
|
|
356
|
+
function setPath(newPath) {
|
|
357
|
+
path = newPath;
|
|
358
|
+
console.log({ path });
|
|
359
|
+
pathInput.value = path;
|
|
360
|
+
window.dispatchEvent(new Event("path"));
|
|
361
|
+
}
|
|
362
|
+
setPath("/api/training/data");
|
|
363
|
+
|
|
364
|
+
/** @type {number} */
|
|
365
|
+
let samplingLength;
|
|
366
|
+
/** @param {number} newSamplingLength */
|
|
367
|
+
function setSamplingLength(newSamplingLength) {
|
|
368
|
+
samplingLength = newSamplingLength;
|
|
369
|
+
console.log({ samplingLength });
|
|
370
|
+
samplingLengthInput.value = samplingLength;
|
|
371
|
+
window.dispatchEvent(new Event("samplingLength"));
|
|
372
|
+
}
|
|
373
|
+
/** @type {HTMLInputElement} */
|
|
374
|
+
const samplingLengthInput = document.getElementById("samplingLength");
|
|
375
|
+
|
|
376
|
+
function updateSamplingLength() {
|
|
377
|
+
const newSamplingLength = numberOfSamples * samplingInterval;
|
|
378
|
+
setSamplingLength(newSamplingLength);
|
|
379
|
+
}
|
|
380
|
+
updateSamplingLength();
|
|
381
|
+
|
|
382
|
+
["samplingInterval", "numberOfSamples"].forEach((eventType) => {
|
|
383
|
+
window.addEventListener(eventType, () => {
|
|
384
|
+
updateSamplingLength();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// SAMPLING
|
|
389
|
+
|
|
390
|
+
let isSampling = false;
|
|
391
|
+
/** @param {boolean} newIsSampling */
|
|
392
|
+
function setIsSampling(newIsSampling) {
|
|
393
|
+
isSampling = newIsSampling;
|
|
394
|
+
console.log({ isSampling });
|
|
395
|
+
window.dispatchEvent(new Event("isSampling"));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** @type {HTMLButtonElement} */
|
|
399
|
+
const toggleSamplingButton = document.getElementById("toggleSampling");
|
|
400
|
+
toggleSamplingButton.addEventListener("click", () => {
|
|
401
|
+
sampleAndUpload();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
function updateToggleSamplingButton() {
|
|
405
|
+
const enabled =
|
|
406
|
+
device.isConnected && isRemoteManagementConnected() && sensorTypes.length > 0 && label.length > 0 && !isSampling;
|
|
407
|
+
toggleSamplingButton.disabled = !enabled;
|
|
408
|
+
toggleSamplingButton.innerText = isSampling ? "sampling..." : "start sampling";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
["isSampling", "remoteManagementConnection", "sensortypes", "label"].forEach((eventType) => {
|
|
412
|
+
window.addEventListener(eventType, () => {
|
|
413
|
+
updateToggleSamplingButton();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
device.addEventListener("isConnected", () => {
|
|
417
|
+
updateToggleSamplingButton();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
async function sampleAndUpload() {
|
|
421
|
+
/** @type {SensorConfiguration} */
|
|
422
|
+
const sensorConfiguration = {};
|
|
423
|
+
sensorTypes.forEach((sensorType) => {
|
|
424
|
+
sensorConfiguration[sensorType] = samplingInterval;
|
|
425
|
+
});
|
|
426
|
+
console.log("sensorConfiguration", sensorConfiguration);
|
|
427
|
+
device.setSensorConfiguration(sensorConfiguration);
|
|
428
|
+
|
|
429
|
+
setIsSampling(true);
|
|
430
|
+
|
|
431
|
+
const deviceData = await collectData(sensorTypes, numberOfSamples);
|
|
432
|
+
await device.clearSensorConfiguration();
|
|
433
|
+
console.log("deviceData", deviceData);
|
|
434
|
+
|
|
435
|
+
sendRemoteManagementMessage?.({ sampleFinished: true });
|
|
436
|
+
setIsSampling(false);
|
|
437
|
+
|
|
438
|
+
sendRemoteManagementMessage?.({ sampleUploading: true });
|
|
439
|
+
await uploadData(sensorTypes, deviceData);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// EDGE IMPULSE API
|
|
443
|
+
|
|
444
|
+
const ingestionApi = "https://ingestion.edgeimpulse.com";
|
|
445
|
+
const remoteManagementEndpoint = "wss://remote-mgmt.edgeimpulse.com";
|
|
446
|
+
const studioEndpoint = "https://studio.edgeimpulse.com";
|
|
447
|
+
|
|
448
|
+
async function getProjects() {
|
|
449
|
+
return new Promise((resolve, reject) => {
|
|
450
|
+
const x = new XMLHttpRequest();
|
|
451
|
+
x.open("GET", `${studioEndpoint}/v1/api/projects`);
|
|
452
|
+
x.onload = () => {
|
|
453
|
+
if (x.status !== 200) {
|
|
454
|
+
reject("No projects found: " + x.status + " - " + JSON.stringify(x.response));
|
|
455
|
+
} else {
|
|
456
|
+
if (!x.response.success) {
|
|
457
|
+
reject(x.response.error);
|
|
458
|
+
} else {
|
|
459
|
+
const projects = x.response.projects;
|
|
460
|
+
console.log("projects", projects);
|
|
461
|
+
resolve(projects);
|
|
462
|
+
window.dispatchEvent(new CustomEvent("edgeImpulseProjects", { detail: { projects } }));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
x.onerror = (err) => reject(err);
|
|
467
|
+
x.responseType = "json";
|
|
468
|
+
x.setRequestHeader("x-api-key", apiKey);
|
|
469
|
+
x.send();
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function getProject() {
|
|
474
|
+
return new Promise((resolve, reject) => {
|
|
475
|
+
const x = new XMLHttpRequest();
|
|
476
|
+
x.open("GET", `${studioEndpoint}/v1/api/${projectId}/public-info`);
|
|
477
|
+
x.onload = () => {
|
|
478
|
+
if (x.status !== 200) {
|
|
479
|
+
reject("No projects found: " + x.status + " - " + JSON.stringify(x.response));
|
|
480
|
+
} else {
|
|
481
|
+
if (!x.response.success) {
|
|
482
|
+
reject(x.response.error);
|
|
483
|
+
} else {
|
|
484
|
+
const project = x.response;
|
|
485
|
+
console.log("project", project);
|
|
486
|
+
resolve(project);
|
|
487
|
+
window.dispatchEvent(new CustomEvent("edgeImpulseProject", { detail: { project } }));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
if (apiKey) {
|
|
492
|
+
x.setRequestHeader("x-api-key", apiKey);
|
|
493
|
+
}
|
|
494
|
+
x.onerror = (err) => reject(err);
|
|
495
|
+
x.responseType = "json";
|
|
496
|
+
x.send();
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function getHmacKey() {
|
|
501
|
+
return new Promise((resolve, reject) => {
|
|
502
|
+
const x = new XMLHttpRequest();
|
|
503
|
+
x.open("GET", `${studioEndpoint}/v1/api/${projectId}/devkeys`);
|
|
504
|
+
x.onload = () => {
|
|
505
|
+
if (x.status !== 200) {
|
|
506
|
+
reject("No development keys found: " + x.status + " - " + JSON.stringify(x.response));
|
|
507
|
+
} else {
|
|
508
|
+
if (!x.response.success) {
|
|
509
|
+
reject(x.response.error);
|
|
510
|
+
} else {
|
|
511
|
+
const { apiKey, hmacKey } = x.response;
|
|
512
|
+
console.log({ apiKey, hmacKey });
|
|
513
|
+
//setApiKey(apiKey);
|
|
514
|
+
setHmacKey(hmacKey);
|
|
515
|
+
resolve({
|
|
516
|
+
apiKey,
|
|
517
|
+
hmacKey,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
x.onerror = (err) => reject(err);
|
|
523
|
+
x.responseType = "json";
|
|
524
|
+
if (apiKey) {
|
|
525
|
+
x.setRequestHeader("x-api-key", apiKey);
|
|
526
|
+
}
|
|
527
|
+
x.send();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// REMOTE MANAGEMENT
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* @typedef SamplingDetails
|
|
535
|
+
* @type {Object}
|
|
536
|
+
* @property {string} path
|
|
537
|
+
* @property {string} label
|
|
538
|
+
* @property {number} length ms
|
|
539
|
+
* @property {number} interval ms
|
|
540
|
+
* @property {string} hmacKey
|
|
541
|
+
* @property {string} sensor
|
|
542
|
+
*/
|
|
543
|
+
|
|
544
|
+
/** @type {WebSocket?} */
|
|
545
|
+
let remoteManagementWebSocket;
|
|
546
|
+
/** @type {(message: object)=>{}?} */
|
|
547
|
+
let sendRemoteManagementMessage;
|
|
548
|
+
async function connectToRemoteManagement() {
|
|
549
|
+
remoteManagementWebSocket?.close();
|
|
550
|
+
|
|
551
|
+
/** @type {number?} */
|
|
552
|
+
let intervalId;
|
|
553
|
+
|
|
554
|
+
const ws = new WebSocket(remoteManagementEndpoint);
|
|
555
|
+
remoteManagementWebSocket = ws;
|
|
556
|
+
|
|
557
|
+
sendRemoteManagementMessage = (message) => {
|
|
558
|
+
console.log("sending message", message);
|
|
559
|
+
ws.send(JSON.stringify(message));
|
|
560
|
+
};
|
|
561
|
+
ws.addEventListener("open", () => {
|
|
562
|
+
console.log("remoteManagementWebSocket.open");
|
|
563
|
+
window.dispatchEvent(new Event("remoteManagementConnection"));
|
|
564
|
+
sendRemoteManagementMessage(remoteManagementHelloMessage());
|
|
565
|
+
intervalId = setInterval(() => {
|
|
566
|
+
console.log("ping");
|
|
567
|
+
ws.send("ping");
|
|
568
|
+
}, 3000);
|
|
569
|
+
});
|
|
570
|
+
ws.addEventListener("close", () => {
|
|
571
|
+
console.log("remoteManagementWebSocket.close");
|
|
572
|
+
window.dispatchEvent(new Event("remoteManagementConnection"));
|
|
573
|
+
clearInterval(intervalId);
|
|
574
|
+
if (reconnectRemoteManagementOnDisconnection && !ws.dontReconnect) {
|
|
575
|
+
window.setTimeout(() => {
|
|
576
|
+
if (!reconnectRemoteManagementOnDisconnection) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
connectToRemoteManagement();
|
|
580
|
+
}, 2000);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
ws.addEventListener("error", (event) => {
|
|
584
|
+
console.log("remoteManagementWebSocket.error", event);
|
|
585
|
+
window.dispatchEvent(new Event("remoteManagementConnection"));
|
|
586
|
+
});
|
|
587
|
+
ws.addEventListener("message", async (event) => {
|
|
588
|
+
console.log("remoteManagementWebSocket.message", event.data);
|
|
589
|
+
|
|
590
|
+
const data = await parseRemoteManagementMessage(event);
|
|
591
|
+
if (!data) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
console.log({ data });
|
|
596
|
+
|
|
597
|
+
if ("hello" in data) {
|
|
598
|
+
const isConnected = data.hello;
|
|
599
|
+
console.log({ isConnected });
|
|
600
|
+
if (isConnected) {
|
|
601
|
+
ws._isConnected = true;
|
|
602
|
+
window.dispatchEvent(new Event("remoteManagementConnection"));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if ("sample" in data) {
|
|
607
|
+
/** @type {SamplingDetails} */
|
|
608
|
+
const samplingDetails = data.sample;
|
|
609
|
+
console.log("samplingDetails", samplingDetails);
|
|
610
|
+
|
|
611
|
+
/** @type {BS.ContinuousSensorType[]} */
|
|
612
|
+
const sensorTypes = samplingDetails.sensor.split(sensorCombinationSeparator);
|
|
613
|
+
console.log("sensorTypes", sensorTypes);
|
|
614
|
+
|
|
615
|
+
const invalidSensors = sensorTypes.filter((sensorType) => !device.allowedTfliteSensorTypes.includes(sensorType));
|
|
616
|
+
if (invalidSensors.length > 0) {
|
|
617
|
+
console.error("invalid sensorTypes", invalidSensors);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const numberOfSamples = samplingDetails.length / samplingDetails.interval;
|
|
622
|
+
setNumberOfSamples(numberOfSamples);
|
|
623
|
+
setSensorTypes(sensorTypes);
|
|
624
|
+
setSamplingInterval(samplingDetails.interval);
|
|
625
|
+
setSamplingLength(samplingDetails.length);
|
|
626
|
+
setLabel(samplingDetails.label);
|
|
627
|
+
setHmacKey(samplingDetails.hmacKey);
|
|
628
|
+
setPath(samplingDetails.path);
|
|
629
|
+
|
|
630
|
+
sampleAndUpload();
|
|
631
|
+
|
|
632
|
+
// /** @type {SensorConfiguration} */
|
|
633
|
+
// const sensorConfiguration = {};
|
|
634
|
+
// sensorTypes.forEach((sensorType) => {
|
|
635
|
+
// sensorConfiguration[sensorType] = samplingDetails.interval;
|
|
636
|
+
// });
|
|
637
|
+
// console.log("sensorConfiguration", sensorConfiguration);
|
|
638
|
+
// device.setSensorConfiguration(sensorConfiguration);
|
|
639
|
+
|
|
640
|
+
// setIsSampling(true);
|
|
641
|
+
|
|
642
|
+
// const deviceData = await collectData(sensorTypes, numberOfSamples);
|
|
643
|
+
// await device.clearSensorConfiguration();
|
|
644
|
+
// console.log("deviceData", deviceData);
|
|
645
|
+
|
|
646
|
+
// sendRemoteManagementMessage?.({ sampleFinished: true });
|
|
647
|
+
// setIsSampling(false);
|
|
648
|
+
|
|
649
|
+
// sendRemoteManagementMessage?.({ sampleUploading: true });
|
|
650
|
+
// await uploadData(samplingDetails, sensorTypes, deviceData);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function parseRemoteManagementMessage(event) {
|
|
656
|
+
if (event.data instanceof Blob) {
|
|
657
|
+
return await readFile(event.data);
|
|
658
|
+
} else if (typeof event.data === "string") {
|
|
659
|
+
if (event.data === "pong") return null;
|
|
660
|
+
return JSON.parse(event.data);
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const sensorCombinationSeparator = " + ";
|
|
666
|
+
|
|
667
|
+
// ChatGPT
|
|
668
|
+
/** @param {string[]} array */
|
|
669
|
+
function generateSubarrays(array) {
|
|
670
|
+
/** @type {string[][]} */
|
|
671
|
+
const results = [];
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* @param {number} start
|
|
675
|
+
* @param {string[]} subArray
|
|
676
|
+
*/
|
|
677
|
+
function helper(start, subArray) {
|
|
678
|
+
results.push(subArray);
|
|
679
|
+
|
|
680
|
+
for (let i = start; i < array.length; i++) {
|
|
681
|
+
helper(i + 1, subArray.concat(array[i]));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
helper(0, []);
|
|
686
|
+
|
|
687
|
+
return results
|
|
688
|
+
.sort((a, b) => a.length - b.length)
|
|
689
|
+
.filter((subArray) => subArray.length > 0)
|
|
690
|
+
.map((subArray) => subArray.join(sensorCombinationSeparator));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const getDeviceId = () => `${device.name}.${device.type}.${device.id}`;
|
|
694
|
+
|
|
695
|
+
function remoteManagementHelloMessage() {
|
|
696
|
+
const sensors = device.allowedTfliteSensorTypes;
|
|
697
|
+
const sensorCombinations = generateSubarrays(sensors);
|
|
698
|
+
console.log("sensorCombinations", sensorCombinations);
|
|
699
|
+
return {
|
|
700
|
+
hello: {
|
|
701
|
+
version: 3,
|
|
702
|
+
apiKey: apiKey,
|
|
703
|
+
deviceId: getDeviceId(),
|
|
704
|
+
deviceType: "BrilliantSole",
|
|
705
|
+
connection: "ip",
|
|
706
|
+
sensors: sensorCombinations.map((sensorCombination) => {
|
|
707
|
+
return {
|
|
708
|
+
name: sensorCombination,
|
|
709
|
+
maxSampleLengthS: 1 * 60,
|
|
710
|
+
frequencies: [50.0, 25.0, 12.5], // 20ms, 40ms, 80ms
|
|
711
|
+
};
|
|
712
|
+
}),
|
|
713
|
+
supportsSnapshotStreaming: false,
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function isRemoteManagementConnected() {
|
|
719
|
+
return remoteManagementWebSocket?.readyState == WebSocket.OPEN && remoteManagementWebSocket?._isConnected;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** @type {HTMLButtonElement} */
|
|
723
|
+
const toggleRemoteManagementConnectionButton = document.getElementById("toggleRemoteManagementConnection");
|
|
724
|
+
toggleRemoteManagementConnectionButton.addEventListener("click", () => {
|
|
725
|
+
if (isRemoteManagementConnected()) {
|
|
726
|
+
remoteManagementWebSocket.dontReconnect = true;
|
|
727
|
+
remoteManagementWebSocket.close();
|
|
728
|
+
toggleRemoteManagementConnectionButton.innerText = "disconnecting...";
|
|
729
|
+
toggleRemoteManagementConnectionButton.disabled = true;
|
|
730
|
+
} else {
|
|
731
|
+
connectToRemoteManagement();
|
|
732
|
+
toggleRemoteManagementConnectionButton.innerText = "connecting...";
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
window.addEventListener("remoteManagementConnection", () => {
|
|
737
|
+
if (isRemoteManagementConnected()) {
|
|
738
|
+
toggleRemoteManagementConnectionButton.innerText = "disconnect";
|
|
739
|
+
toggleRemoteManagementConnectionButton.disabled = false;
|
|
740
|
+
} else {
|
|
741
|
+
toggleRemoteManagementConnectionButton.innerText = "connect";
|
|
742
|
+
toggleRemoteManagementConnectionButton.disabled = false;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
function updateToggleRemoteManagementConnectionButton() {
|
|
747
|
+
const enabled = device.isConnected;
|
|
748
|
+
toggleRemoteManagementConnectionButton.disabled = !enabled;
|
|
749
|
+
}
|
|
750
|
+
device.addEventListener("isConnected", () => {
|
|
751
|
+
updateToggleRemoteManagementConnectionButton();
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
let reconnectRemoteManagementOnDisconnection = false;
|
|
755
|
+
/** @type {HTMLInputElement} */
|
|
756
|
+
const reconnectRemoteManagementOnDisconnectionInput = document.getElementById(
|
|
757
|
+
"reconnectRemoteManagementOnDisconnection"
|
|
758
|
+
);
|
|
759
|
+
reconnectRemoteManagementOnDisconnectionInput.addEventListener("input", (event) => {
|
|
760
|
+
setReconnectRemoteManagementOnDisconnection(event.target.checked);
|
|
761
|
+
});
|
|
762
|
+
reconnectRemoteManagementOnDisconnectionInput.checked = reconnectRemoteManagementOnDisconnection;
|
|
763
|
+
/** @param {boolean} newReconnectRemoteManagementOnDisconnection */
|
|
764
|
+
function setReconnectRemoteManagementOnDisconnection(newReconnectRemoteManagementOnDisconnection) {
|
|
765
|
+
reconnectRemoteManagementOnDisconnection = newReconnectRemoteManagementOnDisconnection;
|
|
766
|
+
console.log({ reconnectRemoteManagementOnDisconnection });
|
|
767
|
+
dispatchEvent(new Event("reconnectRemoteManagementOnDisconnection"));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// DATA COLLECTION
|
|
771
|
+
|
|
772
|
+
const scalars = {
|
|
773
|
+
pressure: 1 / (2 ** 16 - 1),
|
|
774
|
+
linearAcceleration: 1 / 4,
|
|
775
|
+
gyroscope: 1 / 720,
|
|
776
|
+
magnetometer: 1 / 2500,
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
/** @typedef {Object.<string, number>} SensorData */
|
|
780
|
+
/** @typedef {Object.<string, SensorData[]>} DeviceData */
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* @param {BS.ContinuousSensorType[]} sensorTypes
|
|
784
|
+
* @param {number} numberOfSamples
|
|
785
|
+
* @returns {Promise<DeviceData>}
|
|
786
|
+
*/
|
|
787
|
+
async function collectData(sensorTypes, numberOfSamples) {
|
|
788
|
+
return new Promise((resolve) => {
|
|
789
|
+
/** @type {DeviceData} */
|
|
790
|
+
const deviceData = {};
|
|
791
|
+
|
|
792
|
+
sensorTypes.forEach((sensorType) => {
|
|
793
|
+
deviceData[sensorType] = [];
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
console.log("deviceData", deviceData);
|
|
797
|
+
|
|
798
|
+
const onDeviceSensorData = (event) => {
|
|
799
|
+
/** @type {SensorType} */
|
|
800
|
+
const sensorType = event.message.sensorType;
|
|
801
|
+
|
|
802
|
+
if (!(sensorType in deviceData)) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const didFinishCollectingDeviceData = Object.values(deviceData).every(
|
|
807
|
+
(sensorData) => sensorData.length >= numberOfSamples
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
if (didFinishCollectingDeviceData) {
|
|
811
|
+
console.log("finished collecting data for device", deviceData);
|
|
812
|
+
device.removeEventListener("sensorData", onDeviceSensorData);
|
|
813
|
+
resolve(deviceData);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (deviceData[sensorType].length == numberOfSamples) {
|
|
818
|
+
console.log(`finished collecting ${sensorType} data for device`);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const { [sensorType]: data } = event.message;
|
|
823
|
+
deviceData[sensorType].push(data);
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
device.addEventListener("sensorData", onDeviceSensorData);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// DATA UPLOAD
|
|
831
|
+
|
|
832
|
+
const emptySignature = Array(64).fill("0").join("");
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* @param {BS.ContinuousSensorType[]} sensorTypes
|
|
836
|
+
* @param {DeviceData} deviceData
|
|
837
|
+
*/
|
|
838
|
+
async function uploadData(sensorTypes, deviceData) {
|
|
839
|
+
const sensors = sensorTypes.flatMap((sensorType) => {
|
|
840
|
+
let names = [];
|
|
841
|
+
let units;
|
|
842
|
+
switch (sensorType) {
|
|
843
|
+
case "linearAcceleration":
|
|
844
|
+
case "gyroscope":
|
|
845
|
+
case "magnetometer":
|
|
846
|
+
names = ["x", "y", "z"].map((component) => `${sensorType}.${component}`);
|
|
847
|
+
switch (sensorType) {
|
|
848
|
+
case "linearAcceleration":
|
|
849
|
+
units = "g/s";
|
|
850
|
+
break;
|
|
851
|
+
case "gyroscope":
|
|
852
|
+
units = "deg/s";
|
|
853
|
+
break;
|
|
854
|
+
case "magnetometer":
|
|
855
|
+
units = "uT";
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
break;
|
|
859
|
+
case "pressure":
|
|
860
|
+
for (let index = 0; index < device.numberOfPressureSensors; index++) {
|
|
861
|
+
names.push(`${sensorType}.${index}`);
|
|
862
|
+
}
|
|
863
|
+
units = "pressure";
|
|
864
|
+
break;
|
|
865
|
+
default:
|
|
866
|
+
throw `uncaught sensorType ${sensorType}`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return names.map((name) => ({
|
|
870
|
+
name,
|
|
871
|
+
units,
|
|
872
|
+
}));
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
console.log("sensors", sensors);
|
|
876
|
+
|
|
877
|
+
const values = [];
|
|
878
|
+
for (let sampleIndex = 0; sampleIndex < numberOfSamples; sampleIndex++) {
|
|
879
|
+
const value = [];
|
|
880
|
+
|
|
881
|
+
sensorTypes.forEach((sensorType) => {
|
|
882
|
+
const scalar = scalars[sensorType];
|
|
883
|
+
const sensorSamples = deviceData[sensorType];
|
|
884
|
+
|
|
885
|
+
switch (sensorType) {
|
|
886
|
+
case "linearAcceleration":
|
|
887
|
+
case "gyroscope":
|
|
888
|
+
case "magnetometer":
|
|
889
|
+
["x", "y", "z"].forEach((component) => {
|
|
890
|
+
value.push(sensorSamples[sampleIndex][component] * scalar);
|
|
891
|
+
});
|
|
892
|
+
break;
|
|
893
|
+
case "pressure":
|
|
894
|
+
for (let pressureIndex = 0; pressureIndex < device.numberOfPressureSensors; pressureIndex++) {
|
|
895
|
+
value.push(sensorSamples[sampleIndex].sensors[pressureIndex].rawValue * scalar);
|
|
896
|
+
}
|
|
897
|
+
break;
|
|
898
|
+
default:
|
|
899
|
+
throw `uncaught sensorType ${sensorType}`;
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
values.push(value);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
console.log("values", values);
|
|
907
|
+
|
|
908
|
+
const data = {
|
|
909
|
+
protected: {
|
|
910
|
+
ver: "v1",
|
|
911
|
+
alg: "HS256",
|
|
912
|
+
iat: Math.floor(Date.now() / 1000),
|
|
913
|
+
},
|
|
914
|
+
signature: emptySignature,
|
|
915
|
+
payload: {
|
|
916
|
+
device_name: getDeviceId(),
|
|
917
|
+
device_type: "BrilliantSole",
|
|
918
|
+
interval_ms: samplingInterval,
|
|
919
|
+
sensors,
|
|
920
|
+
values,
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
console.log("data", data);
|
|
925
|
+
|
|
926
|
+
data.signature = await createSignature(hmacKey, data);
|
|
927
|
+
|
|
928
|
+
console.log("signature", data.signature);
|
|
929
|
+
|
|
930
|
+
const formData = new FormData();
|
|
931
|
+
formData.append("message", new Blob([JSON.stringify(data)], { type: "application/json" }), "message.json");
|
|
932
|
+
|
|
933
|
+
return new Promise((resolve, reject) => {
|
|
934
|
+
let xml = new XMLHttpRequest();
|
|
935
|
+
xml.onload = () => {
|
|
936
|
+
if (xml.status === 200) {
|
|
937
|
+
resolve(xml.responseText);
|
|
938
|
+
} else {
|
|
939
|
+
reject("Failed to upload (status code " + xml.status + "): " + xml.responseText);
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
xml.onerror = () => reject(undefined);
|
|
943
|
+
xml.open("post", ingestionApi + path);
|
|
944
|
+
xml.setRequestHeader("x-api-key", apiKey);
|
|
945
|
+
xml.setRequestHeader("x-file-name", encodeLabel(label));
|
|
946
|
+
xml.send(formData);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function encodeLabel(header) {
|
|
951
|
+
let encodedHeader;
|
|
952
|
+
try {
|
|
953
|
+
encodedHeader = encodeURIComponent(header);
|
|
954
|
+
} catch (ex) {
|
|
955
|
+
encodedHeader = header;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return encodedHeader;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const textEncoder = new TextEncoder();
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* @param {string} hmacKey
|
|
965
|
+
* @param {object} data
|
|
966
|
+
*/
|
|
967
|
+
async function createSignature(hmacKey, data) {
|
|
968
|
+
// encoder to convert string to Uint8Array
|
|
969
|
+
const key = await crypto.subtle.importKey(
|
|
970
|
+
"raw", // raw format of the key - should be Uint8Array
|
|
971
|
+
textEncoder.encode(hmacKey),
|
|
972
|
+
{
|
|
973
|
+
// algorithm details
|
|
974
|
+
name: "HMAC",
|
|
975
|
+
hash: {
|
|
976
|
+
name: "SHA-256",
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
false, // export = false
|
|
980
|
+
["sign", "verify"] // what this key can do
|
|
981
|
+
);
|
|
982
|
+
// Create signature for encoded input data
|
|
983
|
+
const signature = await crypto.subtle.sign("HMAC", key, textEncoder.encode(JSON.stringify(data)));
|
|
984
|
+
// Convert back to Hex
|
|
985
|
+
const b = new Uint8Array(signature);
|
|
986
|
+
return Array.prototype.map.call(b, (x) => ("00" + x.toString(16)).slice(-2)).join("");
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// EDGE IMPULSE CONFIG
|
|
990
|
+
|
|
991
|
+
const configLocalStorageKey = "EdgeImpulse";
|
|
992
|
+
|
|
993
|
+
let config = {
|
|
994
|
+
projectId,
|
|
995
|
+
apiKey,
|
|
996
|
+
hmacKey,
|
|
997
|
+
};
|
|
998
|
+
/** @type {object?} */
|
|
999
|
+
let loadedConfig;
|
|
1000
|
+
console.log("config", config);
|
|
1001
|
+
function loadConfigFromLocalStorage() {
|
|
1002
|
+
const configString = localStorage.getItem(configLocalStorageKey);
|
|
1003
|
+
if (!configString) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
loadedConfig = JSON.parse(configString);
|
|
1007
|
+
console.log("loaded config", loadedConfig);
|
|
1008
|
+
Object.assign(config, loadedConfig);
|
|
1009
|
+
console.log("updated config", config);
|
|
1010
|
+
window.dispatchEvent(new Event("loadConfig"));
|
|
1011
|
+
}
|
|
1012
|
+
loadConfigFromLocalStorage();
|
|
1013
|
+
|
|
1014
|
+
function saveConfigToLocalStorage() {
|
|
1015
|
+
const isConfigDifferent = !loadedConfig || Object.entries(loadedConfig).some(([key, value]) => config[key] != value);
|
|
1016
|
+
if (!isConfigDifferent) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
console.log("saving config", config);
|
|
1020
|
+
localStorage.setItem(configLocalStorageKey, JSON.stringify(config));
|
|
1021
|
+
loadedConfig = config;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
Object.keys(config).forEach((type) => {
|
|
1025
|
+
window.addEventListener(type, () => {
|
|
1026
|
+
config = {
|
|
1027
|
+
projectId,
|
|
1028
|
+
apiKey,
|
|
1029
|
+
hmacKey,
|
|
1030
|
+
};
|
|
1031
|
+
saveConfigToLocalStorage();
|
|
1032
|
+
});
|
|
1033
|
+
});
|