brilliantsole 0.0.24 → 0.0.25

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.
@@ -1 +1 @@
1
- {"version":3,"file":"brilliantsole.module.js","sources":["../node_modules/tslib/tslib.es6.js","../brilliantsole/utils/environment.ts","../brilliantsole/utils/Console.ts","../brilliantsole/utils/EventDispatcher.ts","../brilliantsole/utils/Timer.ts","../brilliantsole/utils/checksum.ts","../brilliantsole/utils/Text.ts","../brilliantsole/utils/ArrayBufferUtils.ts","../node_modules/auto-bind/index.js","../brilliantsole/FileTransferManager.ts","../brilliantsole/utils/MathUtils.ts","../brilliantsole/utils/RangeHelper.ts","../brilliantsole/utils/CenterOfPressureHelper.ts","../brilliantsole/utils/ArrayUtils.ts","../brilliantsole/sensor/PressureSensorDataManager.ts","../brilliantsole/sensor/MotionSensorDataManager.ts","../brilliantsole/sensor/BarometerSensorDataManager.ts","../brilliantsole/utils/ParseUtils.ts","../brilliantsole/sensor/SensorDataManager.ts","../brilliantsole/sensor/SensorConfigurationManager.ts","../brilliantsole/TfliteManager.ts","../brilliantsole/DeviceInformationManager.ts","../brilliantsole/InformationManager.ts","../brilliantsole/vibration/VibrationWaveformEffects.ts","../brilliantsole/vibration/VibrationManager.ts","../brilliantsole/connection/BaseConnectionManager.ts","../brilliantsole/utils/stringUtils.ts","../brilliantsole/utils/EventUtils.ts","../brilliantsole/connection/bluetooth/bluetoothUUIDs.ts","../brilliantsole/connection/bluetooth/BluetoothConnectionManager.ts","../brilliantsole/connection/bluetooth/WebBluetoothConnectionManager.ts","../brilliantsole/utils/cbor.js","../brilliantsole/utils/mcumgr.js","../brilliantsole/FirmwareManager.ts","../brilliantsole/DeviceManager.ts","../brilliantsole/Device.ts","../brilliantsole/devicePair/DevicePairPressureSensorDataManager.ts","../brilliantsole/devicePair/DevicePairSensorDataManager.ts","../brilliantsole/devicePair/DevicePair.ts","../brilliantsole/server/ServerUtils.ts","../brilliantsole/connection/ClientConnectionManager.ts","../brilliantsole/server/BaseClient.ts","../brilliantsole/server/websocket/WebSocketUtils.ts","../brilliantsole/server/websocket/WebSocketClient.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","type ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\" | \"__BRILLIANTSOLE__PROD__\";\nconst __BRILLIANTSOLE__ENVIRONMENT__: ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\";\n\n//@ts-expect-error\nconst isInProduction = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__PROD__\";\nconst isInDev = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__DEV__\";\n\n// https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts\nconst isInBrowser = typeof window !== \"undefined\" && typeof window?.document !== \"undefined\";\nconst isInNode = typeof process !== \"undefined\" && process?.versions?.node != null;\n\nconst userAgent = (isInBrowser && navigator.userAgent) || \"\";\n\nlet isBluetoothSupported = false;\nif (isInBrowser) {\n isBluetoothSupported = Boolean(navigator.bluetooth);\n} else if (isInNode) {\n isBluetoothSupported = true;\n}\n\nconst isInBluefy = isInBrowser && /Bluefy/i.test(userAgent);\nconst isInWebBLE = isInBrowser && /WebBLE/i.test(userAgent);\n\nconst isAndroid = isInBrowser && /Android/i.test(userAgent);\nconst isSafari = isInBrowser && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);\n\nconst isIOS = isInBrowser && /iPad|iPhone|iPod/i.test(userAgent);\nconst isMac = isInBrowser && /Macintosh/i.test(userAgent);\n\n// @ts-expect-error\nconst isInLensStudio = !isInBrowser && !isInNode && typeof global !== \"undefined\" && typeof Studio !== \"undefined\";\n\nexport {\n isInDev,\n isInProduction,\n isInBrowser,\n isInNode,\n isAndroid,\n isInBluefy,\n isInWebBLE,\n isSafari,\n isInLensStudio,\n isIOS,\n isMac,\n isBluetoothSupported,\n};\n","import { isInDev, isInLensStudio } from \"./environment.ts\";\n\ndeclare var Studio: any | undefined;\n\nexport type LogFunction = (...data: any[]) => void;\nexport type AssertLogFunction = (condition: boolean, ...data: any[]) => void;\n\nexport interface ConsoleLevelFlags {\n log?: boolean;\n warn?: boolean;\n error?: boolean;\n assert?: boolean;\n table?: boolean;\n}\n\ninterface ConsoleLike {\n log?: LogFunction;\n warn?: LogFunction;\n error?: LogFunction;\n assert?: AssertLogFunction;\n table?: LogFunction;\n}\n\nvar __console: ConsoleLike;\nif (isInLensStudio) {\n const log = function (...args: any[]) {\n Studio.log(args.map((value) => new String(value)).join(\",\"));\n };\n __console = {};\n __console.log = log;\n __console.warn = log.bind(__console, \"WARNING\");\n __console.error = log.bind(__console, \"ERROR\");\n} else {\n __console = console;\n}\n\n// console.assert not supported in WebBLE\nif (!__console.assert) {\n const assert: AssertLogFunction = (condition, ...data) => {\n if (!condition) {\n __console.warn!(...data);\n }\n };\n __console.assert = assert;\n}\n\n// console.table not supported in WebBLE\nif (!__console.table) {\n const table: LogFunction = (...data) => {\n __console.log!(...data);\n };\n __console.table = table;\n}\n\nfunction emptyFunction() {}\n\nconst log: LogFunction = __console.log!.bind(__console);\nconst warn: LogFunction = __console.warn!.bind(__console);\nconst error: LogFunction = __console.error!.bind(__console);\nconst table: LogFunction = __console.table!.bind(__console);\nconst assert: AssertLogFunction = __console.assert.bind(__console);\n\nclass Console {\n static #consoles: { [type: string]: Console } = {};\n\n constructor(type: string) {\n if (Console.#consoles[type]) {\n throw new Error(`\"${type}\" console already exists`);\n }\n Console.#consoles[type] = this;\n }\n\n #levelFlags: ConsoleLevelFlags = {\n log: isInDev,\n warn: isInDev,\n assert: true,\n error: true,\n table: true,\n };\n\n setLevelFlags(levelFlags: ConsoleLevelFlags) {\n Object.assign(this.#levelFlags, levelFlags);\n }\n\n /** @throws {Error} if no console with type \"type\" is found */\n static setLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n if (!this.#consoles[type]) {\n throw new Error(`no console found with type \"${type}\"`);\n }\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n\n static setAllLevelFlags(levelFlags: ConsoleLevelFlags) {\n for (const type in this.#consoles) {\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n }\n\n static create(type: string, levelFlags?: ConsoleLevelFlags): Console {\n const console = this.#consoles[type] || new Console(type);\n if (isInDev && levelFlags) {\n console.setLevelFlags(levelFlags);\n }\n return console;\n }\n\n get log() {\n return this.#levelFlags.log ? log : emptyFunction;\n }\n\n get warn() {\n return this.#levelFlags.warn ? warn : emptyFunction;\n }\n\n get error() {\n return this.#levelFlags.error ? error : emptyFunction;\n }\n\n get assert() {\n return this.#levelFlags.assert ? assert : emptyFunction;\n }\n\n get table() {\n return this.#levelFlags.table ? table : emptyFunction;\n }\n\n /** @throws {Error} if condition is not met */\n assertWithError(condition: any, message: string) {\n if (!Boolean(condition)) {\n throw new Error(message);\n }\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertTypeWithError(value: any, type: string) {\n this.assertWithError(typeof value == type, `value ${value} of type \"${typeof value}\" not of type \"${type}\"`);\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertEnumWithError(value: string, enumeration: readonly string[]) {\n this.assertWithError(enumeration.includes(value), `invalid enum \"${value}\"`);\n }\n}\n\nexport function createConsole(type: string, levelFlags?: ConsoleLevelFlags): Console {\n return Console.create(type, levelFlags);\n}\n\n/** @throws {Error} if no console with type is found */\nexport function setConsoleLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n Console.setLevelFlagsForType(type, levelFlags);\n}\n\nexport function setAllConsoleLevelFlags(levelFlags: ConsoleLevelFlags) {\n Console.setAllLevelFlags(levelFlags);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { deepEqual } from \"./ObjectUtils.ts\";\n\nconst _console = createConsole(\"EventDispatcher\", { log: false });\n\nexport type EventMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: { type: T; target: Target; message: EventMessages[T] };\n};\nexport type EventListenerMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n};\n\nexport type Event<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = EventMap<Target, EventType, EventMessages>[keyof EventMessages];\n\ntype SpecificEvent<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>,\n SpecificEventType extends EventType\n> = { type: SpecificEventType; target: Target; message: EventMessages[SpecificEventType] };\n\nexport type BoundEventListeners<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [SpecificEventType in keyof EventMessages]?: (\n // @ts-expect-error\n event: SpecificEvent<Target, EventType, EventMessages, SpecificEventType>\n ) => void;\n};\n\nclass EventDispatcher<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> {\n private listeners: {\n [T in EventType]?: {\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n once?: boolean;\n shouldRemove?: boolean;\n }[];\n } = {};\n\n constructor(private target: Target, private validEventTypes: readonly EventType[]) {\n this.addEventListener = this.addEventListener.bind(this);\n this.removeEventListener = this.removeEventListener.bind(this);\n this.removeEventListeners = this.removeEventListeners.bind(this);\n this.removeAllEventListeners = this.removeAllEventListeners.bind(this);\n this.dispatchEvent = this.dispatchEvent.bind(this);\n this.waitForEvent = this.waitForEvent.bind(this);\n }\n\n private isValidEventType(type: any): type is EventType {\n return this.validEventTypes.includes(type);\n }\n\n private updateEventListeners(type: EventType) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type]!.filter((listenerObj) => {\n if (listenerObj.shouldRemove) {\n _console.log(`removing \"${type}\" eventListener`, listenerObj);\n }\n return !listenerObj.shouldRemove;\n });\n }\n\n addEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void,\n options: { once?: boolean } = { once: false }\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n _console.log(`creating \"${type}\" listeners array`, this.listeners[type]!);\n }\n const alreadyAdded = this.listeners[type].find((listenerObject) => {\n return listenerObject.listener == listener && listenerObject.once == options.once;\n });\n if (alreadyAdded) {\n _console.log(\"already added listener\");\n return;\n }\n _console.log(`adding \"${type}\" listener`, listener, options);\n this.listeners[type]!.push({ listener, once: options.once });\n\n _console.log(`currently have ${this.listeners[type]!.length} \"${type}\" listeners`);\n }\n\n removeEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listener...`, listener);\n this.listeners[type]!.forEach((listenerObj) => {\n const isListenerToRemove = listenerObj.listener === listener;\n if (isListenerToRemove) {\n _console.log(`flagging \"${type}\" listener`, listener);\n listenerObj.shouldRemove = true;\n }\n });\n\n this.updateEventListeners(type);\n }\n\n removeEventListeners<T extends EventType>(type: T): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listeners...`);\n this.listeners[type] = [];\n }\n\n removeAllEventListeners(): void {\n _console.log(`removing listeners...`);\n this.listeners = {};\n }\n\n dispatchEvent<T extends EventType>(type: T, message: EventMessages[T]): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n this.listeners[type]!.forEach((listenerObj) => {\n if (listenerObj.shouldRemove) {\n return;\n }\n\n _console.log(`dispatching \"${type}\" listener`, listenerObj);\n listenerObj.listener({ type, target: this.target, message });\n\n if (listenerObj.once) {\n _console.log(`flagging \"${type}\" listener`, listenerObj);\n listenerObj.shouldRemove = true;\n }\n });\n this.updateEventListeners(type);\n }\n\n waitForEvent<T extends EventType>(type: T): Promise<{ type: T; target: Target; message: EventMessages[T] }> {\n return new Promise((resolve) => {\n const onceListener = (event: { type: T; target: Target; message: EventMessages[T] }) => {\n resolve(event);\n };\n\n this.addEventListener(type, onceListener, { once: true });\n });\n }\n}\n\nexport default EventDispatcher;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"Timer\", { log: false });\n\nexport async function wait(delay: number) {\n _console.log(`waiting for ${delay} ms`);\n return new Promise((resolve: Function) => {\n setTimeout(() => resolve(), delay);\n });\n}\n\nclass Timer {\n #callback!: Function;\n get callback() {\n return this.#callback;\n }\n set callback(newCallback) {\n _console.assertTypeWithError(newCallback, \"function\");\n _console.log({ newCallback });\n this.#callback = newCallback;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n #interval!: number;\n get interval() {\n return this.#interval;\n }\n set interval(newInterval) {\n _console.assertTypeWithError(newInterval, \"number\");\n _console.assertWithError(newInterval > 0, \"interval must be above 0\");\n _console.log({ newInterval });\n this.#interval = newInterval;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n constructor(callback: Function, interval: number) {\n this.interval = interval;\n this.callback = callback;\n }\n\n #intervalId: number | undefined;\n get isRunning() {\n return this.#intervalId != undefined;\n }\n\n start(immediately = false) {\n if (this.isRunning) {\n _console.log(\"interval already running\");\n return;\n }\n _console.log(\"starting interval\");\n this.#intervalId = setInterval(this.#callback, this.#interval);\n if (immediately) {\n this.#callback();\n }\n }\n stop() {\n if (!this.isRunning) {\n _console.log(\"interval already not running\");\n return;\n }\n _console.log(\"stopping interval\");\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n restart(startImmediately = false) {\n this.stop();\n this.start(startImmediately);\n }\n}\nexport default Timer;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"checksum\", { log: true });\n\n// https://github.com/googlecreativelab/tiny-motion-trainer/blob/5fceb49f018ae0c403bf9f0ccc437309c2acb507/frontend/src/tf4micro-motion-kit/modules/bleFileTransfer#L195\n\n// See http://home.thep.lu.se/~bjorn/crc/ for more information on simple CRC32 calculations.\nexport function crc32ForByte(r: number) {\n for (let j = 0; j < 8; ++j) {\n r = (r & 1 ? 0 : 0xedb88320) ^ (r >>> 1);\n }\n return r ^ 0xff000000;\n}\n\nconst tableSize = 256;\nconst crc32Table = new Uint32Array(tableSize);\nfor (let i = 0; i < tableSize; ++i) {\n crc32Table[i] = crc32ForByte(i);\n}\n\nexport function crc32(dataIterable: ArrayBuffer | number[]) {\n let dataBytes = new Uint8Array(dataIterable);\n let crc = 0;\n for (let i = 0; i < dataBytes.byteLength; ++i) {\n const crcLowByte = crc & 0x000000ff;\n const dataByte = dataBytes[i];\n const tableIndex = crcLowByte ^ dataByte;\n // The last >>> is to convert this into an unsigned 32-bit integer.\n crc = (crc32Table[tableIndex] ^ (crc >>> 8)) >>> 0;\n }\n return crc;\n}\n\n// This is a small test function for the CRC32 implementation, not normally called but left in\n// for debugging purposes. We know the expected CRC32 of [97, 98, 99, 100, 101] is 2240272485,\n// or 0x8587d865, so if anything else is output we know there's an error in the implementation.\nexport function testCrc32() {\n const testArray = [97, 98, 99, 100, 101];\n const testArrayCrc32 = crc32(testArray);\n _console.log(\"CRC32 for [97, 98, 99, 100, 101] is 0x\" + testArrayCrc32.toString(16) + \" (\" + testArrayCrc32 + \")\");\n}\n","var _TextEncoder;\nif (typeof TextEncoder == \"undefined\") {\n _TextEncoder = class {\n encode(string: string) {\n const encoding = Array.from(string).map((char) => char.charCodeAt(0));\n return Uint8Array.from(encoding);\n }\n };\n} else {\n _TextEncoder = TextEncoder;\n}\n\nvar _TextDecoder;\nif (typeof TextDecoder == \"undefined\") {\n _TextDecoder = class {\n decode(data: ArrayBuffer) {\n const byteArray = Array.from(new Uint8Array(data));\n return byteArray\n .map((value) => {\n return String.fromCharCode(value);\n })\n .join(\"\");\n }\n };\n} else {\n _TextDecoder = TextDecoder;\n}\n\nexport const textEncoder = new _TextEncoder();\nexport const textDecoder = new _TextDecoder();\n","import { createConsole } from \"./Console.ts\";\nimport { textEncoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ArrayBufferUtils\", { log: false });\n\nexport function concatenateArrayBuffers(...arrayBuffers: any[]): ArrayBuffer {\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer != undefined || arrayBuffer != null);\n arrayBuffers = arrayBuffers.map((arrayBuffer) => {\n if (typeof arrayBuffer == \"number\") {\n const number = arrayBuffer;\n return Uint8Array.from([Math.floor(number)]);\n } else if (typeof arrayBuffer == \"boolean\") {\n const boolean = arrayBuffer;\n return Uint8Array.from([boolean ? 1 : 0]);\n } else if (typeof arrayBuffer == \"string\") {\n const string = arrayBuffer;\n return stringToArrayBuffer(string);\n } else if (arrayBuffer instanceof Array) {\n const array = arrayBuffer;\n return concatenateArrayBuffers(...array);\n } else if (arrayBuffer instanceof ArrayBuffer) {\n return arrayBuffer;\n } else if (\"buffer\" in arrayBuffer && arrayBuffer.buffer instanceof ArrayBuffer) {\n const bufferContainer = arrayBuffer;\n return bufferContainer.buffer;\n } else if (arrayBuffer instanceof DataView) {\n const dataView = arrayBuffer;\n return dataView.buffer;\n } else if (typeof arrayBuffer == \"object\") {\n const object = arrayBuffer;\n return objectToArrayBuffer(object);\n } else {\n return arrayBuffer;\n }\n });\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer && \"byteLength\" in arrayBuffer);\n const length = arrayBuffers.reduce((length, arrayBuffer) => length + arrayBuffer.byteLength, 0);\n const uint8Array = new Uint8Array(length);\n let byteOffset = 0;\n arrayBuffers.forEach((arrayBuffer) => {\n uint8Array.set(new Uint8Array(arrayBuffer), byteOffset);\n byteOffset += arrayBuffer.byteLength;\n });\n return uint8Array.buffer;\n}\n\nexport function dataToArrayBuffer(data: Buffer) {\n return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n}\n\nexport function stringToArrayBuffer(string: string) {\n const encoding = textEncoder.encode(string);\n return concatenateArrayBuffers(encoding.byteLength, encoding);\n}\n\nexport function objectToArrayBuffer(object: object) {\n return stringToArrayBuffer(JSON.stringify(object));\n}\n\nexport function sliceDataView(dataView: DataView, begin: number, length?: number) {\n let end;\n if (length != undefined) {\n end = dataView.byteOffset + begin + length;\n }\n _console.log({ dataView, begin, end, length });\n return new DataView(dataView.buffer.slice(dataView.byteOffset + begin, end));\n}\n\nexport type FileLike = number[] | ArrayBuffer | DataView | URL | string | File;\n\nexport async function getFileBuffer(file: FileLike) {\n let fileBuffer;\n if (file instanceof Array) {\n fileBuffer = Uint8Array.from(file);\n } else if (file instanceof DataView) {\n fileBuffer = file.buffer;\n } else if (typeof file == \"string\" || file instanceof URL) {\n const response = await fetch(file);\n fileBuffer = await response.arrayBuffer();\n } else if (file instanceof File) {\n fileBuffer = await file.arrayBuffer();\n } else if (file instanceof ArrayBuffer) {\n fileBuffer = file;\n } else {\n throw { error: \"invalid file type\", file };\n }\n return fileBuffer;\n}\n","// Gets all non-builtin properties up the prototype chain.\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nexport default function autoBind(self, {include, exclude} = {}) {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n}\n","import { createConsole } from \"./utils/Console.ts\";\nimport { crc32 } from \"./utils/checksum.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FileTransferManager\", { log: true });\n\nexport const FileTransferMessageTypes = [\n \"maxFileLength\",\n \"getFileType\",\n \"setFileType\",\n \"getFileLength\",\n \"setFileLength\",\n \"getFileChecksum\",\n \"setFileChecksum\",\n \"setFileTransferCommand\",\n \"fileTransferStatus\",\n \"getFileBlock\",\n \"setFileBlock\",\n \"fileBytesTransferred\",\n] as const;\nexport type FileTransferMessageType = (typeof FileTransferMessageTypes)[number];\n\nexport const FileTypes = [\"tflite\"] as const;\nexport type FileType = (typeof FileTypes)[number];\n\nexport const FileTransferStatuses = [\"idle\", \"sending\", \"receiving\"] as const;\nexport type FileTransferStatus = (typeof FileTransferStatuses)[number];\n\nexport const FileTransferCommands = [\"startSend\", \"startReceive\", \"cancel\"] as const;\nexport type FileTransferCommand = (typeof FileTransferCommands)[number];\n\nexport const FileTransferDirections = [\"sending\", \"receiving\"] as const;\nexport type FileTransferDirection = (typeof FileTransferDirections)[number];\n\nexport const FileTransferEventTypes = [\n ...FileTransferMessageTypes,\n \"fileTransferProgress\",\n \"fileTransferComplete\",\n \"fileReceived\",\n] as const;\nexport type FileTransferEventType = (typeof FileTransferEventTypes)[number];\n\nexport interface FileTransferEventMessages {\n maxFileLength: { maxFileLength: number };\n getFileType: { fileType: FileType };\n getFileLength: { fileLength: number };\n getFileChecksum: { fileChecksum: number };\n fileTransferStatus: { fileTransferStatus: FileTransferStatus };\n getFileBlock: { fileTransferBlock: DataView };\n fileTransferProgress: { progress: number };\n fileTransferComplete: { direction: FileTransferDirection };\n fileReceived: { file: File | Blob };\n}\n\nexport type FileTransferEventDispatcher = EventDispatcher<Device, FileTransferEventType, FileTransferEventMessages>;\nexport type SendFileTransferMessageCallback = SendMessageCallback<FileTransferMessageType>;\n\nclass FileTransferManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendFileTransferMessageCallback;\n\n eventDispatcher!: FileTransferEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #assertValidType(type: FileType) {\n _console.assertEnumWithError(type, FileTypes);\n }\n #assertValidTypeEnum(typeEnum: number) {\n _console.assertWithError(typeEnum in FileTypes, `invalid typeEnum ${typeEnum}`);\n }\n\n #assertValidStatusEnum(statusEnum: number) {\n _console.assertWithError(statusEnum in FileTransferStatuses, `invalid statusEnum ${statusEnum}`);\n }\n #assertValidCommand(command: FileTransferCommand) {\n _console.assertEnumWithError(command, FileTransferCommands);\n }\n\n static #MaxLength = 0; // kB\n static get MaxLength() {\n return this.#MaxLength;\n }\n #maxLength = FileTransferManager.MaxLength;\n /** kB */\n get maxLength() {\n return this.#maxLength;\n }\n #parseMaxLength(dataView: DataView) {\n _console.log(\"parseFileMaxLength\", dataView);\n const maxLength = dataView.getUint32(0, true);\n _console.log(`maxLength: ${maxLength / 1024}kB`);\n this.#updateMaxLength(maxLength);\n }\n #updateMaxLength(maxLength: number) {\n _console.log({ maxLength });\n this.#maxLength = maxLength;\n this.#dispatchEvent(\"maxFileLength\", { maxFileLength: maxLength });\n }\n #assertValidLength(length: number) {\n _console.assertWithError(\n length <= this.maxLength,\n `file length ${length}kB too large - must be ${this.maxLength}kB or less`\n );\n }\n\n #type: FileType | undefined;\n get type() {\n return this.#type;\n }\n #parseType(dataView: DataView) {\n _console.log(\"parseFileType\", dataView);\n const typeEnum = dataView.getUint8(0);\n this.#assertValidTypeEnum(typeEnum);\n const type = FileTypes[typeEnum];\n this.#updateType(type);\n }\n #updateType(type: FileType) {\n _console.log({ fileTransferType: type });\n this.#type = type;\n this.#dispatchEvent(\"getFileType\", { fileType: type });\n }\n async #setType(newType: FileType, sendImmediately?: boolean) {\n this.#assertValidType(newType);\n if (this.type == newType) {\n _console.log(`redundant type assignment ${newType}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileType\");\n\n const typeEnum = FileTypes.indexOf(newType);\n this.sendMessage([{ type: \"setFileType\", data: Uint8Array.from([typeEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #length = 0;\n get length() {\n return this.#length;\n }\n #parseLength(dataView: DataView) {\n _console.log(\"parseFileLength\", dataView);\n const length = dataView.getUint32(0, true);\n\n this.#updateLength(length);\n }\n #updateLength(length: number) {\n _console.log(`length: ${length / 1024}kB`);\n this.#length = length;\n this.#dispatchEvent(\"getFileLength\", { fileLength: length });\n }\n async #setLength(newLength: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newLength, \"number\");\n this.#assertValidLength(newLength);\n if (this.length == newLength) {\n _console.log(`redundant length assignment ${newLength}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileLength\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newLength, true);\n this.sendMessage([{ type: \"setFileLength\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #checksum = 0;\n get checksum() {\n return this.#checksum;\n }\n #parseChecksum(dataView: DataView) {\n _console.log(\"checksum\", dataView);\n const checksum = dataView.getUint32(0, true);\n this.#updateChecksum(checksum);\n }\n #updateChecksum(checksum: number) {\n _console.log({ checksum });\n this.#checksum = checksum;\n this.#dispatchEvent(\"getFileChecksum\", { fileChecksum: checksum });\n }\n async #setChecksum(newChecksum: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newChecksum, \"number\");\n if (this.checksum == newChecksum) {\n _console.log(`redundant checksum assignment ${newChecksum}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileChecksum\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newChecksum, true);\n this.sendMessage([{ type: \"setFileChecksum\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n async #setCommand(command: FileTransferCommand, sendImmediately?: boolean) {\n this.#assertValidCommand(command);\n\n const promise = this.waitForEvent(\"fileTransferStatus\");\n\n const commandEnum = FileTransferCommands.indexOf(command);\n this.sendMessage(\n [{ type: \"setFileTransferCommand\", data: Uint8Array.from([commandEnum]).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #status: FileTransferStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #parseStatus(dataView: DataView) {\n _console.log(\"parseFileStatus\", dataView);\n const statusEnum = dataView.getUint8(0);\n this.#assertValidStatusEnum(statusEnum);\n const status = FileTransferStatuses[statusEnum];\n this.#updateStatus(status);\n }\n #updateStatus(status: FileTransferStatus) {\n _console.log({ status });\n this.#status = status;\n this.#dispatchEvent(\"fileTransferStatus\", { fileTransferStatus: status });\n this.#receivedBlocks.length = 0;\n }\n #assertIsIdle() {\n _console.assertWithError(this.#status == \"idle\", \"status is not idle\");\n }\n #assertIsNotIdle() {\n _console.assertWithError(this.#status != \"idle\", \"status is idle\");\n }\n\n // BLOCK\n\n #receivedBlocks: ArrayBuffer[] = [];\n\n async #parseBlock(dataView: DataView) {\n _console.log(\"parseFileBlock\", dataView);\n this.#receivedBlocks.push(dataView.buffer);\n\n const bytesReceived = this.#receivedBlocks.reduce((sum, arrayBuffer) => (sum += arrayBuffer.byteLength), 0);\n const progress = bytesReceived / this.#length;\n\n _console.log(`received ${bytesReceived} of ${this.#length} bytes (${progress * 100}%)`);\n\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n\n if (bytesReceived != this.#length) {\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, bytesReceived, true);\n\n if (this.isServerSide) {\n return;\n }\n await this.sendMessage([{ type: \"fileBytesTransferred\", data: dataView.buffer }]);\n return;\n }\n\n _console.log(\"file transfer complete\");\n\n let fileName = new Date().toLocaleString();\n switch (this.type) {\n case \"tflite\":\n fileName += \".tflite\";\n break;\n }\n\n let file: File | Blob;\n if (typeof File !== \"undefined\") {\n file = new File(this.#receivedBlocks, fileName);\n } else {\n file = new Blob(this.#receivedBlocks);\n }\n\n const arrayBuffer = await file.arrayBuffer();\n const checksum = crc32(arrayBuffer);\n _console.log({ checksum });\n\n if (checksum != this.#checksum) {\n _console.error(`wrong checksum - expected ${this.#checksum}, got ${checksum}`);\n return;\n }\n\n _console.log(\"received file\", file);\n\n this.#dispatchEvent(\"getFileBlock\", { fileTransferBlock: dataView });\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"receiving\" });\n this.#dispatchEvent(\"fileReceived\", { file });\n }\n\n parseMessage(messageType: FileTransferMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"maxFileLength\":\n this.#parseMaxLength(dataView);\n break;\n case \"getFileType\":\n case \"setFileType\":\n this.#parseType(dataView);\n break;\n case \"getFileLength\":\n case \"setFileLength\":\n this.#parseLength(dataView);\n break;\n case \"getFileChecksum\":\n case \"setFileChecksum\":\n this.#parseChecksum(dataView);\n break;\n case \"fileTransferStatus\":\n this.#parseStatus(dataView);\n break;\n case \"getFileBlock\":\n this.#parseBlock(dataView);\n break;\n case \"fileBytesTransferred\":\n this.#parseBytesTransferred(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async send(type: FileType, file: FileLike) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n const fileBuffer = await getFileBuffer(file);\n\n const promises: Promise<any>[] = [];\n\n promises.push(this.#setType(type, false));\n const fileLength = fileBuffer.byteLength;\n promises.push(this.#setLength(fileLength, false));\n const checksum = crc32(fileBuffer);\n promises.push(this.#setChecksum(checksum, false));\n promises.push(this.#setCommand(\"startSend\", false));\n\n this.sendMessage();\n\n await Promise.all(promises);\n\n await this.#send(fileBuffer);\n }\n\n #buffer?: ArrayBuffer;\n #bytesTransferred = 0;\n async #send(buffer: ArrayBuffer) {\n this.#buffer = buffer;\n this.#bytesTransferred = 0;\n return this.#sendBlock();\n }\n\n mtu!: number;\n async #sendBlock(): Promise<void> {\n if (this.status != \"sending\") {\n return;\n }\n if (!this.#buffer) {\n if (!this.isServerSide) {\n _console.error(\"no buffer defined\");\n }\n return;\n }\n\n const buffer = this.#buffer;\n let offset = this.#bytesTransferred;\n\n const slicedBuffer = buffer.slice(offset, offset + (this.mtu - 3 - 3));\n _console.log(\"slicedBuffer\", slicedBuffer);\n const bytesLeft = buffer.byteLength - offset;\n\n const progress = 1 - bytesLeft / buffer.byteLength;\n _console.log(\n `sending bytes ${offset}-${offset + slicedBuffer.byteLength} of ${buffer.byteLength} bytes (${progress * 100}%)`\n );\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n if (slicedBuffer.byteLength == 0) {\n _console.log(\"finished sending buffer\");\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"sending\" });\n } else {\n await this.sendMessage([{ type: \"setFileBlock\", data: slicedBuffer }]);\n this.#bytesTransferred = offset + slicedBuffer.byteLength;\n //return this.#sendBlock(buffer, offset + slicedBuffer.byteLength);\n }\n }\n\n async #parseBytesTransferred(dataView: DataView) {\n _console.log(\"parseBytesTransferred\", dataView);\n const bytesTransferred = dataView.getUint32(0, true);\n _console.log({ bytesTransferred });\n if (this.status != \"sending\") {\n _console.error(`not currently sending file`);\n return;\n }\n if (!this.isServerSide && this.#bytesTransferred != bytesTransferred) {\n _console.error(`bytesTransferred are not equal - got ${bytesTransferred}, expected ${this.#bytesTransferred}`);\n this.cancel();\n return;\n }\n this.#sendBlock();\n }\n\n async receive(type: FileType) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n\n await this.#setType(type);\n await this.#setCommand(\"startReceive\");\n }\n\n async cancel() {\n this.#assertIsNotIdle();\n await this.#setCommand(\"cancel\");\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n }\n}\n\nexport default FileTransferManager;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"MathUtils\", { log: true });\n\nexport function getInterpolation(value: number, min: number, max: number, span: number) {\n if (span == undefined) {\n span = max - min;\n }\n return (value - min) / span;\n}\n\nexport const Uint16Max = 2 ** 16;\n\nfunction removeLower2Bytes(number: number) {\n const lower2Bytes = number % Uint16Max;\n return number - lower2Bytes;\n}\n\nconst timestampThreshold = 60_000;\n\nexport function parseTimestamp(dataView: DataView, byteOffset: number) {\n const now = Date.now();\n const nowWithoutLower2Bytes = removeLower2Bytes(now);\n const lower2Bytes = dataView.getUint16(byteOffset, true);\n let timestamp = nowWithoutLower2Bytes + lower2Bytes;\n if (Math.abs(now - timestamp) > timestampThreshold) {\n _console.log(\"correcting timestamp delta\");\n timestamp += Uint16Max * Math.sign(now - timestamp);\n }\n return timestamp;\n}\n\nexport interface Vector2 {\n x: number;\n y: number;\n}\n\nexport interface Vector3 extends Vector2 {\n z: number;\n}\n\nexport interface Quaternion {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\nexport interface Euler {\n heading: number;\n pitch: number;\n roll: number;\n}\n","import { getInterpolation } from \"./MathUtils.ts\";\n\ninterface Range {\n min: number;\n max: number;\n span: number;\n}\n\nconst initialRange: Range = { min: Infinity, max: -Infinity, span: 0 };\n\nclass RangeHelper {\n #range: Range = Object.assign({}, initialRange);\n get min() {\n return this.#range.min;\n }\n get max() {\n return this.#range.max;\n }\n\n set min(newMin) {\n this.#range.min = newMin;\n this.#range.max = Math.max(newMin, this.#range.max);\n this.#updateSpan();\n }\n set max(newMax) {\n this.#range.max = newMax;\n this.#range.min = Math.min(newMax, this.#range.min);\n this.#updateSpan();\n }\n\n #updateSpan() {\n this.#range.span = this.#range.max - this.#range.min;\n }\n\n reset() {\n Object.assign(this.#range, initialRange);\n }\n\n update(value: number) {\n this.#range.min = Math.min(value, this.#range.min);\n this.#range.max = Math.max(value, this.#range.max);\n this.#updateSpan();\n }\n\n getNormalization(value: number, weightByRange: boolean) {\n let normalization = getInterpolation(value, this.#range.min, this.#range.max, this.#range.span);\n if (weightByRange) {\n normalization *= this.#range.span;\n }\n return normalization || 0;\n }\n\n updateAndGetNormalization(value: number, weightByRange: boolean) {\n this.update(value);\n return this.getNormalization(value, weightByRange);\n }\n}\n\nexport default RangeHelper;\n","import RangeHelper from \"./RangeHelper.ts\";\n\nimport { Vector2 } from \"./MathUtils.ts\";\n\nexport type CenterOfPressure = Vector2;\n\nexport interface CenterOfPressureRange {\n x: RangeHelper;\n y: RangeHelper;\n}\n\nclass CenterOfPressureHelper {\n #range: CenterOfPressureRange = {\n x: new RangeHelper(),\n y: new RangeHelper(),\n };\n reset() {\n this.#range.x.reset();\n this.#range.y.reset();\n }\n\n update(centerOfPressure: CenterOfPressure) {\n this.#range.x.update(centerOfPressure.x);\n this.#range.y.update(centerOfPressure.y);\n }\n getNormalization(centerOfPressure: CenterOfPressure): CenterOfPressure {\n return {\n x: this.#range.x.getNormalization(centerOfPressure.x, false),\n y: this.#range.y.getNormalization(centerOfPressure.y, false),\n };\n }\n\n updateAndGetNormalization(centerOfPressure: CenterOfPressure) {\n this.update(centerOfPressure);\n return this.getNormalization(centerOfPressure);\n }\n}\n\nexport default CenterOfPressureHelper;\n","export function createArray(arrayLength: number, objectOrCallback: ((index: number) => any) | object) {\n return new Array(arrayLength).fill(1).map((_, index) => {\n if (typeof objectOrCallback == \"function\") {\n const callback = objectOrCallback;\n return callback(index);\n } else {\n const object = objectOrCallback;\n return Object.assign({}, object);\n }\n });\n}\n\nexport function arrayWithoutDuplicates(array: any[]) {\n return array.filter((value, index) => array.indexOf(value) == index);\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport RangeHelper from \"../utils/RangeHelper.ts\";\nimport { createArray } from \"../utils/ArrayUtils.ts\";\n\nconst _console = createConsole(\"PressureDataManager\", { log: true });\n\nexport const PressureSensorTypes = [\"pressure\"] as const;\nexport type PressureSensorType = (typeof PressureSensorTypes)[number];\n\nexport const ContinuousPressureSensorTypes = PressureSensorTypes;\nexport type ContinuousPressureSensorType = (typeof ContinuousPressureSensorTypes)[number];\n\nimport { Vector2 } from \"../utils/MathUtils.ts\";\nexport type PressureSensorPosition = Vector2;\n\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\n\nexport interface PressureSensorValue {\n position: PressureSensorPosition;\n rawValue: number;\n scaledValue: number;\n normalizedValue: number;\n weightedValue: number;\n}\n\nexport interface PressureData {\n sensors: PressureSensorValue[];\n scaledSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface PressureDataEventMessages {\n pressure: { pressure: PressureData };\n}\n\nexport const DefaultNumberOfPressureSensors = 8;\n\nclass PressureSensorDataManager {\n #positions: PressureSensorPosition[] = [];\n get positions() {\n return this.#positions;\n }\n\n get numberOfSensors() {\n return this.positions.length;\n }\n\n parsePositions(dataView: DataView) {\n const positions: PressureSensorPosition[] = [];\n\n for (\n let pressureSensorIndex = 0, byteOffset = 0;\n byteOffset < dataView.byteLength;\n pressureSensorIndex++, byteOffset += 2\n ) {\n positions.push({\n x: dataView.getUint8(byteOffset) / 2 ** 8,\n y: dataView.getUint8(byteOffset + 1) / 2 ** 8,\n });\n }\n\n _console.log({ positions });\n\n this.#positions = positions;\n\n this.#sensorRangeHelpers = createArray(this.numberOfSensors, () => new RangeHelper());\n\n this.resetRange();\n }\n\n #sensorRangeHelpers!: RangeHelper[];\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetRange() {\n this.#sensorRangeHelpers.forEach((rangeHelper) => rangeHelper.reset());\n this.#centerOfPressureHelper.reset();\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure: PressureData = { sensors: [], scaledSum: 0, normalizedSum: 0 };\n for (let index = 0, byteOffset = 0; byteOffset < dataView.byteLength; index++, byteOffset += 2) {\n const rawValue = dataView.getUint16(byteOffset, true);\n const scaledValue = rawValue * scalar;\n const rangeHelper = this.#sensorRangeHelpers[index];\n const normalizedValue = rangeHelper.updateAndGetNormalization(scaledValue, true);\n const position = this.positions[index];\n pressure.sensors[index] = { rawValue, scaledValue, normalizedValue, position, weightedValue: 0 };\n\n pressure.scaledSum += scaledValue;\n pressure.normalizedSum += normalizedValue / this.numberOfSensors;\n }\n\n if (pressure.scaledSum > 0 && pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n pressure.sensors.forEach((sensor) => {\n sensor.weightedValue = sensor.scaledValue / pressure.scaledSum;\n pressure.center!.x += sensor.position.x * sensor.weightedValue;\n pressure.center!.y += sensor.position.y * sensor.weightedValue;\n });\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ pressure });\n return pressure;\n }\n}\n\nexport default PressureSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nconst _console = createConsole(\"MotionSensorDataManager\", { log: true });\n\nexport const MotionSensorTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n \"orientation\",\n \"activity\",\n \"stepCounter\",\n \"stepDetector\",\n \"deviceOrientation\",\n] as const;\nexport type MotionSensorType = (typeof MotionSensorTypes)[number];\n\nexport const ContinuousMotionTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n] as const;\nexport type ContinuousMotionType = (typeof ContinuousMotionTypes)[number];\n\nimport { Vector3, Quaternion, Euler } from \"../utils/MathUtils.ts\";\nimport { ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nexport const Vector2Size = 2 * 2;\nexport const Vector3Size = 3 * 2;\nexport const QuaternionSize = 4 * 2;\n\nexport const ActivityTypes = [\"still\", \"walking\", \"running\", \"bicycle\", \"vehicle\", \"tilting\"] as const;\nexport type ActivityType = (typeof ActivityTypes)[number];\n\nexport interface Activity {\n still: boolean;\n walking: boolean;\n running: boolean;\n bicycle: boolean;\n vehicle: boolean;\n tilting: boolean;\n}\n\nexport const DeviceOrientations = [\n \"portraitUpright\",\n \"landscapeLeft\",\n \"portraitUpsideDown\",\n \"landscapeRight\",\n \"unknown\",\n] as const;\nexport type DeviceOrientation = (typeof DeviceOrientations)[number];\n\nexport interface MotionSensorDataEventMessages {\n acceleration: { acceleration: Vector3 };\n gravity: { gravity: Vector3 };\n linearAcceleration: { linearAcceleration: Vector3 };\n gyroscope: { gyroscope: Vector3 };\n magnetometer: { magnetometer: Vector3 };\n gameRotation: { gameRotation: Quaternion };\n rotation: { rotation: Quaternion };\n orientation: { orientation: Euler };\n stepDetector: { stepDetector: Object };\n stepCounter: { stepCounter: number };\n activity: { activity: Activity };\n deviceOrientation: { deviceOrientation: DeviceOrientation };\n}\n\nexport type MotionSensorDataEventMessage = ValueOf<MotionSensorDataEventMessages>;\n\nclass MotionSensorDataManager {\n parseVector3(dataView: DataView, scalar: number): Vector3 {\n let [x, y, z] = [dataView.getInt16(0, true), dataView.getInt16(2, true), dataView.getInt16(4, true)].map(\n (value) => value * scalar\n );\n\n const vector: Vector3 = { x, y, z };\n\n _console.log({ vector });\n return vector;\n }\n\n parseQuaternion(dataView: DataView, scalar: number): Quaternion {\n let [x, y, z, w] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n dataView.getInt16(6, true),\n ].map((value) => value * scalar);\n\n const quaternion: Quaternion = { x, y, z, w };\n\n _console.log({ quaternion });\n return quaternion;\n }\n\n parseEuler(dataView: DataView, scalar: number): Euler {\n let [heading, pitch, roll] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n ].map((value) => value * scalar);\n\n pitch *= -1;\n heading *= -1;\n heading += 360;\n\n const euler: Euler = { heading, pitch, roll };\n\n _console.log({ euler });\n return euler;\n }\n\n parseStepCounter(dataView: DataView) {\n _console.log(\"parseStepCounter\", dataView);\n const stepCount = dataView.getUint32(0, true);\n _console.log({ stepCount });\n return stepCount;\n }\n\n parseActivity(dataView: DataView) {\n _console.log(\"parseActivity\", dataView);\n const activity: Partial<Activity> = {};\n\n const activityBitfield = dataView.getUint8(0);\n _console.log(\"activityBitfield\", activityBitfield.toString(2));\n ActivityTypes.forEach((activityType, index) => {\n activity[activityType] = Boolean(activityBitfield & (1 << index));\n });\n\n _console.log(\"activity\", activity);\n\n return activity as Activity;\n }\n\n parseDeviceOrientation(dataView: DataView) {\n _console.log(\"parseDeviceOrientation\", dataView);\n const index = dataView.getUint8(0);\n const deviceOrientation = DeviceOrientations[index];\n _console.assertWithError(deviceOrientation, \"undefined deviceOrientation\");\n _console.log({ deviceOrientation });\n return deviceOrientation;\n }\n}\n\nexport default MotionSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nexport const BarometerSensorTypes = [\"barometer\"] as const;\nexport type BarometerSensorType = (typeof BarometerSensorTypes)[number];\n\nexport const ContinuousBarometerSensorTypes = BarometerSensorTypes;\nexport type ContinuousBarometerSensorType = (typeof ContinuousBarometerSensorTypes)[number];\n\nexport interface BarometerSensorDataEventMessages {\n barometer: {\n barometer: number;\n //altitude: number;\n };\n}\n\nconst _console = createConsole(\"BarometerSensorDataManager\", { log: true });\n\nclass BarometerSensorDataManager {\n #calculcateAltitude(pressure: number) {\n const P0 = 101325; // Standard atmospheric pressure at sea level in Pascals\n const T0 = 288.15; // Standard temperature at sea level in Kelvin\n const L = 0.0065; // Temperature lapse rate in K/m\n const R = 8.3144598; // Universal gas constant in J/(mol·K)\n const g = 9.80665; // Acceleration due to gravity in m/s²\n const M = 0.0289644; // Molar mass of Earth's air in kg/mol\n\n const exponent = (R * L) / (g * M);\n const h = (T0 / L) * (1 - Math.pow(pressure / P0, exponent));\n\n return h;\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure = dataView.getUint32(0, true) * scalar;\n const altitude = this.#calculcateAltitude(pressure);\n _console.log({ pressure, altitude });\n return { pressure };\n }\n}\n\nexport default BarometerSensorDataManager;\n","import { sliceDataView } from \"./ArrayBufferUtils.ts\";\nimport { createConsole } from \"./Console.ts\";\nimport { textDecoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ParseUtils\", { log: true });\n\nexport function parseStringFromDataView(dataView: DataView, byteOffset: number = 0) {\n const stringLength = dataView.getUint8(byteOffset++);\n const string = textDecoder.decode(\n dataView.buffer.slice(dataView.byteOffset + byteOffset, dataView.byteOffset + byteOffset + stringLength)\n );\n byteOffset += stringLength;\n return { string, byteOffset };\n}\n\nexport function parseMessage<MessageType extends string>(\n dataView: DataView,\n messageTypes: readonly MessageType[],\n callback: (messageType: MessageType, dataView: DataView, context?: any) => void,\n context?: any,\n parseMessageLengthAsUint16: boolean = false\n) {\n let byteOffset = 0;\n while (byteOffset < dataView.byteLength) {\n const messageTypeEnum = dataView.getUint8(byteOffset++);\n _console.assertWithError(messageTypeEnum in messageTypes, `invalid messageTypeEnum ${messageTypeEnum}`);\n const messageType = messageTypes[messageTypeEnum];\n\n let messageLength: number;\n if (parseMessageLengthAsUint16) {\n messageLength = dataView.getUint16(byteOffset, true);\n byteOffset += 2;\n } else {\n messageLength = dataView.getUint8(byteOffset++);\n }\n\n _console.log({ messageTypeEnum, messageType, messageLength, dataView, byteOffset });\n\n const _dataView = sliceDataView(dataView, byteOffset, messageLength);\n _console.log({ _dataView });\n\n callback(messageType, _dataView, context);\n\n byteOffset += messageLength;\n }\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport { parseTimestamp } from \"../utils/MathUtils.ts\";\nimport PressureSensorDataManager, { PressureDataEventMessages } from \"./PressureSensorDataManager.ts\";\nimport MotionSensorDataManager, { MotionSensorDataEventMessages } from \"./MotionSensorDataManager.ts\";\nimport BarometerSensorDataManager, { BarometerSensorDataEventMessages } from \"./BarometerSensorDataManager.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport { MotionSensorTypes, ContinuousMotionTypes } from \"./MotionSensorDataManager.ts\";\nimport { PressureSensorTypes, ContinuousPressureSensorTypes } from \"./PressureSensorDataManager.ts\";\nimport { BarometerSensorTypes, ContinuousBarometerSensorTypes } from \"./BarometerSensorDataManager.ts\";\nimport Device from \"../Device.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"SensorDataManager\", { log: true });\n\nexport const SensorTypes = [...PressureSensorTypes, ...MotionSensorTypes, ...BarometerSensorTypes] as const;\nexport type SensorType = (typeof SensorTypes)[number];\n\nexport const ContinuousSensorTypes = [\n ...ContinuousPressureSensorTypes,\n ...ContinuousMotionTypes,\n ...ContinuousBarometerSensorTypes,\n] as const;\nexport type ContinuousSensorType = (typeof ContinuousSensorTypes)[number];\n\nexport const SensorDataMessageTypes = [\"getPressurePositions\", \"getSensorScalars\", \"sensorData\"] as const;\nexport type SensorDataMessageType = (typeof SensorDataMessageTypes)[number];\n\nexport const SensorDataEventTypes = [...SensorDataMessageTypes, ...SensorTypes] as const;\nexport type SensorDataEventType = (typeof SensorDataEventTypes)[number];\n\ninterface BaseSensorDataEventMessage {\n timestamp: number;\n}\n\ntype BaseSensorDataEventMessages = BarometerSensorDataEventMessages &\n MotionSensorDataEventMessages &\n PressureDataEventMessages;\ntype _SensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseSensorDataEventMessages, \"sensorType\">,\n BaseSensorDataEventMessage\n>;\nexport type SensorDataEventMessage = ValueOf<_SensorDataEventMessages>;\ninterface AnySensorDataEventMessages {\n sensorData: SensorDataEventMessage;\n}\nexport type SensorDataEventMessages = _SensorDataEventMessages & AnySensorDataEventMessages;\n\nexport type SensorDataEventDispatcher = EventDispatcher<Device, SensorDataEventType, SensorDataEventMessages>;\n\nclass SensorDataManager {\n pressureSensorDataManager = new PressureSensorDataManager();\n motionSensorDataManager = new MotionSensorDataManager();\n barometerSensorDataManager = new BarometerSensorDataManager();\n\n #scalars: Map<SensorType, number> = new Map();\n\n static AssertValidSensorType(sensorType: SensorType) {\n _console.assertEnumWithError(sensorType, SensorTypes);\n }\n static AssertValidSensorTypeEnum(sensorTypeEnum: number) {\n _console.assertTypeWithError(sensorTypeEnum, \"number\");\n _console.assertWithError(sensorTypeEnum in SensorTypes, `invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n\n eventDispatcher!: SensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n parseMessage(messageType: SensorDataMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorScalars\":\n this.parseScalars(dataView);\n break;\n case \"getPressurePositions\":\n this.pressureSensorDataManager.parsePositions(dataView);\n break;\n case \"sensorData\":\n this.parseData(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n parseScalars(dataView: DataView) {\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 5) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorScalar = dataView.getFloat32(byteOffset + 1, true);\n _console.log({ sensorType, sensorScalar });\n this.#scalars.set(sensorType, sensorScalar);\n }\n }\n\n private parseData(dataView: DataView) {\n _console.log(\"sensorData\", Array.from(new Uint8Array(dataView.buffer)));\n\n let byteOffset = 0;\n const timestamp = parseTimestamp(dataView, byteOffset);\n byteOffset += 2;\n\n const _dataView = new DataView(dataView.buffer, byteOffset);\n\n parseMessage(_dataView, SensorTypes, this.parseDataCallback.bind(this), { timestamp });\n }\n\n private parseDataCallback(sensorType: SensorType, dataView: DataView, { timestamp }: { timestamp: number }) {\n const scalar = this.#scalars.get(sensorType) || 1;\n\n let sensorData = null;\n switch (sensorType) {\n case \"pressure\":\n sensorData = this.pressureSensorDataManager.parseData(dataView, scalar);\n break;\n case \"acceleration\":\n case \"gravity\":\n case \"linearAcceleration\":\n case \"gyroscope\":\n case \"magnetometer\":\n sensorData = this.motionSensorDataManager.parseVector3(dataView, scalar);\n break;\n case \"gameRotation\":\n case \"rotation\":\n sensorData = this.motionSensorDataManager.parseQuaternion(dataView, scalar);\n break;\n case \"orientation\":\n sensorData = this.motionSensorDataManager.parseEuler(dataView, scalar);\n break;\n case \"stepCounter\":\n sensorData = this.motionSensorDataManager.parseStepCounter(dataView);\n break;\n case \"stepDetector\":\n sensorData = {};\n break;\n case \"activity\":\n sensorData = this.motionSensorDataManager.parseActivity(dataView);\n break;\n case \"deviceOrientation\":\n sensorData = this.motionSensorDataManager.parseDeviceOrientation(dataView);\n break;\n case \"barometer\":\n sensorData = this.barometerSensorDataManager.parseData(dataView, scalar);\n break;\n default:\n _console.error(`uncaught sensorType \"${sensorType}\"`);\n }\n\n _console.assertWithError(sensorData != null, `no sensorData defined for sensorType \"${sensorType}\"`);\n\n _console.log({ sensorType, sensorData });\n // @ts-expect-error\n this.dispatchEvent(sensorType, { sensorType, [sensorType]: sensorData, timestamp });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, [sensorType]: sensorData, timestamp });\n }\n}\n\nexport default SensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport SensorDataManager, { SensorTypes, SensorType } from \"./SensorDataManager.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport Device, { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"../../node_modules/auto-bind/index.js\";\n\nconst _console = createConsole(\"SensorConfigurationManager\", { log: true });\n\nexport type SensorConfiguration = { [sensorType in SensorType]?: number };\n\nexport const MaxSensorRate = 2 ** 16 - 1;\nexport const SensorRateStep = 5;\n\nexport const SensorConfigurationMessageTypes = [\"getSensorConfiguration\", \"setSensorConfiguration\"] as const;\nexport type SensorConfigurationMessageType = (typeof SensorConfigurationMessageTypes)[number];\n\nexport const SensorConfigurationEventTypes = SensorConfigurationMessageTypes;\nexport type SensorConfigurationEventType = (typeof SensorConfigurationEventTypes)[number];\n\nexport interface SensorConfigurationEventMessages {\n getSensorConfiguration: { sensorConfiguration: SensorConfiguration };\n}\n\nexport type SensorConfigurationEventDispatcher = EventDispatcher<\n Device,\n SensorConfigurationEventType,\n SensorConfigurationEventMessages\n>;\n\nexport type SendSensorConfigurationMessageCallback = SendMessageCallback<SensorConfigurationMessageType>;\n\nclass SensorConfigurationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendSensorConfigurationMessageCallback;\n\n eventDispatcher!: SensorConfigurationEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #availableSensorTypes!: SensorType[];\n #assertAvailableSensorType(sensorType: SensorType) {\n _console.assertWithError(this.#availableSensorTypes, \"must get initial sensorConfiguration\");\n const isSensorTypeAvailable = this.#availableSensorTypes?.includes(sensorType);\n _console.assert(isSensorTypeAvailable, `unavailable sensor type \"${sensorType}\"`);\n return isSensorTypeAvailable;\n }\n\n #configuration!: SensorConfiguration;\n get configuration() {\n return this.#configuration;\n }\n\n #updateConfiguration(updatedConfiguration: SensorConfiguration) {\n this.#configuration = updatedConfiguration;\n _console.log({ updatedConfiguration: this.#configuration });\n this.#dispatchEvent(\"getSensorConfiguration\", { sensorConfiguration: this.configuration });\n }\n\n #isRedundant(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n return sensorTypes.every((sensorType) => {\n return this.configuration[sensorType] == sensorConfiguration[sensorType];\n });\n }\n\n async setConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n if (clearRest) {\n newSensorConfiguration = Object.assign({ ...this.zeroSensorConfiguration }, newSensorConfiguration);\n }\n _console.log({ newSensorConfiguration });\n if (this.#isRedundant(newSensorConfiguration)) {\n _console.log(\"redundant sensor configuration\");\n return;\n }\n const setSensorConfigurationData = this.#createData(newSensorConfiguration);\n _console.log({ setSensorConfigurationData });\n\n const promise = this.waitForEvent(\"getSensorConfiguration\");\n this.sendMessage([{ type: \"setSensorConfiguration\", data: setSensorConfigurationData.buffer }]);\n await promise;\n }\n\n #parse(dataView: DataView) {\n const parsedSensorConfiguration: SensorConfiguration = {};\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 3) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorRate = dataView.getUint16(byteOffset + 1, true);\n _console.log({ sensorType, sensorRate });\n parsedSensorConfiguration[sensorType] = sensorRate;\n }\n _console.log({ parsedSensorConfiguration });\n this.#availableSensorTypes = Object.keys(parsedSensorConfiguration) as SensorType[];\n return parsedSensorConfiguration;\n }\n\n static #AssertValidSensorRate(sensorRate: number) {\n _console.assertTypeWithError(sensorRate, \"number\");\n _console.assertWithError(sensorRate >= 0, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate < MaxSensorRate, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate % SensorRateStep == 0, `sensorRate must be multiple of ${SensorRateStep}`);\n }\n\n #assertValidSensorRate(sensorRate: number) {\n SensorConfigurationManager.#AssertValidSensorRate(sensorRate);\n }\n\n #createData(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n sensorTypes = sensorTypes.filter((sensorType) => this.#assertAvailableSensorType(sensorType));\n\n const dataView = new DataView(new ArrayBuffer(sensorTypes.length * 3));\n sensorTypes.forEach((sensorType, index) => {\n SensorDataManager.AssertValidSensorType(sensorType);\n const sensorTypeEnum = SensorTypes.indexOf(sensorType);\n dataView.setUint8(index * 3, sensorTypeEnum);\n\n const sensorRate = sensorConfiguration[sensorType]!;\n this.#assertValidSensorRate(sensorRate);\n dataView.setUint16(index * 3 + 1, sensorRate, true);\n });\n _console.log({ sensorConfigurationData: dataView });\n return dataView;\n }\n\n // ZERO\n static #ZeroSensorConfiguration: SensorConfiguration = {};\n static get ZeroSensorConfiguration() {\n return this.#ZeroSensorConfiguration;\n }\n static {\n SensorTypes.forEach((sensorType) => {\n this.#ZeroSensorConfiguration[sensorType] = 0;\n });\n }\n get zeroSensorConfiguration() {\n const zeroSensorConfiguration: SensorConfiguration = {};\n SensorTypes.forEach((sensorType) => {\n zeroSensorConfiguration[sensorType] = 0;\n });\n return zeroSensorConfiguration;\n }\n async clearSensorConfiguration() {\n return this.setConfiguration(this.zeroSensorConfiguration);\n }\n\n // MESSAGE\n parseMessage(messageType: SensorConfigurationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorConfiguration\":\n case \"setSensorConfiguration\":\n const newSensorConfiguration = this.#parse(dataView);\n this.#updateConfiguration(newSensorConfiguration);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default SensorConfigurationManager;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport SensorDataManager, { SensorTypes } from \"./sensor/SensorDataManager.ts\";\nimport { arrayWithoutDuplicates } from \"./utils/ArrayUtils.ts\";\nimport { SensorRateStep } from \"./sensor/SensorConfigurationManager.ts\";\nimport { parseTimestamp } from \"./utils/MathUtils.ts\";\nimport { SensorType } from \"./sensor/SensorDataManager.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"TfliteManager\", { log: true });\n\nexport const TfliteMessageTypes = [\n \"getTfliteName\",\n \"setTfliteName\",\n \"getTfliteTask\",\n \"setTfliteTask\",\n \"getTfliteSampleRate\",\n \"setTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"setTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"setTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"setTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n \"setTfliteInferencingEnabled\",\n \"tfliteInference\",\n] as const;\nexport type TfliteMessageType = (typeof TfliteMessageTypes)[number];\n\nexport const TfliteEventTypes = TfliteMessageTypes;\nexport type TfliteEventType = (typeof TfliteEventTypes)[number];\n\nexport const TfliteTasks = [\"classification\", \"regression\"] as const;\nexport type TfliteTask = (typeof TfliteTasks)[number];\n\nexport interface TfliteEventMessages {\n getTfliteName: { tfliteName: string };\n getTfliteTask: { tfliteTask: TfliteTask };\n getTfliteSampleRate: { tfliteSampleRate: number };\n getTfliteSensorTypes: { tfliteSensorTypes: SensorType[] };\n tfliteIsReady: { tfliteIsReady: boolean };\n getTfliteCaptureDelay: { tfliteCaptureDelay: number };\n getTfliteThreshold: { tfliteThreshold: number };\n getTfliteInferencingEnabled: { tfliteInferencingEnabled: boolean };\n tfliteInference: { tfliteInference: TfliteInference };\n}\n\nexport interface TfliteInference {\n timestamp: number;\n values: number[];\n maxValue?: number;\n maxIndex?: number;\n}\n\nexport type TfliteEventDispatcher = EventDispatcher<Device, TfliteEventType, TfliteEventMessages>;\nexport type SendTfliteMessageCallback = SendMessageCallback<TfliteMessageType>;\n\nexport const TfliteSensorTypes: SensorType[] = [\"pressure\", \"linearAcceleration\", \"gyroscope\", \"magnetometer\"] as const;\nexport type TfliteSensorType = (typeof TfliteSensorTypes)[number];\n\nclass TfliteManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendTfliteMessageCallback;\n\n #assertValidTask(task: TfliteTask) {\n _console.assertEnumWithError(task, TfliteTasks);\n }\n #assertValidTaskEnum(taskEnum: number) {\n _console.assertWithError(taskEnum in TfliteTasks, `invalid taskEnum ${taskEnum}`);\n }\n\n eventDispatcher!: TfliteEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #name!: string;\n get name() {\n return this.#name;\n }\n #parseName(dataView: DataView) {\n _console.log(\"parseName\", dataView);\n const name = textDecoder.decode(dataView.buffer);\n this.#updateName(name);\n }\n #updateName(name: string) {\n _console.log({ name });\n this.#name = name;\n this.#dispatchEvent(\"getTfliteName\", { tfliteName: name });\n }\n async setName(newName: string, sendImmediately?: boolean) {\n _console.assertTypeWithError(newName, \"string\");\n if (this.name == newName) {\n _console.log(`redundant name assignment ${newName}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteName\");\n\n const setNameData = textEncoder.encode(newName);\n this.sendMessage([{ type: \"setTfliteName\", data: setNameData.buffer }], sendImmediately);\n\n await promise;\n }\n\n #task!: TfliteTask;\n get task() {\n return this.#task;\n }\n #parseTask(dataView: DataView) {\n _console.log(\"parseTask\", dataView);\n const taskEnum = dataView.getUint8(0);\n this.#assertValidTaskEnum(taskEnum);\n const task = TfliteTasks[taskEnum];\n this.#updateTask(task);\n }\n #updateTask(task: TfliteTask) {\n _console.log({ task });\n this.#task = task;\n this.#dispatchEvent(\"getTfliteTask\", { tfliteTask: task });\n }\n async setTask(newTask: TfliteTask, sendImmediately?: boolean) {\n this.#assertValidTask(newTask);\n if (this.task == newTask) {\n _console.log(`redundant task assignment ${newTask}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteTask\");\n\n const taskEnum = TfliteTasks.indexOf(newTask);\n this.sendMessage([{ type: \"setTfliteTask\", data: Uint8Array.from([taskEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #sampleRate!: number;\n get sampleRate() {\n return this.#sampleRate;\n }\n #parseSampleRate(dataView: DataView) {\n _console.log(\"parseSampleRate\", dataView);\n const sampleRate = dataView.getUint16(0, true);\n this.#updateSampleRate(sampleRate);\n }\n #updateSampleRate(sampleRate: number) {\n _console.log({ sampleRate });\n this.#sampleRate = sampleRate;\n this.#dispatchEvent(\"getTfliteSampleRate\", { tfliteSampleRate: sampleRate });\n }\n async setSampleRate(newSampleRate: number, sendImmediately?: boolean) {\n _console.assertTypeWithError(newSampleRate, \"number\");\n newSampleRate -= newSampleRate % SensorRateStep;\n _console.assertWithError(\n newSampleRate >= SensorRateStep,\n `sampleRate must be multiple of ${SensorRateStep} greater than 0 (got ${newSampleRate})`\n );\n if (this.#sampleRate == newSampleRate) {\n _console.log(`redundant sampleRate assignment ${newSampleRate}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteSampleRate\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newSampleRate, true);\n this.sendMessage([{ type: \"setTfliteSampleRate\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n static AssertValidSensorType(sensorType: SensorType) {\n SensorDataManager.AssertValidSensorType(sensorType);\n _console.assertWithError(TfliteSensorTypes.includes(sensorType), `invalid tflite sensorType \"${sensorType}\"`);\n }\n\n #sensorTypes: SensorType[] = [];\n get sensorTypes() {\n return this.#sensorTypes.slice();\n }\n #parseSensorTypes(dataView: DataView) {\n _console.log(\"parseSensorTypes\", dataView);\n const sensorTypes: SensorType[] = [];\n for (let index = 0; index < dataView.byteLength; index++) {\n const sensorTypeEnum = dataView.getUint8(index);\n const sensorType = SensorTypes[sensorTypeEnum];\n if (sensorType) {\n sensorTypes.push(sensorType);\n } else {\n _console.error(`invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n }\n this.#updateSensorTypes(sensorTypes);\n }\n #updateSensorTypes(sensorTypes: SensorType[]) {\n _console.log({ sensorTypes });\n this.#sensorTypes = sensorTypes;\n this.#dispatchEvent(\"getTfliteSensorTypes\", { tfliteSensorTypes: sensorTypes });\n }\n async setSensorTypes(newSensorTypes: SensorType[], sendImmediately?: boolean) {\n newSensorTypes.forEach((sensorType) => {\n TfliteManager.AssertValidSensorType(sensorType);\n });\n\n const promise = this.waitForEvent(\"getTfliteSensorTypes\");\n\n newSensorTypes = arrayWithoutDuplicates(newSensorTypes);\n const newSensorTypeEnums = newSensorTypes.map((sensorType) => SensorTypes.indexOf(sensorType)).sort();\n _console.log(newSensorTypes, newSensorTypeEnums);\n this.sendMessage(\n [{ type: \"setTfliteSensorTypes\", data: Uint8Array.from(newSensorTypeEnums).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #isReady!: boolean;\n get isReady() {\n return this.#isReady;\n }\n #parseIsReady(dataView: DataView) {\n _console.log(\"parseIsReady\", dataView);\n const isReady = Boolean(dataView.getUint8(0));\n this.#updateIsReady(isReady);\n }\n #updateIsReady(isReady: boolean) {\n _console.log({ isReady });\n this.#isReady = isReady;\n this.#dispatchEvent(\"tfliteIsReady\", { tfliteIsReady: isReady });\n }\n #assertIsReady() {\n _console.assertWithError(this.isReady, `tflite is not ready`);\n }\n\n #captureDelay!: number;\n get captureDelay() {\n return this.#captureDelay;\n }\n #parseCaptureDelay(dataView: DataView) {\n _console.log(\"parseCaptureDelay\", dataView);\n const captureDelay = dataView.getUint16(0, true);\n this.#updateCaptueDelay(captureDelay);\n }\n #updateCaptueDelay(captureDelay: number) {\n _console.log({ captureDelay });\n this.#captureDelay = captureDelay;\n this.#dispatchEvent(\"getTfliteCaptureDelay\", { tfliteCaptureDelay: captureDelay });\n }\n async setCaptureDelay(newCaptureDelay: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newCaptureDelay, \"number\");\n if (this.#captureDelay == newCaptureDelay) {\n _console.log(`redundant captureDelay assignment ${newCaptureDelay}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteCaptureDelay\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newCaptureDelay, true);\n this.sendMessage([{ type: \"setTfliteCaptureDelay\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #threshold!: number;\n get threshold() {\n return this.#threshold;\n }\n #parseThreshold(dataView: DataView) {\n _console.log(\"parseThreshold\", dataView);\n const threshold = dataView.getFloat32(0, true);\n this.#updateThreshold(threshold);\n }\n #updateThreshold(threshold: number) {\n _console.log({ threshold });\n this.#threshold = threshold;\n this.#dispatchEvent(\"getTfliteThreshold\", { tfliteThreshold: threshold });\n }\n async setThreshold(newThreshold: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newThreshold, \"number\");\n _console.assertWithError(newThreshold >= 0, `threshold must be positive (got ${newThreshold})`);\n if (this.#threshold == newThreshold) {\n _console.log(`redundant threshold assignment ${newThreshold}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteThreshold\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setFloat32(0, newThreshold, true);\n this.sendMessage([{ type: \"setTfliteThreshold\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #inferencingEnabled!: boolean;\n get inferencingEnabled() {\n return this.#inferencingEnabled;\n }\n #parseInferencingEnabled(dataView: DataView) {\n _console.log(\"parseInferencingEnabled\", dataView);\n const inferencingEnabled = Boolean(dataView.getUint8(0));\n this.#updateInferencingEnabled(inferencingEnabled);\n }\n #updateInferencingEnabled(inferencingEnabled: boolean) {\n _console.log({ inferencingEnabled });\n this.#inferencingEnabled = inferencingEnabled;\n this.#dispatchEvent(\"getTfliteInferencingEnabled\", { tfliteInferencingEnabled: inferencingEnabled });\n }\n async setInferencingEnabled(newInferencingEnabled: boolean, sendImmediately: boolean = true) {\n _console.assertTypeWithError(newInferencingEnabled, \"boolean\");\n if (!newInferencingEnabled && !this.isReady) {\n return;\n }\n this.#assertIsReady();\n if (this.#inferencingEnabled == newInferencingEnabled) {\n _console.log(`redundant inferencingEnabled assignment ${newInferencingEnabled}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteInferencingEnabled\");\n\n this.sendMessage(\n [\n {\n type: \"setTfliteInferencingEnabled\",\n data: Uint8Array.from([Number(newInferencingEnabled)]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n async toggleInferencingEnabled() {\n return this.setInferencingEnabled(!this.inferencingEnabled);\n }\n\n async enableInferencing() {\n if (this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(true);\n }\n async disableInferencing() {\n if (!this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(false);\n }\n\n #parseInference(dataView: DataView) {\n _console.log(\"parseInference\", dataView);\n\n const timestamp = parseTimestamp(dataView, 0);\n _console.log({ timestamp });\n\n const values: number[] = [];\n for (let index = 0, byteOffset = 2; byteOffset < dataView.byteLength; index++, byteOffset += 4) {\n const value = dataView.getFloat32(byteOffset, true);\n values.push(value);\n }\n _console.log(\"values\", values);\n\n const inference: TfliteInference = {\n timestamp,\n values,\n };\n\n if (this.task == \"classification\") {\n let maxValue = 0;\n let maxIndex = 0;\n values.forEach((value, index) => {\n if (value > maxValue) {\n maxValue = value;\n maxIndex = index;\n }\n });\n _console.log({ maxIndex, maxValue });\n inference.maxIndex = maxIndex;\n inference.maxValue = maxValue;\n }\n\n this.#dispatchEvent(\"tfliteInference\", { tfliteInference: inference });\n }\n\n parseMessage(messageType: TfliteMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getTfliteName\":\n case \"setTfliteName\":\n this.#parseName(dataView);\n break;\n case \"getTfliteTask\":\n case \"setTfliteTask\":\n this.#parseTask(dataView);\n break;\n case \"getTfliteSampleRate\":\n case \"setTfliteSampleRate\":\n this.#parseSampleRate(dataView);\n break;\n case \"getTfliteSensorTypes\":\n case \"setTfliteSensorTypes\":\n this.#parseSensorTypes(dataView);\n break;\n case \"tfliteIsReady\":\n this.#parseIsReady(dataView);\n break;\n case \"getTfliteCaptureDelay\":\n case \"setTfliteCaptureDelay\":\n this.#parseCaptureDelay(dataView);\n break;\n case \"getTfliteThreshold\":\n case \"setTfliteThreshold\":\n this.#parseThreshold(dataView);\n break;\n case \"getTfliteInferencingEnabled\":\n case \"setTfliteInferencingEnabled\":\n this.#parseInferencingEnabled(dataView);\n break;\n case \"tfliteInference\":\n this.#parseInference(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default TfliteManager;\n","import Device from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder } from \"./utils/Text.ts\";\n\nconst _console = createConsole(\"DeviceInformationManager\", { log: true });\n\nexport interface PnpId {\n source: \"Bluetooth\" | \"USB\";\n vendorId: number;\n productId: number;\n productVersion: number;\n}\n\nexport interface DeviceInformation {\n manufacturerName: string;\n modelNumber: string;\n softwareRevision: string;\n hardwareRevision: string;\n firmwareRevision: string;\n pnpId: PnpId;\n serialNumber: string;\n}\n\nexport const DeviceInformationMessageTypes = [\n \"manufacturerName\",\n \"modelNumber\",\n \"softwareRevision\",\n \"hardwareRevision\",\n \"firmwareRevision\",\n \"pnpId\",\n \"serialNumber\",\n] as const;\nexport type DeviceInformationMessageType = (typeof DeviceInformationMessageTypes)[number];\n\nexport const DeviceInformationEventTypes = [...DeviceInformationMessageTypes, \"deviceInformation\"] as const;\nexport type DeviceInformationEventType = (typeof DeviceInformationEventTypes)[number];\n\nexport interface DeviceInformationEventMessages {\n manufacturerName: { manufacturerName: string };\n modelNumber: { modelNumber: string };\n softwareRevision: { softwareRevision: string };\n hardwareRevision: { hardwareRevision: string };\n firmwareRevision: { firmwareRevision: string };\n pnpId: { pnpId: PnpId };\n serialNumber: { serialNumber: string };\n deviceInformation: { deviceInformation: DeviceInformation };\n}\n\nexport type DeviceInformationEventDispatcher = EventDispatcher<\n Device,\n DeviceInformationEventType,\n DeviceInformationEventMessages\n>;\n\nclass DeviceInformationManager {\n eventDispatcher!: DeviceInformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #information: Partial<DeviceInformation> = {};\n get information() {\n return this.#information as DeviceInformation;\n }\n clear() {\n this.#information = {};\n }\n get #isComplete() {\n return DeviceInformationMessageTypes.every((key) => key in this.#information);\n }\n\n #update(partialDeviceInformation: Partial<DeviceInformation>) {\n _console.log({ partialDeviceInformation });\n const deviceInformationNames = Object.keys(partialDeviceInformation) as (keyof DeviceInformation)[];\n deviceInformationNames.forEach((deviceInformationName) => {\n // @ts-expect-error\n this.#dispatchEvent(deviceInformationName, {\n [deviceInformationName]: partialDeviceInformation[deviceInformationName],\n });\n });\n\n Object.assign(this.#information, partialDeviceInformation);\n _console.log({ deviceInformation: this.#information });\n if (this.#isComplete) {\n _console.log(\"completed deviceInformation\");\n this.#dispatchEvent(\"deviceInformation\", { deviceInformation: this.information });\n }\n }\n\n parseMessage(messageType: DeviceInformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"manufacturerName\":\n const manufacturerName = textDecoder.decode(dataView.buffer);\n _console.log({ manufacturerName });\n this.#update({ manufacturerName });\n break;\n case \"modelNumber\":\n const modelNumber = textDecoder.decode(dataView.buffer);\n _console.log({ modelNumber });\n this.#update({ modelNumber });\n break;\n case \"softwareRevision\":\n const softwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ softwareRevision });\n this.#update({ softwareRevision });\n break;\n case \"hardwareRevision\":\n const hardwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ hardwareRevision });\n this.#update({ hardwareRevision });\n break;\n case \"firmwareRevision\":\n const firmwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ firmwareRevision });\n this.#update({ firmwareRevision });\n break;\n case \"pnpId\":\n const pnpId: PnpId = {\n source: dataView.getUint8(0) === 1 ? \"Bluetooth\" : \"USB\",\n productId: dataView.getUint16(3, true),\n productVersion: dataView.getUint16(5, true),\n vendorId: 0,\n };\n if (pnpId.source == \"Bluetooth\") {\n pnpId.vendorId = dataView.getUint16(1, true);\n } else {\n // no need to implement\n }\n _console.log({ pnpId });\n this.#update({ pnpId });\n break;\n case \"serialNumber\":\n const serialNumber = textDecoder.decode(dataView.buffer);\n _console.log({ serialNumber });\n // will only be used for node\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default DeviceInformationManager;\n","import Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { Uint16Max } from \"./utils/MathUtils.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"InformationManager\", { log: true });\n\nexport const DeviceTypes = [\"leftInsole\", \"rightInsole\"] as const;\nexport type DeviceType = (typeof DeviceTypes)[number];\n\nexport const InsoleSides = [\"left\", \"right\"] as const;\nexport type InsoleSide = (typeof InsoleSides)[number];\n\nexport const MinNameLength = 2;\nexport const MaxNameLength = 30;\n\nexport const InformationMessageTypes = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getMtu\",\n \"getId\",\n \"getName\",\n \"setName\",\n \"getType\",\n \"setType\",\n \"getCurrentTime\",\n \"setCurrentTime\",\n] as const;\nexport type InformationMessageType = (typeof InformationMessageTypes)[number];\n\nexport const InformationEventTypes = InformationMessageTypes;\nexport type InformationEventType = (typeof InformationEventTypes)[number];\n\nexport interface InformationEventMessages {\n isCharging: { isCharging: boolean };\n getBatteryCurrent: { batteryCurrent: number };\n getMtu: { mtu: number };\n getId: { id: string };\n getName: { name: string };\n getType: { type: DeviceType };\n getCurrentTime: { currentTime: number };\n}\n\nexport type InformationEventDispatcher = EventDispatcher<Device, InformationEventType, InformationEventMessages>;\nexport type SendInformationMessageCallback = SendMessageCallback<InformationMessageType>;\n\nclass InformationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendInformationMessageCallback;\n\n eventDispatcher!: InformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #isCharging = false;\n get isCharging() {\n return this.#isCharging;\n }\n #updateIsCharging(updatedIsCharging: boolean) {\n _console.assertTypeWithError(updatedIsCharging, \"boolean\");\n this.#isCharging = updatedIsCharging;\n _console.log({ isCharging: this.#isCharging });\n this.#dispatchEvent(\"isCharging\", { isCharging: this.#isCharging });\n }\n\n #batteryCurrent!: number;\n get batteryCurrent() {\n return this.#batteryCurrent;\n }\n async getBatteryCurrent() {\n _console.log(\"getting battery current...\");\n const promise = this.waitForEvent(\"getBatteryCurrent\");\n this.sendMessage([{ type: \"getBatteryCurrent\" }]);\n await promise;\n }\n #updateBatteryCurrent(updatedBatteryCurrent: number) {\n _console.assertTypeWithError(updatedBatteryCurrent, \"number\");\n this.#batteryCurrent = updatedBatteryCurrent;\n _console.log({ batteryCurrent: this.#batteryCurrent });\n this.#dispatchEvent(\"getBatteryCurrent\", { batteryCurrent: this.#batteryCurrent });\n }\n\n #id!: string;\n get id() {\n return this.#id;\n }\n #updateId(updatedId: string) {\n _console.assertTypeWithError(updatedId, \"string\");\n this.#id = updatedId;\n _console.log({ id: this.#id });\n this.#dispatchEvent(\"getId\", { id: this.#id });\n }\n\n #name = \"\";\n get name() {\n return this.#name;\n }\n\n updateName(updatedName: string) {\n _console.assertTypeWithError(updatedName, \"string\");\n this.#name = updatedName;\n _console.log({ updatedName: this.#name });\n this.#dispatchEvent(\"getName\", { name: this.#name });\n }\n async setName(newName: string) {\n _console.assertTypeWithError(newName, \"string\");\n _console.assertWithError(\n newName.length >= MinNameLength,\n `name must be greater than ${MinNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n _console.assertWithError(\n newName.length < MaxNameLength,\n `name must be less than ${MaxNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n const setNameData = textEncoder.encode(newName);\n _console.log({ setNameData });\n\n const promise = this.waitForEvent(\"getName\");\n this.sendMessage([{ type: \"setName\", data: setNameData.buffer }]);\n await promise;\n }\n\n // TYPE\n #type!: DeviceType;\n get type() {\n return this.#type;\n }\n get typeEnum() {\n return DeviceTypes.indexOf(this.type);\n }\n #assertValidDeviceType(type: DeviceType) {\n _console.assertEnumWithError(type, DeviceTypes);\n }\n #assertValidDeviceTypeEnum(typeEnum: number) {\n _console.assertTypeWithError(typeEnum, \"number\");\n _console.assertWithError(typeEnum in DeviceTypes, `invalid typeEnum ${typeEnum}`);\n }\n updateType(updatedType: DeviceType) {\n this.#assertValidDeviceType(updatedType);\n if (updatedType == this.type) {\n _console.log(\"redundant type assignment\");\n return;\n }\n this.#type = updatedType;\n _console.log({ updatedType: this.#type });\n\n this.#dispatchEvent(\"getType\", { type: this.#type });\n }\n async #setTypeEnum(newTypeEnum: number) {\n this.#assertValidDeviceTypeEnum(newTypeEnum);\n const setTypeData = Uint8Array.from([newTypeEnum]);\n _console.log({ setTypeData });\n const promise = this.waitForEvent(\"getType\");\n this.sendMessage([{ type: \"setType\", data: setTypeData.buffer }]);\n await promise;\n }\n async setType(newType: DeviceType) {\n this.#assertValidDeviceType(newType);\n const newTypeEnum = DeviceTypes.indexOf(newType);\n this.#setTypeEnum(newTypeEnum);\n }\n\n get isInsole() {\n switch (this.type) {\n case \"leftInsole\":\n case \"rightInsole\":\n return true;\n default:\n // for future non-insole device types\n return false;\n }\n }\n\n get insoleSide(): InsoleSide {\n switch (this.type) {\n case \"leftInsole\":\n return \"left\";\n case \"rightInsole\":\n return \"right\";\n }\n }\n\n #mtu = 0;\n get mtu() {\n return this.#mtu;\n }\n #updateMtu(newMtu: number) {\n _console.assertTypeWithError(newMtu, \"number\");\n if (this.#mtu == newMtu) {\n _console.log(\"redundant mtu assignment\", newMtu);\n return;\n }\n this.#mtu = newMtu;\n\n this.#dispatchEvent(\"getMtu\", { mtu: this.#mtu });\n }\n\n #isCurrentTimeSet = false;\n get isCurrentTimeSet() {\n return this.#isCurrentTimeSet;\n }\n\n #onCurrentTime(currentTime: number) {\n _console.log({ currentTime });\n this.#isCurrentTimeSet = currentTime != 0 || Math.abs(Date.now() - currentTime) < Uint16Max;\n if (!this.#isCurrentTimeSet) {\n this.#setCurrentTime(false);\n }\n }\n async #setCurrentTime(sendImmediately?: boolean) {\n _console.log(\"setting current time...\");\n const dataView = new DataView(new ArrayBuffer(8));\n dataView.setBigUint64(0, BigInt(Date.now()), true);\n const promise = this.waitForEvent(\"getCurrentTime\");\n this.sendMessage([{ type: \"setCurrentTime\", data: dataView.buffer }], sendImmediately);\n await promise;\n }\n\n // MESSAGE\n parseMessage(messageType: InformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"isCharging\":\n const isCharging = Boolean(dataView.getUint8(0));\n _console.log({ isCharging });\n this.#updateIsCharging(isCharging);\n break;\n case \"getBatteryCurrent\":\n const batteryCurrent = dataView.getFloat32(0, true);\n _console.log({ batteryCurrent });\n this.#updateBatteryCurrent(batteryCurrent);\n break;\n case \"getId\":\n const id = textDecoder.decode(dataView.buffer);\n _console.log({ id });\n this.#updateId(id);\n break;\n case \"getName\":\n case \"setName\":\n const name = textDecoder.decode(dataView.buffer);\n _console.log({ name });\n this.updateName(name);\n break;\n case \"getType\":\n case \"setType\":\n const typeEnum = dataView.getUint8(0);\n const type = DeviceTypes[typeEnum];\n _console.log({ typeEnum, type });\n this.updateType(type);\n break;\n case \"getMtu\":\n const mtu = dataView.getUint16(0, true);\n _console.log({ mtu });\n this.#updateMtu(mtu);\n break;\n case \"getCurrentTime\":\n case \"setCurrentTime\":\n const currentTime = Number(dataView.getBigUint64(0, true));\n this.#onCurrentTime(currentTime);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n this.#isCurrentTimeSet = false;\n }\n}\n\nexport default InformationManager;\n","export const VibrationWaveformEffects = [\n \"none\",\n \"strongClick100\",\n \"strongClick60\",\n \"strongClick30\",\n \"sharpClick100\",\n \"sharpClick60\",\n \"sharpClick30\",\n \"softBump100\",\n \"softBump60\",\n \"softBump30\",\n \"doubleClick100\",\n \"doubleClick60\",\n \"tripleClick100\",\n \"softFuzz60\",\n \"strongBuzz100\",\n \"alert750ms\",\n \"alert1000ms\",\n \"strongClick1_100\",\n \"strongClick2_80\",\n \"strongClick3_60\",\n \"strongClick4_30\",\n \"mediumClick100\",\n \"mediumClick80\",\n \"mediumClick60\",\n \"sharpTick100\",\n \"sharpTick80\",\n \"sharpTick60\",\n \"shortDoubleClickStrong100\",\n \"shortDoubleClickStrong80\",\n \"shortDoubleClickStrong60\",\n \"shortDoubleClickStrong30\",\n \"shortDoubleClickMedium100\",\n \"shortDoubleClickMedium80\",\n \"shortDoubleClickMedium60\",\n \"shortDoubleSharpTick100\",\n \"shortDoubleSharpTick80\",\n \"shortDoubleSharpTick60\",\n \"longDoubleSharpClickStrong100\",\n \"longDoubleSharpClickStrong80\",\n \"longDoubleSharpClickStrong60\",\n \"longDoubleSharpClickStrong30\",\n \"longDoubleSharpClickMedium100\",\n \"longDoubleSharpClickMedium80\",\n \"longDoubleSharpClickMedium60\",\n \"longDoubleSharpTick100\",\n \"longDoubleSharpTick80\",\n \"longDoubleSharpTick60\",\n \"buzz100\",\n \"buzz80\",\n \"buzz60\",\n \"buzz40\",\n \"buzz20\",\n \"pulsingStrong100\",\n \"pulsingStrong60\",\n \"pulsingMedium100\",\n \"pulsingMedium60\",\n \"pulsingSharp100\",\n \"pulsingSharp60\",\n \"transitionClick100\",\n \"transitionClick80\",\n \"transitionClick60\",\n \"transitionClick40\",\n \"transitionClick20\",\n \"transitionClick10\",\n \"transitionHum100\",\n \"transitionHum80\",\n \"transitionHum60\",\n \"transitionHum40\",\n \"transitionHum20\",\n \"transitionHum10\",\n \"transitionRampDownLongSmooth2_100\",\n \"transitionRampDownLongSmooth1_100\",\n \"transitionRampDownMediumSmooth1_100\",\n \"transitionRampDownMediumSmooth2_100\",\n \"transitionRampDownShortSmooth1_100\",\n \"transitionRampDownShortSmooth2_100\",\n \"transitionRampDownLongSharp1_100\",\n \"transitionRampDownLongSharp2_100\",\n \"transitionRampDownMediumSharp1_100\",\n \"transitionRampDownMediumSharp2_100\",\n \"transitionRampDownShortSharp1_100\",\n \"transitionRampDownShortSharp2_100\",\n \"transitionRampUpLongSmooth1_100\",\n \"transitionRampUpLongSmooth2_100\",\n \"transitionRampUpMediumSmooth1_100\",\n \"transitionRampUpMediumSmooth2_100\",\n \"transitionRampUpShortSmooth1_100\",\n \"transitionRampUpShortSmooth2_100\",\n \"transitionRampUpLongSharp1_100\",\n \"transitionRampUpLongSharp2_100\",\n \"transitionRampUpMediumSharp1_100\",\n \"transitionRampUpMediumSharp2_100\",\n \"transitionRampUpShortSharp1_100\",\n \"transitionRampUpShortSharp2_100\",\n \"transitionRampDownLongSmooth1_50\",\n \"transitionRampDownLongSmooth2_50\",\n \"transitionRampDownMediumSmooth1_50\",\n \"transitionRampDownMediumSmooth2_50\",\n \"transitionRampDownShortSmooth1_50\",\n \"transitionRampDownShortSmooth2_50\",\n \"transitionRampDownLongSharp1_50\",\n \"transitionRampDownLongSharp2_50\",\n \"transitionRampDownMediumSharp1_50\",\n \"transitionRampDownMediumSharp2_50\",\n \"transitionRampDownShortSharp1_50\",\n \"transitionRampDownShortSharp2_50\",\n \"transitionRampUpLongSmooth1_50\",\n \"transitionRampUpLongSmooth2_50\",\n \"transitionRampUpMediumSmooth1_50\",\n \"transitionRampUpMediumSmooth2_50\",\n \"transitionRampUpShortSmooth1_50\",\n \"transitionRampUpShortSmooth2_50\",\n \"transitionRampUpLongSharp1_50\",\n \"transitionRampUpLongSharp2_50\",\n \"transitionRampUpMediumSharp1_50\",\n \"transitionRampUpMediumSharp2_50\",\n \"transitionRampUpShortSharp1_50\",\n \"transitionRampUpShortSharp2_50\",\n \"longBuzz100\",\n \"smoothHum50\",\n \"smoothHum40\",\n \"smoothHum30\",\n \"smoothHum20\",\n \"smoothHum10\",\n] as const;\n\nexport type VibrationWaveformEffect = (typeof VibrationWaveformEffects)[number];\n","import { createConsole } from \"../utils/Console.ts\";\nimport { VibrationWaveformEffect, VibrationWaveformEffects } from \"./VibrationWaveformEffects.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"VibrationManager\");\n\nexport const VibrationLocations = [\"front\", \"rear\"] as const;\nexport type VibrationLocation = (typeof VibrationLocations)[number];\n\nexport const VibrationTypes = [\"waveformEffect\", \"waveform\"] as const;\nexport type VibrationType = (typeof VibrationTypes)[number];\n\nexport interface VibrationWaveformEffectSegment {\n effect?: VibrationWaveformEffect;\n delay?: number;\n loopCount?: number;\n}\n\nexport interface VibrationWaveformSegment {\n duration: number;\n amplitude: number;\n}\n\nexport const VibrationMessageTypes = [\"triggerVibration\"] as const;\nexport type VibrationMessageType = (typeof VibrationMessageTypes)[number];\n\nexport const MaxNumberOfVibrationWaveformEffectSegments = 8;\nexport const MaxVibrationWaveformSegmentDuration = 2550;\nexport const MaxVibrationWaveformEffectSegmentDelay = 1270;\nexport const MaxVibrationWaveformEffectSegmentLoopCount = 3;\nexport const MaxNumberOfVibrationWaveformSegments = 20;\nexport const MaxVibrationWaveformEffectSequenceLoopCount = 6;\n\ninterface BaseVibrationConfiguration {\n type: VibrationType;\n locations?: VibrationLocation[];\n}\n\nexport interface VibrationWaveformEffectConfiguration extends BaseVibrationConfiguration {\n type: \"waveformEffect\";\n segments: VibrationWaveformEffectSegment[];\n loopCount?: number;\n}\n\nexport interface VibrationWaveformConfiguration extends BaseVibrationConfiguration {\n type: \"waveform\";\n segments: VibrationWaveformSegment[];\n}\n\nexport type VibrationConfiguration = VibrationWaveformEffectConfiguration | VibrationWaveformConfiguration;\n\nexport type SendVibrationMessageCallback = SendMessageCallback<VibrationMessageType>;\n\nclass VibrationManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendVibrationMessageCallback;\n\n #verifyLocation(location: VibrationLocation) {\n _console.assertTypeWithError(location, \"string\");\n _console.assertWithError(VibrationLocations.includes(location), `invalid location \"${location}\"`);\n }\n #verifyLocations(locations: VibrationLocation[]) {\n this.#assertNonEmptyArray(locations);\n locations.forEach((location) => {\n this.#verifyLocation(location);\n });\n }\n #createLocationsBitmask(locations: VibrationLocation[]) {\n this.#verifyLocations(locations);\n\n let locationsBitmask = 0;\n locations.forEach((location) => {\n const locationIndex = VibrationLocations.indexOf(location);\n locationsBitmask |= 1 << locationIndex;\n });\n _console.log({ locationsBitmask });\n _console.assertWithError(locationsBitmask > 0, `locationsBitmask must not be zero`);\n return locationsBitmask;\n }\n\n #assertNonEmptyArray(array: any[]) {\n _console.assertWithError(Array.isArray(array), \"passed non-array\");\n _console.assertWithError(array.length > 0, \"passed empty array\");\n }\n\n #verifyWaveformEffect(waveformEffect: VibrationWaveformEffect) {\n _console.assertWithError(\n VibrationWaveformEffects.includes(waveformEffect),\n `invalid waveformEffect \"${waveformEffect}\"`\n );\n }\n\n #verifyWaveformEffectSegment(waveformEffectSegment: VibrationWaveformEffectSegment) {\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n this.#verifyWaveformEffect(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n _console.assertWithError(delay >= 0, `delay must be 0ms or greater (got ${delay})`);\n _console.assertWithError(\n delay <= MaxVibrationWaveformEffectSegmentDelay,\n `delay must be ${MaxVibrationWaveformEffectSegmentDelay}ms or less (got ${delay})`\n );\n } else {\n throw Error(\"no effect or delay found in waveformEffectSegment\");\n }\n\n if (waveformEffectSegment.loopCount != undefined) {\n const { loopCount } = waveformEffectSegment;\n this.#verifyWaveformEffectSegmentLoopCount(loopCount);\n }\n }\n\n #verifyWaveformEffectSegmentLoopCount(waveformEffectSegmentLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSegmentLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSegmentLoopCount >= 0,\n `waveformEffectSegmentLoopCount must be 0 or greater (got ${waveformEffectSegmentLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSegmentLoopCount <= MaxVibrationWaveformEffectSegmentLoopCount,\n `waveformEffectSegmentLoopCount must be ${MaxVibrationWaveformEffectSegmentLoopCount} or fewer (got ${waveformEffectSegmentLoopCount})`\n );\n }\n\n #verifyWaveformEffectSegments(waveformEffectSegments: VibrationWaveformEffectSegment[]) {\n this.#assertNonEmptyArray(waveformEffectSegments);\n _console.assertWithError(\n waveformEffectSegments.length <= MaxNumberOfVibrationWaveformEffectSegments,\n `must have ${MaxNumberOfVibrationWaveformEffectSegments} waveformEffectSegments or fewer (got ${waveformEffectSegments.length})`\n );\n waveformEffectSegments.forEach((waveformEffectSegment) => {\n this.#verifyWaveformEffectSegment(waveformEffectSegment);\n });\n }\n\n #verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSequenceLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSequenceLoopCount >= 0,\n `waveformEffectSequenceLoopCount must be 0 or greater (got ${waveformEffectSequenceLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSequenceLoopCount <= MaxVibrationWaveformEffectSequenceLoopCount,\n `waveformEffectSequenceLoopCount must be ${MaxVibrationWaveformEffectSequenceLoopCount} or fewer (got ${waveformEffectSequenceLoopCount})`\n );\n }\n\n #verifyWaveformSegment(waveformSegment: VibrationWaveformSegment) {\n _console.assertTypeWithError(waveformSegment.amplitude, \"number\");\n _console.assertWithError(\n waveformSegment.amplitude >= 0,\n `amplitude must be 0 or greater (got ${waveformSegment.amplitude})`\n );\n _console.assertWithError(\n waveformSegment.amplitude <= 1,\n `amplitude must be 1 or less (got ${waveformSegment.amplitude})`\n );\n\n _console.assertTypeWithError(waveformSegment.duration, \"number\");\n _console.assertWithError(\n waveformSegment.duration > 0,\n `duration must be greater than 0ms (got ${waveformSegment.duration}ms)`\n );\n _console.assertWithError(\n waveformSegment.duration <= MaxVibrationWaveformSegmentDuration,\n `duration must be ${MaxVibrationWaveformSegmentDuration}ms or less (got ${waveformSegment.duration}ms)`\n );\n }\n\n #verifyWaveformSegments(waveformSegments: VibrationWaveformSegment[]) {\n this.#assertNonEmptyArray(waveformSegments);\n _console.assertWithError(\n waveformSegments.length <= MaxNumberOfVibrationWaveformSegments,\n `must have ${MaxNumberOfVibrationWaveformSegments} waveformSegments or fewer (got ${waveformSegments.length})`\n );\n waveformSegments.forEach((waveformSegment) => {\n this.#verifyWaveformSegment(waveformSegment);\n });\n }\n\n #createWaveformEffectsData(\n locations: VibrationLocation[],\n waveformEffectSegments: VibrationWaveformEffectSegment[],\n waveformEffectSequenceLoopCount: number = 0\n ) {\n this.#verifyWaveformEffectSegments(waveformEffectSegments);\n this.#verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount);\n\n let dataArray = [];\n let byteOffset = 0;\n\n const hasAtLeast1WaveformEffectWithANonzeroLoopCount = waveformEffectSegments.some((waveformEffectSegment) => {\n const { loopCount } = waveformEffectSegment;\n return loopCount != undefined && loopCount > 0;\n });\n\n const includeAllWaveformEffectSegments =\n hasAtLeast1WaveformEffectWithANonzeroLoopCount || waveformEffectSequenceLoopCount != 0;\n\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegments && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegment = waveformEffectSegments[index] || { effect: \"none\" };\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n dataArray[byteOffset++] = VibrationWaveformEffects.indexOf(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n dataArray[byteOffset++] = (1 << 7) | Math.floor(delay / 10); // set most significant bit to 1\n } else {\n throw Error(\"invalid waveformEffectSegment\");\n }\n }\n\n const includeAllWaveformEffectSegmentLoopCounts = waveformEffectSequenceLoopCount != 0;\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegmentLoopCounts && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegmentLoopCount = waveformEffectSegments[index]?.loopCount || 0;\n if (index == 0 || index == 4) {\n dataArray[byteOffset] = 0;\n }\n const bitOffset = 2 * (index % 4);\n dataArray[byteOffset] |= waveformEffectSegmentLoopCount << bitOffset;\n if (index == 3 || index == 7) {\n byteOffset++;\n }\n }\n\n if (waveformEffectSequenceLoopCount != 0) {\n dataArray[byteOffset++] = waveformEffectSequenceLoopCount;\n }\n const dataView = new DataView(Uint8Array.from(dataArray).buffer);\n _console.log({ dataArray, dataView });\n return this.#createData(locations, \"waveformEffect\", dataView);\n }\n #createWaveformData(locations: VibrationLocation[], waveformSegments: VibrationWaveformSegment[]) {\n this.#verifyWaveformSegments(waveformSegments);\n const dataView = new DataView(new ArrayBuffer(waveformSegments.length * 2));\n waveformSegments.forEach((waveformSegment, index) => {\n dataView.setUint8(index * 2, Math.floor(waveformSegment.amplitude * 127));\n dataView.setUint8(index * 2 + 1, Math.floor(waveformSegment.duration / 10));\n });\n _console.log({ dataView });\n return this.#createData(locations, \"waveform\", dataView);\n }\n\n #verifyVibrationType(vibrationType: VibrationType) {\n _console.assertTypeWithError(vibrationType, \"string\");\n _console.assertWithError(VibrationTypes.includes(vibrationType), `invalid vibrationType \"${vibrationType}\"`);\n }\n\n #createData(locations: VibrationLocation[], vibrationType: VibrationType, dataView: DataView) {\n _console.assertWithError(dataView?.byteLength > 0, \"no data received\");\n const locationsBitmask = this.#createLocationsBitmask(locations);\n this.#verifyVibrationType(vibrationType);\n const vibrationTypeIndex = VibrationTypes.indexOf(vibrationType);\n _console.log({ locationsBitmask, vibrationTypeIndex, dataView });\n const data = concatenateArrayBuffers(locationsBitmask, vibrationTypeIndex, dataView.byteLength, dataView);\n _console.log({ data });\n return data;\n }\n\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately: boolean = true) {\n let triggerVibrationData!: ArrayBuffer;\n vibrationConfigurations.forEach((vibrationConfiguration) => {\n const { type } = vibrationConfiguration;\n\n let { locations } = vibrationConfiguration;\n locations = locations || VibrationLocations.slice();\n\n let arrayBuffer: ArrayBuffer;\n\n switch (type) {\n case \"waveformEffect\":\n {\n const { segments, loopCount } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformEffectsData(locations, segments, loopCount);\n }\n break;\n case \"waveform\":\n {\n const { segments } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformData(locations, segments);\n }\n break;\n default:\n throw Error(`invalid vibration type \"${type}\"`);\n }\n _console.log({ type, arrayBuffer });\n triggerVibrationData = concatenateArrayBuffers(triggerVibrationData, arrayBuffer);\n });\n await this.sendMessage([{ type: \"triggerVibration\", data: triggerVibrationData }], sendImmediately);\n }\n}\n\nexport default VibrationManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport Timer from \"../utils/Timer.ts\";\n\nimport { FileTransferMessageTypes } from \"../FileTransferManager.ts\";\nimport { TfliteMessageTypes } from \"../TfliteManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { InformationMessageTypes } from \"../InformationManager.ts\";\nimport { VibrationMessageTypes } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfigurationMessageTypes } from \"../sensor/SensorConfigurationManager.ts\";\nimport { SensorDataMessageTypes } from \"../sensor/SensorDataManager.ts\";\n\nconst _console = createConsole(\"BaseConnectionManager\", { log: true });\n\nexport const ConnectionTypes = [\"webBluetooth\", \"noble\", \"client\"] as const;\nexport type ConnectionType = (typeof ConnectionTypes)[number];\n\nexport const ConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ConnectionStatus = (typeof ConnectionStatuses)[number];\n\nexport const ConnectionEventTypes = [...ConnectionStatuses, \"connectionStatus\", \"isConnected\"] as const;\nexport type ConnectionEventType = (typeof ConnectionEventTypes)[number];\n\nexport interface ConnectionStatusEventMessages {\n notConnected: any;\n connecting: any;\n connected: any;\n disconnecting: any;\n connectionStatus: { connectionStatus: ConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport interface TxMessage {\n type: TxRxMessageType;\n data?: ArrayBuffer;\n}\n\nexport const TxRxMessageTypes = [\n ...InformationMessageTypes,\n ...SensorConfigurationMessageTypes,\n ...SensorDataMessageTypes,\n ...VibrationMessageTypes,\n ...TfliteMessageTypes,\n ...FileTransferMessageTypes,\n] as const;\nexport type TxRxMessageType = (typeof TxRxMessageTypes)[number];\n\nexport const SMPMessageTypes = [\"smp\"] as const;\nexport type SMPMessageType = (typeof SMPMessageTypes)[number];\n\nexport const BatteryLevelMessageTypes = [\"batteryLevel\"] as const;\nexport type BatteryLevelMessageType = (typeof BatteryLevelMessageTypes)[number];\n\nexport const MetaConnectionMessageTypes = [\"rx\", \"tx\"] as const;\nexport type MetaConnectionMessageType = (typeof MetaConnectionMessageTypes)[number];\n\nexport const ConnectionMessageTypes = [\n ...BatteryLevelMessageTypes,\n ...DeviceInformationMessageTypes,\n ...MetaConnectionMessageTypes,\n ...TxRxMessageTypes,\n ...SMPMessageTypes,\n] as const;\nexport type ConnectionMessageType = (typeof ConnectionMessageTypes)[number];\n\nexport type ConnectionStatusCallback = (status: ConnectionStatus) => void;\nexport type MessageReceivedCallback = (messageType: ConnectionMessageType, dataView: DataView) => void;\nexport type MessagesReceivedCallback = () => void;\n\nabstract class BaseConnectionManager {\n static #AssertValidTxRxMessageType(messageType: TxRxMessageType) {\n _console.assertEnumWithError(messageType, TxRxMessageTypes);\n }\n\n abstract get bluetoothId(): string;\n\n // CALLBACKS\n onStatusUpdated?: ConnectionStatusCallback;\n onMessageReceived?: MessageReceivedCallback;\n onMessagesReceived?: MessagesReceivedCallback;\n\n protected get baseConstructor() {\n return this.constructor as typeof BaseConnectionManager;\n }\n static get isSupported() {\n return false;\n }\n get isSupported() {\n return this.baseConstructor.isSupported;\n }\n\n static type: ConnectionType;\n get type(): ConnectionType {\n return this.baseConstructor.type;\n }\n\n /** @throws {Error} if not supported */\n #assertIsSupported() {\n _console.assertWithError(this.isSupported, `${this.constructor.name} is not supported`);\n }\n\n constructor() {\n this.#assertIsSupported();\n }\n\n #status: ConnectionStatus = \"notConnected\";\n get status() {\n return this.#status;\n }\n protected set status(newConnectionStatus) {\n _console.assertEnumWithError(newConnectionStatus, ConnectionStatuses);\n if (this.#status == newConnectionStatus) {\n _console.log(`tried to assign same connection status \"${newConnectionStatus}\"`);\n return;\n }\n _console.log(`new connection status \"${newConnectionStatus}\"`);\n this.#status = newConnectionStatus;\n this.onStatusUpdated!(this.status);\n\n if (this.isConnected) {\n this.#timer.start();\n } else {\n this.#timer.stop();\n }\n\n if (this.#status == \"notConnected\") {\n this.mtu = undefined;\n }\n }\n\n get isConnected() {\n return this.status == \"connected\";\n }\n\n /** @throws {Error} if connected */\n #assertIsNotConnected() {\n _console.assertWithError(!this.isConnected, \"device is already connected\");\n }\n /** @throws {Error} if connecting */\n #assertIsNotConnecting() {\n _console.assertWithError(this.status != \"connecting\", \"device is already connecting\");\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"device is not connected\");\n }\n /** @throws {Error} if disconnecting */\n #assertIsNotDisconnecting() {\n _console.assertWithError(this.status != \"disconnecting\", \"device is already disconnecting\");\n }\n /** @throws {Error} if not connected or is disconnecting */\n #assertIsConnectedAndNotDisconnecting() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n }\n\n async connect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n this.status = \"connecting\";\n }\n get canReconnect() {\n return false;\n }\n async reconnect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n _console.assert(this.canReconnect, \"unable to reconnect\");\n }\n async disconnect() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n this.status = \"disconnecting\";\n _console.log(\"disconnecting from device...\");\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n this.#assertIsConnectedAndNotDisconnecting();\n _console.log(\"sending smp message\", data);\n }\n\n #pendingMessages: TxMessage[] = [];\n #isSendingMessages = false;\n async sendTxMessages(messages: TxMessage[] | undefined, sendImmediately: boolean = true) {\n this.#assertIsConnectedAndNotDisconnecting();\n\n if (messages) {\n this.#pendingMessages.push(...messages);\n }\n\n if (!sendImmediately) {\n return;\n }\n\n if (this.#isSendingMessages) {\n return;\n }\n this.#isSendingMessages = true;\n\n _console.log(\"sendTxMessages\", this.#pendingMessages.slice());\n\n const arrayBuffers = this.#pendingMessages.map((message) => {\n BaseConnectionManager.#AssertValidTxRxMessageType(message.type);\n const messageTypeEnum = TxRxMessageTypes.indexOf(message.type);\n const dataLength = new DataView(new ArrayBuffer(2));\n dataLength.setUint16(0, message.data?.byteLength || 0, true);\n return concatenateArrayBuffers(messageTypeEnum, dataLength, message.data);\n });\n\n if (this.mtu) {\n while (arrayBuffers.length > 0) {\n let arrayBufferByteLength = 0;\n let arrayBufferCount = 0;\n arrayBuffers.some((arrayBuffer) => {\n if (arrayBufferByteLength + arrayBuffer.byteLength > this.mtu! - 3) {\n return true;\n }\n arrayBufferCount++;\n arrayBufferByteLength += arrayBuffer.byteLength;\n });\n const arrayBuffersToSend = arrayBuffers.splice(0, arrayBufferCount);\n _console.log({ arrayBufferCount, arrayBuffersToSend });\n\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffersToSend);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n } else {\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffers);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n this.#pendingMessages.length = 0;\n\n this.#isSendingMessages = false;\n }\n\n mtu?: number;\n\n async sendTxData(data: ArrayBuffer) {\n _console.log(\"sendTxData\", data);\n }\n\n parseRxMessage(dataView: DataView) {\n parseMessage(dataView, TxRxMessageTypes, this.#onRxMessage.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onRxMessage(messageType: TxRxMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n this.onMessageReceived!(messageType, dataView);\n }\n\n #timer = new Timer(this.#checkConnection.bind(this), 5000);\n #checkConnection() {\n //console.log(\"checking connection...\");\n if (!this.isConnected) {\n _console.log(\"timer detected disconnection\");\n this.status = \"notConnected\";\n }\n }\n}\n\nexport default BaseConnectionManager;\n","export function spacesToPascalCase(string: string) {\n return string\n .replace(/(?:^\\w|\\b\\w)/g, function (match) {\n return match.toUpperCase();\n })\n .replace(/\\s+/g, \"\");\n}\n\nexport function capitalizeFirstCharacter(string: string) {\n return string[0].toUpperCase() + string.slice(1);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { spacesToPascalCase } from \"./stringUtils.ts\";\n\nconst _console = createConsole(\"EventUtils\", { log: false });\n\ntype BoundEventListeners = { [eventType: string]: EventListener };\nexport type BoundGenericEventListeners = { [eventType: string]: Function };\n\nexport function bindEventListeners(\n eventTypes: readonly string[],\n boundEventListeners: BoundGenericEventListeners,\n target: any\n) {\n _console.log(\"bindEventListeners\", { eventTypes, boundEventListeners, target });\n eventTypes.forEach((eventType) => {\n const _eventType = `_on${spacesToPascalCase(eventType)}`;\n _console.assertWithError(target[_eventType], `no event \"${_eventType}\" found in target`);\n _console.log(`binding eventType \"${eventType}\" as ${_eventType} from target`, target);\n const boundEvent = target[_eventType].bind(target);\n target[_eventType] = boundEvent;\n boundEventListeners[eventType] = boundEvent;\n });\n}\n\nexport function addEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let addEventListener = target.addEventListener || target.addListener || target.on || target.AddEventListener;\n _console.assertWithError(addEventListener, \"no add listener function found for target\");\n addEventListener = addEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n addEventListener(eventType, eventListener);\n });\n}\n\nexport function removeEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let removeEventListener = target.removeEventListener || target.removeListener || target.RemoveEventListener;\n _console.assertWithError(removeEventListener, \"no remove listener function found for target\");\n removeEventListener = removeEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n removeEventListener(eventType, eventListener);\n });\n}\n","import { isInBrowser, isInNode } from \"../../utils/environment.ts\";\nimport { createConsole } from \"../../utils/Console.ts\";\n\nconst _console = createConsole(\"bluetoothUUIDs\", { log: false });\n\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nvar BluetoothUUID = webbluetooth.BluetoothUUID;\n/** NODE_END */\n/** BROWSER_START */\nif (isInBrowser) {\n var BluetoothUUID = window.BluetoothUUID;\n}\n/** BROWSER_END */\n\nfunction generateBluetoothUUID(value: string): BluetoothServiceUUID {\n _console.assertTypeWithError(value, \"string\");\n _console.assertWithError(value.length == 4, \"value must be 4 characters long\");\n return `ea6da725-${value}-4f9b-893d-c3913e33b39f`;\n}\n\nfunction stringToCharacteristicUUID(identifier: string): BluetoothCharacteristicUUID {\n return BluetoothUUID?.getCharacteristic?.(identifier);\n}\n\nfunction stringToServiceUUID(identifier: string): BluetoothServiceUUID {\n return BluetoothUUID?.getService?.(identifier);\n}\n\nexport type BluetoothServiceName = \"deviceInformation\" | \"battery\" | \"main\" | \"smp\";\nimport { DeviceInformationMessageType } from \"../../DeviceInformationManager.ts\";\nexport type BluetoothCharacteristicName = DeviceInformationMessageType | \"batteryLevel\" | \"rx\" | \"tx\" | \"smp\";\n\ninterface BluetoothCharacteristicInformation {\n uuid: BluetoothCharacteristicUUID;\n}\ninterface BluetoothServiceInformation {\n uuid: BluetoothServiceUUID;\n characteristics: { [characteristicName in BluetoothCharacteristicName]?: BluetoothCharacteristicInformation };\n}\ninterface BluetoothServicesInformation {\n services: { [serviceName in BluetoothServiceName]: BluetoothServiceInformation };\n}\nconst bluetoothUUIDs: BluetoothServicesInformation = Object.freeze({\n services: {\n deviceInformation: {\n uuid: stringToServiceUUID(\"device_information\"),\n characteristics: {\n manufacturerName: {\n uuid: stringToCharacteristicUUID(\"manufacturer_name_string\"),\n },\n modelNumber: {\n uuid: stringToCharacteristicUUID(\"model_number_string\"),\n },\n hardwareRevision: {\n uuid: stringToCharacteristicUUID(\"hardware_revision_string\"),\n },\n firmwareRevision: {\n uuid: stringToCharacteristicUUID(\"firmware_revision_string\"),\n },\n softwareRevision: {\n uuid: stringToCharacteristicUUID(\"software_revision_string\"),\n },\n pnpId: {\n uuid: stringToCharacteristicUUID(\"pnp_id\"),\n },\n serialNumber: {\n uuid: stringToCharacteristicUUID(\"serial_number_string\"),\n },\n },\n },\n battery: {\n uuid: stringToServiceUUID(\"battery_service\"),\n characteristics: {\n batteryLevel: {\n uuid: stringToCharacteristicUUID(\"battery_level\"),\n },\n },\n },\n main: {\n uuid: generateBluetoothUUID(\"0000\"),\n characteristics: {\n rx: { uuid: generateBluetoothUUID(\"1000\") },\n tx: { uuid: generateBluetoothUUID(\"1001\") },\n },\n },\n smp: {\n uuid: \"8d53dc1d-1db7-4cd3-868b-8a527460aa84\",\n characteristics: {\n smp: { uuid: \"da2e7828-fbce-4e01-ae9e-261174997c48\" },\n },\n },\n },\n});\n\nexport const serviceUUIDs = [bluetoothUUIDs.services.main.uuid];\nexport const optionalServiceUUIDs = [\n bluetoothUUIDs.services.deviceInformation.uuid,\n bluetoothUUIDs.services.battery.uuid,\n bluetoothUUIDs.services.smp.uuid,\n];\nexport const allServiceUUIDs = [...serviceUUIDs, ...optionalServiceUUIDs];\n\nexport function getServiceNameFromUUID(serviceUUID: BluetoothServiceUUID): BluetoothServiceName | undefined {\n serviceUUID = serviceUUID.toString().toLowerCase();\n const serviceNames = Object.keys(bluetoothUUIDs.services) as BluetoothServiceName[];\n return serviceNames.find((serviceName) => {\n const serviceInfo = bluetoothUUIDs.services[serviceName];\n let serviceInfoUUID = serviceInfo.uuid.toString();\n if (serviceUUID.length == 4) {\n serviceInfoUUID = serviceInfoUUID.slice(4, 8);\n }\n if (!serviceUUID.includes(\"-\")) {\n serviceInfoUUID = serviceInfoUUID.replaceAll(\"-\", \"\");\n }\n return serviceUUID == serviceInfoUUID;\n });\n}\n\nexport const characteristicUUIDs: BluetoothCharacteristicUUID[] = [];\nexport const allCharacteristicUUIDs: BluetoothCharacteristicUUID[] = [];\n\nexport const characteristicNames: BluetoothCharacteristicName[] = [];\nexport const allCharacteristicNames: BluetoothCharacteristicName[] = [];\n\nObject.values(bluetoothUUIDs.services).forEach((serviceInfo) => {\n if (!serviceInfo.characteristics) {\n return;\n }\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicNames.forEach((characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[characteristicName]!;\n if (serviceUUIDs.includes(serviceInfo.uuid)) {\n characteristicUUIDs.push(characteristicInfo.uuid);\n characteristicNames.push(characteristicName);\n }\n allCharacteristicUUIDs.push(characteristicInfo.uuid);\n allCharacteristicNames.push(characteristicName);\n });\n}, []);\n\n//_console.log({ characteristicUUIDs, allCharacteristicUUIDs });\n\nexport function getCharacteristicNameFromUUID(\n characteristicUUID: BluetoothCharacteristicUUID\n): BluetoothCharacteristicName | undefined {\n //_console.log({ characteristicUUID });\n characteristicUUID = characteristicUUID.toString().toLowerCase();\n var characteristicName: BluetoothCharacteristicName | undefined;\n Object.values(bluetoothUUIDs.services).some((serviceInfo) => {\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicName = characteristicNames.find((_characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[_characteristicName]!;\n let characteristicInfoUUID = characteristicInfo.uuid.toString();\n if (characteristicUUID.length == 4) {\n characteristicInfoUUID = characteristicInfoUUID.slice(4, 8);\n }\n if (!characteristicUUID.includes(\"-\")) {\n characteristicInfoUUID = characteristicInfoUUID.replaceAll(\"-\", \"\");\n }\n return characteristicUUID == characteristicInfoUUID;\n });\n return characteristicName;\n });\n return characteristicName;\n}\n\nexport function getCharacteristicProperties(\n characteristicName: BluetoothCharacteristicName\n): BluetoothCharacteristicProperties {\n const properties = {\n broadcast: false,\n read: true,\n writeWithoutResponse: false,\n write: false,\n notify: false,\n indicate: false,\n authenticatedSignedWrites: false,\n reliableWrite: false,\n writableAuxiliaries: false,\n };\n\n // read\n switch (characteristicName) {\n case \"rx\":\n case \"tx\":\n case \"smp\":\n properties.read = false;\n break;\n }\n\n // notify\n switch (characteristicName) {\n case \"batteryLevel\":\n case \"rx\":\n case \"smp\":\n properties.notify = true;\n break;\n }\n\n // write without response\n switch (characteristicName) {\n case \"smp\":\n properties.writeWithoutResponse = true;\n break;\n }\n\n // write\n switch (characteristicName) {\n case \"tx\":\n properties.write = true;\n break;\n }\n\n return properties;\n}\n\nexport const serviceDataUUID = \"0000\";\n","import { createConsole } from \"../../utils/Console.ts\";\nimport BaseConnectionManager from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"BluetoothConnectionManager\", { log: true });\n\nimport { BluetoothCharacteristicName } from \"./bluetoothUUIDs.ts\";\n\nabstract class BluetoothConnectionManager extends BaseConnectionManager {\n isInRange = true;\n\n protected onCharacteristicValueChanged(characteristicName: BluetoothCharacteristicName, dataView: DataView) {\n if (characteristicName == \"rx\") {\n this.parseRxMessage(dataView);\n } else {\n this.onMessageReceived?.(characteristicName, dataView);\n }\n }\n\n protected async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n _console.log(\"writeCharacteristic\", ...arguments);\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n await this.writeCharacteristic(\"smp\", data);\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n await this.writeCharacteristic(\"tx\", data);\n }\n}\n\nexport default BluetoothConnectionManager;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { isInNode, isInBrowser, isInBluefy, isInWebBLE } from \"../../utils/environment.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport {\n serviceUUIDs,\n optionalServiceUUIDs,\n getServiceNameFromUUID,\n getCharacteristicNameFromUUID,\n getCharacteristicProperties,\n} from \"./bluetoothUUIDs.ts\";\nimport BluetoothConnectionManager from \"./BluetoothConnectionManager.ts\";\nimport { BluetoothCharacteristicName, BluetoothServiceName } from \"./bluetoothUUIDs.ts\";\nimport { ConnectionType } from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"WebBluetoothConnectionManager\", { log: true });\n\ntype WebBluetoothInterface = webbluetooth.Bluetooth | Bluetooth;\n\ninterface BluetoothService extends BluetoothRemoteGATTService {\n name?: BluetoothServiceName;\n}\ninterface BluetoothCharacteristic extends BluetoothRemoteGATTCharacteristic {\n name?: BluetoothCharacteristicName;\n}\n\nvar bluetooth: WebBluetoothInterface | undefined;\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nif (isInNode) {\n bluetooth = webbluetooth.bluetooth;\n}\n/** NODE_END */\n\n/** BROWSER_START */\nif (isInBrowser) {\n bluetooth = window.navigator.bluetooth;\n}\n/** BROWSER_END */\n\nclass WebBluetoothConnectionManager extends BluetoothConnectionManager {\n get bluetoothId() {\n return this.device!.id;\n }\n\n #boundBluetoothCharacteristicEventListeners: { [eventType: string]: EventListener } = {\n characteristicvaluechanged: this.#onCharacteristicvaluechanged.bind(this),\n };\n #boundBluetoothDeviceEventListeners: { [eventType: string]: EventListener } = {\n gattserverdisconnected: this.#onGattserverdisconnected.bind(this),\n };\n\n static get isSupported() {\n return Boolean(bluetooth);\n }\n static get type(): ConnectionType {\n return \"webBluetooth\";\n }\n\n #device!: BluetoothDevice | undefined;\n get device() {\n return this.#device;\n }\n set device(newDevice) {\n if (this.#device == newDevice) {\n _console.log(\"tried to assign the same BluetoothDevice\");\n return;\n }\n if (this.#device) {\n removeEventListeners(this.#device, this.#boundBluetoothDeviceEventListeners);\n }\n if (newDevice) {\n addEventListeners(newDevice, this.#boundBluetoothDeviceEventListeners);\n }\n this.#device = newDevice;\n }\n\n get server(): BluetoothRemoteGATTServer | undefined {\n return this.#device?.gatt;\n }\n get isConnected() {\n return this.server?.connected || false;\n }\n\n #services: Map<BluetoothServiceName, BluetoothService> = new Map();\n #characteristics: Map<BluetoothCharacteristicName, BluetoothCharacteristic> = new Map();\n\n async connect() {\n await super.connect();\n\n try {\n const device = await bluetooth!.requestDevice({\n filters: [{ services: serviceUUIDs }],\n optionalServices: isInBrowser ? optionalServiceUUIDs : [],\n });\n\n _console.log(\"got BluetoothDevice\");\n this.device = device;\n\n _console.log(\"connecting to device...\");\n const server = await this.server!.connect();\n _console.log(`connected to device? ${server.connected}`);\n\n await this.#getServicesAndCharacteristics();\n\n _console.log(\"fully connected\");\n\n this.status = \"connected\";\n } catch (error) {\n _console.error(error);\n this.status = \"notConnected\";\n this.server?.disconnect();\n this.#removeEventListeners();\n }\n }\n async #getServicesAndCharacteristics() {\n this.#removeEventListeners();\n\n _console.log(\"getting services...\");\n const services = await this.server!.getPrimaryServices();\n _console.log(\"got services\", services.length);\n //const service = await this.server!.getPrimaryService(\"8d53dc1d-1db7-4cd3-868b-8a527460aa84\");\n\n _console.log(\"getting characteristics...\");\n for (const serviceIndex in services) {\n const service = services[serviceIndex] as BluetoothService;\n _console.log({ service });\n const serviceName = getServiceNameFromUUID(service.uuid)!;\n _console.assertWithError(serviceName, `no name found for service uuid \"${service.uuid}\"`);\n _console.log(`got \"${serviceName}\" service`);\n service.name = serviceName;\n this.#services.set(serviceName, service);\n _console.log(`getting characteristics for \"${serviceName}\" service`);\n const characteristics = await service.getCharacteristics();\n _console.log(`got characteristics for \"${serviceName}\" service`);\n for (const characteristicIndex in characteristics) {\n const characteristic = characteristics[characteristicIndex] as BluetoothCharacteristic;\n _console.log({ characteristic });\n const characteristicName = getCharacteristicNameFromUUID(characteristic.uuid)!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic uuid \"${characteristic.uuid}\" in \"${serviceName}\" service`\n );\n _console.log(`got \"${characteristicName}\" characteristic in \"${serviceName}\" service`);\n characteristic.name = characteristicName;\n this.#characteristics.set(characteristicName, characteristic);\n addEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`starting notifications for \"${characteristicName}\" characteristic`);\n await characteristic.startNotifications();\n }\n if (characteristicProperties.read) {\n _console.log(`reading \"${characteristicName}\" characteristic...`);\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n }\n }\n async #removeEventListeners() {\n if (this.device) {\n removeEventListeners(this.device, this.#boundBluetoothDeviceEventListeners);\n }\n\n const promises = Array.from(this.#characteristics.keys()).map((characteristicName) => {\n const characteristic = this.#characteristics.get(characteristicName)!;\n removeEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`stopping notifications for \"${characteristicName}\" characteristic`);\n return characteristic.stopNotifications();\n }\n });\n\n return Promise.allSettled(promises);\n }\n async disconnect() {\n await this.#removeEventListeners();\n await super.disconnect();\n this.server?.disconnect();\n this.status = \"notConnected\";\n }\n\n #onCharacteristicvaluechanged(event: Event) {\n _console.log(\"oncharacteristicvaluechanged\");\n\n const characteristic = event.target as BluetoothCharacteristic;\n this.#onCharacteristicValueChanged(characteristic);\n }\n\n #onCharacteristicValueChanged(characteristic: BluetoothCharacteristic) {\n _console.log(\"onCharacteristicValue\");\n\n const characteristicName = characteristic.name!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic with uuid \"${characteristic.uuid}\"`\n );\n\n _console.log(`oncharacteristicvaluechanged for \"${characteristicName}\" characteristic`);\n const dataView = characteristic.value!;\n _console.assertWithError(dataView, `no data found for \"${characteristicName}\" characteristic`);\n _console.log(`data for \"${characteristicName}\" characteristic`, Array.from(new Uint8Array(dataView.buffer)));\n\n try {\n this.onCharacteristicValueChanged(characteristicName, dataView);\n } catch (error) {\n _console.error(error);\n }\n }\n\n async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n super.writeCharacteristic(characteristicName, data);\n\n const characteristic = this.#characteristics.get(characteristicName)!;\n _console.assertWithError(characteristic, `${characteristicName} characteristic not found`);\n _console.log(\"writing characteristic\", characteristic, data);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.writeWithoutResponse) {\n _console.log(\"writing without response\");\n await characteristic.writeValueWithoutResponse(data);\n } else {\n _console.log(\"writing with response\");\n await characteristic.writeValueWithResponse(data);\n }\n _console.log(\"wrote characteristic\");\n\n if (characteristicProperties.read && !characteristicProperties.notify) {\n _console.log(\"reading value after write...\");\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n\n #onGattserverdisconnected() {\n _console.log(\"gattserverdisconnected\");\n this.status = \"notConnected\";\n }\n\n get canReconnect() {\n return Boolean(this.server && !this.server.connected && this.isInRange);\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.status = \"connecting\";\n try {\n await this.server!.connect();\n } catch (error) {\n _console.error(error);\n this.isInRange = false;\n }\n\n if (this.isConnected) {\n _console.log(\"successfully reconnected!\");\n await this.#getServicesAndCharacteristics();\n this.status = \"connected\";\n } else {\n _console.log(\"unable to reconnect\");\n this.status = \"notConnected\";\n }\n }\n}\n\nexport default WebBluetoothConnectionManager;\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nconst POW_2_24 = 5.960464477539063e-8;\nconst POW_2_32 = 4294967296;\nconst POW_2_53 = 9007199254740992;\n\nexport function encode(value) {\n let data = new ArrayBuffer(256);\n let dataView = new DataView(data);\n let lastLength;\n let offset = 0;\n\n function prepareWrite(length) {\n let newByteLength = data.byteLength;\n const requiredLength = offset + length;\n while (newByteLength < requiredLength) {\n newByteLength <<= 1;\n }\n if (newByteLength !== data.byteLength) {\n const oldDataView = dataView;\n data = new ArrayBuffer(newByteLength);\n dataView = new DataView(data);\n const uint32count = (offset + 3) >> 2;\n for (let i = 0; i < uint32count; ++i) {\n dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));\n }\n }\n\n lastLength = length;\n return dataView;\n }\n function commitWrite() {\n offset += lastLength;\n }\n function writeFloat64(value) {\n commitWrite(prepareWrite(8).setFloat64(offset, value));\n }\n function writeUint8(value) {\n commitWrite(prepareWrite(1).setUint8(offset, value));\n }\n function writeUint8Array(value) {\n const dataView = prepareWrite(value.length);\n for (let i = 0; i < value.length; ++i) {\n dataView.setUint8(offset + i, value[i]);\n }\n commitWrite();\n }\n function writeUint16(value) {\n commitWrite(prepareWrite(2).setUint16(offset, value));\n }\n function writeUint32(value) {\n commitWrite(prepareWrite(4).setUint32(offset, value));\n }\n function writeUint64(value) {\n const low = value % POW_2_32;\n const high = (value - low) / POW_2_32;\n const dataView = prepareWrite(8);\n dataView.setUint32(offset, high);\n dataView.setUint32(offset + 4, low);\n commitWrite();\n }\n function writeTypeAndLength(type, length) {\n if (length < 24) {\n writeUint8((type << 5) | length);\n } else if (length < 0x100) {\n writeUint8((type << 5) | 24);\n writeUint8(length);\n } else if (length < 0x10000) {\n writeUint8((type << 5) | 25);\n writeUint16(length);\n } else if (length < 0x100000000) {\n writeUint8((type << 5) | 26);\n writeUint32(length);\n } else {\n writeUint8((type << 5) | 27);\n writeUint64(length);\n }\n }\n\n function encodeItem(value) {\n let i;\n const utf8data = [];\n let length;\n\n if (value === false) {\n return writeUint8(0xf4);\n }\n if (value === true) {\n return writeUint8(0xf5);\n }\n if (value === null) {\n return writeUint8(0xf6);\n }\n if (value === undefined) {\n return writeUint8(0xf7);\n }\n\n switch (typeof value) {\n case \"number\":\n if (Math.floor(value) === value) {\n if (value >= 0 && value <= POW_2_53) {\n return writeTypeAndLength(0, value);\n }\n if (-POW_2_53 <= value && value < 0) {\n return writeTypeAndLength(1, -(value + 1));\n }\n }\n writeUint8(0xfb);\n return writeFloat64(value);\n\n case \"string\":\n for (i = 0; i < value.length; ++i) {\n let charCode = value.charCodeAt(i);\n if (charCode < 0x80) {\n utf8data.push(charCode);\n } else if (charCode < 0x800) {\n utf8data.push(0xc0 | (charCode >> 6));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else if (charCode < 0xd800) {\n utf8data.push(0xe0 | (charCode >> 12));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else {\n charCode = (charCode & 0x3ff) << 10;\n charCode |= value.charCodeAt(++i) & 0x3ff;\n charCode += 0x10000;\n\n utf8data.push(0xf0 | (charCode >> 18));\n utf8data.push(0x80 | ((charCode >> 12) & 0x3f));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n }\n }\n\n writeTypeAndLength(3, utf8data.length);\n return writeUint8Array(utf8data);\n\n default:\n if (Array.isArray(value)) {\n length = value.length;\n writeTypeAndLength(4, length);\n for (i = 0; i < length; ++i) {\n encodeItem(value[i]);\n }\n } else if (value instanceof Uint8Array) {\n writeTypeAndLength(2, value.length);\n writeUint8Array(value);\n } else {\n const keys = Object.keys(value);\n length = keys.length;\n writeTypeAndLength(5, length);\n for (i = 0; i < length; ++i) {\n const key = keys[i];\n encodeItem(key);\n encodeItem(value[key]);\n }\n }\n }\n }\n\n encodeItem(value);\n\n if (\"slice\" in data) {\n return data.slice(0, offset);\n }\n\n const ret = new ArrayBuffer(offset);\n const retView = new DataView(ret);\n for (let i = 0; i < offset; ++i) {\n retView.setUint8(i, dataView.getUint8(i));\n }\n return ret;\n}\n\nexport function decode(data, tagger, simpleValue) {\n const dataView = new DataView(data);\n let offset = 0;\n\n if (typeof tagger !== \"function\") {\n tagger = function (value) {\n return value;\n };\n }\n if (typeof simpleValue !== \"function\") {\n simpleValue = function () {\n return undefined;\n };\n }\n\n function commitRead(length, value) {\n offset += length;\n return value;\n }\n function readArrayBuffer(length) {\n return commitRead(length, new Uint8Array(data, offset, length));\n }\n function readFloat16() {\n const tempArrayBuffer = new ArrayBuffer(4);\n const tempDataView = new DataView(tempArrayBuffer);\n const value = readUint16();\n\n const sign = value & 0x8000;\n let exponent = value & 0x7c00;\n const fraction = value & 0x03ff;\n\n if (exponent === 0x7c00) {\n exponent = 0xff << 10;\n } else if (exponent !== 0) {\n exponent += (127 - 15) << 10;\n } else if (fraction !== 0) {\n return (sign ? -1 : 1) * fraction * POW_2_24;\n }\n\n tempDataView.setUint32(0, (sign << 16) | (exponent << 13) | (fraction << 13));\n return tempDataView.getFloat32(0);\n }\n function readFloat32() {\n return commitRead(4, dataView.getFloat32(offset));\n }\n function readFloat64() {\n return commitRead(8, dataView.getFloat64(offset));\n }\n function readUint8() {\n return commitRead(1, dataView.getUint8(offset));\n }\n function readUint16() {\n return commitRead(2, dataView.getUint16(offset));\n }\n function readUint32() {\n return commitRead(4, dataView.getUint32(offset));\n }\n function readUint64() {\n return readUint32() * POW_2_32 + readUint32();\n }\n function readBreak() {\n if (dataView.getUint8(offset) !== 0xff) {\n return false;\n }\n offset += 1;\n return true;\n }\n function readLength(additionalInformation) {\n if (additionalInformation < 24) {\n return additionalInformation;\n }\n if (additionalInformation === 24) {\n return readUint8();\n }\n if (additionalInformation === 25) {\n return readUint16();\n }\n if (additionalInformation === 26) {\n return readUint32();\n }\n if (additionalInformation === 27) {\n return readUint64();\n }\n if (additionalInformation === 31) {\n return -1;\n }\n throw new Error(\"Invalid length encoding\");\n }\n function readIndefiniteStringLength(majorType) {\n const initialByte = readUint8();\n if (initialByte === 0xff) {\n return -1;\n }\n const length = readLength(initialByte & 0x1f);\n if (length < 0 || initialByte >> 5 !== majorType) {\n throw new Error(\"Invalid indefinite length element\");\n }\n return length;\n }\n\n function appendUtf16Data(utf16data, length) {\n for (let i = 0; i < length; ++i) {\n let value = readUint8();\n if (value & 0x80) {\n if (value < 0xe0) {\n value = ((value & 0x1f) << 6) | (readUint8() & 0x3f);\n length -= 1;\n } else if (value < 0xf0) {\n value = ((value & 0x0f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 2;\n } else {\n value =\n ((value & 0x0f) << 18) | ((readUint8() & 0x3f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 3;\n }\n }\n\n if (value < 0x10000) {\n utf16data.push(value);\n } else {\n value -= 0x10000;\n utf16data.push(0xd800 | (value >> 10));\n utf16data.push(0xdc00 | (value & 0x3ff));\n }\n }\n }\n\n function decodeItem() {\n const initialByte = readUint8();\n const majorType = initialByte >> 5;\n const additionalInformation = initialByte & 0x1f;\n let i;\n let length;\n\n if (majorType === 7) {\n switch (additionalInformation) {\n case 25:\n return readFloat16();\n case 26:\n return readFloat32();\n case 27:\n return readFloat64();\n }\n }\n\n length = readLength(additionalInformation);\n if (length < 0 && (majorType < 2 || majorType > 6)) {\n throw new Error(\"Invalid length\");\n }\n\n const utf16data = [];\n let retArray;\n const retObject = {};\n\n switch (majorType) {\n case 0:\n return length;\n case 1:\n return -1 - length;\n case 2:\n if (length < 0) {\n const elements = [];\n let fullArrayLength = 0;\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n fullArrayLength += length;\n elements.push(readArrayBuffer(length));\n }\n const fullArray = new Uint8Array(fullArrayLength);\n let fullArrayOffset = 0;\n for (i = 0; i < elements.length; ++i) {\n fullArray.set(elements[i], fullArrayOffset);\n fullArrayOffset += elements[i].length;\n }\n return fullArray;\n }\n return readArrayBuffer(length);\n case 3:\n if (length < 0) {\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n appendUtf16Data(utf16data, length);\n }\n } else {\n appendUtf16Data(utf16data, length);\n }\n return String.fromCharCode.apply(null, utf16data);\n case 4:\n if (length < 0) {\n retArray = [];\n while (!readBreak()) {\n retArray.push(decodeItem());\n }\n } else {\n retArray = new Array(length);\n for (i = 0; i < length; ++i) {\n retArray[i] = decodeItem();\n }\n }\n return retArray;\n case 5:\n for (i = 0; i < length || (length < 0 && !readBreak()); ++i) {\n const key = decodeItem();\n retObject[key] = decodeItem();\n }\n return retObject;\n case 6:\n return tagger(decodeItem(), length);\n case 7:\n switch (length) {\n case 20:\n return false;\n case 21:\n return true;\n case 22:\n return null;\n case 23:\n return undefined;\n default:\n return simpleValue(length);\n }\n }\n }\n\n const ret = decodeItem();\n if (offset !== data.byteLength) {\n throw new Error(\"Remaining bytes\");\n }\n return ret;\n}\n\nexport const CBOR = {\n encode,\n decode,\n};\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2023 Laird Connectivity\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * @file mcumgr\n * @brief Provides MCU manager operation functions for the Xbit USB Shell.\n * This file is inspired by the MIT licensed mcumgr file originally\n * authored by Andras Barthazi (https://github.com/boogie/mcumgr-web),\n * updated to also support file upload/download over SMP.\n */\n\nimport { CBOR } from \"./cbor.js\";\nimport { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"mcumgr\", { log: true });\n\nexport const constants = {\n // Opcodes\n MGMT_OP_READ: 0,\n MGMT_OP_READ_RSP: 1,\n MGMT_OP_WRITE: 2,\n MGMT_OP_WRITE_RSP: 3,\n\n // Groups\n MGMT_GROUP_ID_OS: 0,\n MGMT_GROUP_ID_IMAGE: 1,\n MGMT_GROUP_ID_STAT: 2,\n MGMT_GROUP_ID_CONFIG: 3,\n MGMT_GROUP_ID_LOG: 4,\n MGMT_GROUP_ID_CRASH: 5,\n MGMT_GROUP_ID_SPLIT: 6,\n MGMT_GROUP_ID_RUN: 7,\n MGMT_GROUP_ID_FS: 8,\n MGMT_GROUP_ID_SHELL: 9,\n\n // OS group\n OS_MGMT_ID_ECHO: 0,\n OS_MGMT_ID_CONS_ECHO_CTRL: 1,\n OS_MGMT_ID_TASKSTAT: 2,\n OS_MGMT_ID_MPSTAT: 3,\n OS_MGMT_ID_DATETIME_STR: 4,\n OS_MGMT_ID_RESET: 5,\n\n // Image group\n IMG_MGMT_ID_STATE: 0,\n IMG_MGMT_ID_UPLOAD: 1,\n IMG_MGMT_ID_FILE: 2,\n IMG_MGMT_ID_CORELIST: 3,\n IMG_MGMT_ID_CORELOAD: 4,\n IMG_MGMT_ID_ERASE: 5,\n\n // Filesystem group\n FS_MGMT_ID_FILE: 0,\n};\n\nexport class MCUManager {\n constructor() {\n this._mtu = 256;\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n onMessage(callback) {\n this._messageCallback = callback;\n return this;\n }\n\n onImageUploadNext(callback) {\n this._imageUploadNextCallback = callback;\n return this;\n }\n\n onImageUploadProgress(callback) {\n this._imageUploadProgressCallback = callback;\n return this;\n }\n\n onImageUploadFinished(callback) {\n this._imageUploadFinishedCallback = callback;\n return this;\n }\n\n onFileUploadNext(callback) {\n this._fileUploadNextCallback = callback;\n return this;\n }\n\n onFileUploadProgress(callback) {\n this._fileUploadProgressCallback = callback;\n return this;\n }\n\n onFileUploadFinished(callback) {\n this._fileUploadFinishedCallback = callback;\n return this;\n }\n\n onFileDownloadNext(callback) {\n this._fileDownloadNextCallback = callback;\n return this;\n }\n\n onFileDownloadProgress(callback) {\n this._fileDownloadProgressCallback = callback;\n return this;\n }\n\n onFileDownloadFinished(callback) {\n this._fileDownloadFinishedCallback = callback;\n return this;\n }\n\n _getMessage(op, group, id, data) {\n const _flags = 0;\n let encodedData = [];\n if (typeof data !== \"undefined\") {\n encodedData = [...new Uint8Array(CBOR.encode(data))];\n }\n const lengthLo = encodedData.length & 255;\n const lengthHi = encodedData.length >> 8;\n const groupLo = group & 255;\n const groupHi = group >> 8;\n const message = [op, _flags, lengthHi, lengthLo, groupHi, groupLo, this._seq, id, ...encodedData];\n this._seq = (this._seq + 1) % 256;\n\n return message;\n }\n\n _notification(buffer) {\n _console.log(\"mcumgr - message received\");\n const message = new Uint8Array(buffer);\n this._buffer = new Uint8Array([...this._buffer, ...message]);\n const messageLength = this._buffer[2] * 256 + this._buffer[3];\n if (this._buffer.length < messageLength + 8) return;\n this._processMessage(this._buffer.slice(0, messageLength + 8));\n this._buffer = this._buffer.slice(messageLength + 8);\n }\n\n _processMessage(message) {\n const [op, , lengthHi, lengthLo, groupHi, groupLo, , id] = message;\n const data = CBOR.decode(message.slice(8).buffer);\n const length = lengthHi * 256 + lengthLo;\n const group = groupHi * 256 + groupLo;\n\n _console.log(\"mcumgr - Process Message - Group: \" + group + \", Id: \" + id + \", Off: \" + data.off);\n if (group === constants.MGMT_GROUP_ID_IMAGE && id === constants.IMG_MGMT_ID_UPLOAD && data.off) {\n this._uploadOffset = data.off;\n this._uploadNext();\n return;\n }\n if (\n op === constants.MGMT_OP_WRITE_RSP &&\n group === constants.MGMT_GROUP_ID_FS &&\n id === constants.FS_MGMT_ID_FILE &&\n data.off\n ) {\n this._uploadFileOffset = data.off;\n this._uploadFileNext();\n return;\n }\n if (op === constants.MGMT_OP_READ_RSP && group === constants.MGMT_GROUP_ID_FS && id === constants.FS_MGMT_ID_FILE) {\n this._downloadFileOffset += data.data.length;\n if (data.len != undefined) {\n this._downloadFileLength = data.len;\n }\n _console.log(\"downloaded \" + this._downloadFileOffset + \" bytes of \" + this._downloadFileLength);\n if (this._downloadFileLength > 0) {\n this._fileDownloadProgressCallback({\n percentage: Math.floor((this._downloadFileOffset / this._downloadFileLength) * 100),\n });\n }\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n this._downloadFileNext();\n return;\n }\n\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n }\n\n cmdReset() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_RESET);\n }\n\n smpEcho(message) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_ECHO, {\n d: message,\n });\n }\n\n cmdImageState() {\n return this._getMessage(constants.MGMT_OP_READ, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE);\n }\n\n cmdImageErase() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_ERASE, {});\n }\n\n cmdImageTest(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: false,\n });\n }\n\n cmdImageConfirm(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: true,\n });\n }\n\n _hash(image) {\n return crypto.subtle.digest(\"SHA-256\", image);\n }\n\n async _uploadNext() {\n if (!this._uploadImage) {\n return;\n }\n\n if (this._uploadOffset >= this._uploadImage.byteLength) {\n this._uploadIsInProgress = false;\n this._imageUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadOffset };\n if (this._uploadOffset === 0) {\n message.len = this._uploadImage.byteLength;\n message.sha = new Uint8Array(await this._hash(this._uploadImage));\n }\n this._imageUploadProgressCallback({\n percentage: Math.floor((this._uploadOffset / this._uploadImage.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead - 3 - 5;\n\n message.data = new Uint8Array(this._uploadImage.slice(this._uploadOffset, this._uploadOffset + length));\n\n this._uploadOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_IMAGE,\n constants.IMG_MGMT_ID_UPLOAD,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._imageUploadNextCallback({ packet });\n }\n async reset() {\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n async cmdUpload(image, slot = 0) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n\n this._uploadOffset = 0;\n this._uploadImage = image;\n this._uploadSlot = slot;\n\n this._uploadNext();\n }\n\n async cmdUploadFile(filebuf, destFilename) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n this._uploadFileOffset = 0;\n this._uploadFile = filebuf;\n this._uploadFilename = destFilename;\n\n this._uploadFileNext();\n }\n\n async _uploadFileNext() {\n _console.log(\"uploadFileNext - offset: \" + this._uploadFileOffset + \", length: \" + this._uploadFile.byteLength);\n\n if (this._uploadFileOffset >= this._uploadFile.byteLength) {\n this._uploadIsInProgress = false;\n this._fileUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadFileOffset };\n if (this._uploadFileOffset === 0) {\n message.len = this._uploadFile.byteLength;\n }\n message.name = this._uploadFilename;\n this._fileUploadProgressCallback({\n percentage: Math.floor((this._uploadFileOffset / this._uploadFile.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead;\n\n message.data = new Uint8Array(this._uploadFile.slice(this._uploadFileOffset, this._uploadFileOffset + length));\n\n this._uploadFileOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._fileUploadNextCallback({ packet });\n }\n\n async cmdDownloadFile(filename, destFilename) {\n if (this._downloadIsInProgress) {\n _console.error(\"Download is already in progress.\");\n return;\n }\n this._downloadIsInProgress = true;\n this._downloadFileOffset = 0;\n this._downloadFileLength = 0;\n this._downloadRemoteFilename = filename;\n this._downloadLocalFilename = destFilename;\n\n this._downloadFileNext();\n }\n\n async _downloadFileNext() {\n if (this._downloadFileLength > 0) {\n if (this._downloadFileOffset >= this._downloadFileLength) {\n this._downloadIsInProgress = false;\n this._fileDownloadFinishedCallback();\n return;\n }\n }\n\n const message = { off: this._downloadFileOffset };\n if (this._downloadFileOffset === 0) {\n message.name = this._downloadRemoteFilename;\n }\n\n const packet = this._getMessage(\n constants.MGMT_OP_READ,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n _console.log(\"mcumgr - _downloadNext: Message Length: \" + packet.length);\n this._fileDownloadNextCallback({ packet });\n }\n\n async imageInfo(image) {\n const info = {};\n const view = new Uint8Array(image);\n\n // check header length\n if (view.length < 32) {\n throw new Error(\"Invalid image (too short file)\");\n }\n\n // check MAGIC bytes 0x96f3b83d\n if (view[0] !== 0x3d || view[1] !== 0xb8 || view[2] !== 0xf3 || view[3] !== 0x96) {\n throw new Error(\"Invalid image (wrong magic bytes)\");\n }\n\n // check load address is 0x00000000\n if (view[4] !== 0x00 || view[5] !== 0x00 || view[6] !== 0x00 || view[7] !== 0x00) {\n throw new Error(\"Invalid image (wrong load address)\");\n }\n\n const headerSize = view[8] + view[9] * 2 ** 8;\n\n // check protected TLV area size is 0\n if (view[10] !== 0x00 || view[11] !== 0x00) {\n throw new Error(\"Invalid image (wrong protected TLV area size)\");\n }\n\n const imageSize = view[12] + view[13] * 2 ** 8 + view[14] * 2 ** 16 + view[15] * 2 ** 24;\n info.imageSize = imageSize;\n\n // check image size is correct\n if (view.length < imageSize + headerSize) {\n throw new Error(\"Invalid image (wrong image size)\");\n }\n\n // check flags is 0x00000000\n if (view[16] !== 0x00 || view[17] !== 0x00 || view[18] !== 0x00 || view[19] !== 0x00) {\n throw new Error(\"Invalid image (wrong flags)\");\n }\n\n const version = `${view[20]}.${view[21]}.${view[22] + view[23] * 2 ** 8}`;\n info.version = version;\n\n info.hash = [...new Uint8Array(await this._hash(image.slice(0, imageSize + 32)))]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return info;\n }\n}\n","import Device, { SendSmpMessageCallback } from \"./Device.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { MCUManager, constants } from \"./utils/mcumgr.js\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FirmwareManager\", { log: true });\n\nexport const FirmwareMessageTypes = [\"smp\"] as const;\nexport type FirmwareMessageType = (typeof FirmwareMessageTypes)[number];\n\nexport const FirmwareEventTypes = [\n ...FirmwareMessageTypes,\n \"firmwareImages\",\n \"firmwareUploadProgress\",\n \"firmwareStatus\",\n \"firmwareUploadComplete\",\n] as const;\nexport type FirmwareEventType = (typeof FirmwareEventTypes)[number];\n\nexport const FirmwareStatuses = [\"idle\", \"uploading\", \"uploaded\", \"pending\", \"testing\", \"erasing\"] as const;\nexport type FirmwareStatus = (typeof FirmwareStatuses)[number];\n\nexport interface FirmwareImage {\n slot: number;\n active: boolean;\n confirmed: boolean;\n pending: boolean;\n permanent: boolean;\n bootable: boolean;\n version: string;\n hash?: Uint8Array;\n empty?: boolean;\n}\n\nexport interface FirmwareEventMessages {\n smp: { dataView: DataView };\n firmwareImages: { firmwareImages: FirmwareImage[] };\n firmwareUploadProgress: { progress: number };\n firmwareStatus: { firmwareStatus: FirmwareStatus };\n //firmwareUploadComplete: {};\n}\n\nexport type FirmwareEventDispatcher = EventDispatcher<Device, FirmwareEventType, FirmwareEventMessages>;\n\nclass FirmwareManager {\n sendMessage!: SendSmpMessageCallback;\n\n constructor() {\n this.#assignMcuManagerCallbacks();\n autoBind(this);\n }\n\n eventDispatcher!: FirmwareEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n parseMessage(messageType: FirmwareMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"smp\":\n this.#mcuManager._notification(Array.from(new Uint8Array(dataView.buffer)));\n this.#dispatchEvent(\"smp\", { dataView });\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async uploadFirmware(file: FileLike) {\n _console.log(\"uploadFirmware\", file);\n\n const promise = this.waitForEvent(\"firmwareUploadComplete\");\n\n await this.getImages();\n\n const arrayBuffer = await getFileBuffer(file);\n const imageInfo = await this.#mcuManager.imageInfo(arrayBuffer);\n _console.log({ imageInfo });\n\n this.#mcuManager.cmdUpload(arrayBuffer, 1);\n\n this.#updateStatus(\"uploading\");\n\n await promise;\n }\n\n #status: FirmwareStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #updateStatus(newStatus: FirmwareStatus) {\n _console.assertEnumWithError(newStatus, FirmwareStatuses);\n if (this.#status == newStatus) {\n _console.log(`redundant firmwareStatus assignment \"${newStatus}\"`);\n return;\n }\n\n this.#status = newStatus;\n _console.log({ firmwareStatus: this.#status });\n this.#dispatchEvent(\"firmwareStatus\", { firmwareStatus: this.#status });\n }\n\n // COMMANDS\n\n #images!: FirmwareImage[];\n get images() {\n return this.#images;\n }\n #assertImages() {\n _console.assertWithError(this.#images, \"didn't get imageState\");\n }\n #assertValidImageIndex(imageIndex: number) {\n _console.assertTypeWithError(imageIndex, \"number\");\n _console.assertWithError(imageIndex == 0 || imageIndex == 1, \"imageIndex must be 0 or 1\");\n }\n async getImages() {\n const promise = this.waitForEvent(\"firmwareImages\");\n\n _console.log(\"getting firmware image state...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageState()).buffer);\n\n await promise;\n }\n\n async testImage(imageIndex: number = 1) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (!this.#images[imageIndex]) {\n _console.log(`image ${imageIndex} not found`);\n return;\n }\n if (this.#images[imageIndex].pending == true) {\n _console.log(`image ${imageIndex} is already pending`);\n return;\n }\n if (this.#images[imageIndex].empty) {\n _console.log(`image ${imageIndex} is empty`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"testing firmware image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageTest(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async eraseImage() {\n this.#assertImages();\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"erasing image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageErase()).buffer);\n\n this.#updateStatus(\"erasing\");\n\n await promise;\n await this.getImages();\n }\n\n async confirmImage(imageIndex: number = 0) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (this.#images[imageIndex].confirmed === true) {\n _console.log(`image ${imageIndex} is already confirmed`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"confirming image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageConfirm(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async echo(string: string) {\n _console.assertTypeWithError(string, \"string\");\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"sending echo...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.smpEcho(string)).buffer);\n\n await promise;\n }\n\n async reset() {\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"resetting...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdReset()).buffer);\n\n await promise;\n }\n\n // MTU\n #mtu!: number;\n get mtu() {\n return this.#mtu;\n }\n set mtu(newMtu: number) {\n this.#mtu = newMtu;\n this.#mcuManager._mtu = newMtu;\n }\n\n // MCUManager\n #mcuManager = new MCUManager();\n\n #assignMcuManagerCallbacks() {\n this.#mcuManager.onMessage(this.#onMcuMessage.bind(this));\n\n this.#mcuManager.onFileDownloadNext(this.#onMcuFileDownloadNext);\n this.#mcuManager.onFileDownloadProgress(this.#onMcuFileDownloadProgress.bind(this));\n this.#mcuManager.onFileDownloadFinished(this.#onMcuFileDownloadFinished.bind(this));\n\n this.#mcuManager.onFileUploadNext(this.#onMcuFileUploadNext.bind(this));\n this.#mcuManager.onFileUploadProgress(this.#onMcuFileUploadProgress.bind(this));\n this.#mcuManager.onFileUploadFinished(this.#onMcuFileUploadFinished.bind(this));\n\n this.#mcuManager.onImageUploadNext(this.#onMcuImageUploadNext.bind(this));\n this.#mcuManager.onImageUploadProgress(this.#onMcuImageUploadProgress.bind(this));\n this.#mcuManager.onImageUploadFinished(this.#onMcuImageUploadFinished.bind(this));\n }\n\n #onMcuMessage({ op, group, id, data, length }: { op: number; group: number; id: number; data: any; length: number }) {\n _console.log(\"onMcuMessage\", ...arguments);\n\n switch (group) {\n case constants.MGMT_GROUP_ID_OS:\n switch (id) {\n case constants.OS_MGMT_ID_ECHO:\n _console.log(`echo \"${data.r}\"`);\n break;\n case constants.OS_MGMT_ID_TASKSTAT:\n _console.table(data.tasks);\n break;\n case constants.OS_MGMT_ID_MPSTAT:\n _console.log(data);\n break;\n }\n break;\n case constants.MGMT_GROUP_ID_IMAGE:\n switch (id) {\n case constants.IMG_MGMT_ID_STATE:\n this.#onMcuImageState(data);\n }\n break;\n default:\n throw Error(`uncaught mcuMessage group ${group}`);\n }\n }\n\n #onMcuFileDownloadNext() {\n _console.log(\"onMcuFileDownloadNext\", ...arguments);\n }\n #onMcuFileDownloadProgress() {\n _console.log(\"onMcuFileDownloadProgress\", ...arguments);\n }\n #onMcuFileDownloadFinished() {\n _console.log(\"onMcuFileDownloadFinished\", ...arguments);\n }\n\n #onMcuFileUploadNext() {\n _console.log(\"onMcuFileUploadNext\");\n }\n #onMcuFileUploadProgress() {\n _console.log(\"onMcuFileUploadProgress\");\n }\n #onMcuFileUploadFinished() {\n _console.log(\"onMcuFileUploadFinished\");\n }\n\n #onMcuImageUploadNext({ packet }: { packet: number[] }) {\n _console.log(\"onMcuImageUploadNext\");\n this.sendMessage(Uint8Array.from(packet).buffer);\n }\n #onMcuImageUploadProgress({ percentage }: { percentage: number }) {\n const progress = percentage / 100;\n _console.log(\"onMcuImageUploadProgress\", ...arguments);\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress });\n }\n async #onMcuImageUploadFinished() {\n _console.log(\"onMcuImageUploadFinished\", ...arguments);\n\n await this.getImages();\n\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress: 100 });\n this.#dispatchEvent(\"firmwareUploadComplete\", {});\n }\n\n #onMcuImageState({ images }: { images?: FirmwareImage[] }) {\n if (images) {\n this.#images = images;\n _console.log(\"images\", this.#images);\n } else {\n _console.log(\"no images found\");\n return;\n }\n\n let newStatus: FirmwareStatus = \"idle\";\n\n if (this.#images.length == 2) {\n if (!this.#images[1].bootable) {\n _console.warn('Slot 1 has a invalid image. Click \"Erase Image\" to erase it or upload a different image');\n } else if (!this.#images[0].confirmed) {\n _console.log(\n 'Slot 0 has a valid image. Click \"Confirm Image\" to confirm it or wait and the device will swap images back.'\n );\n newStatus = \"testing\";\n } else {\n if (this.#images[1].pending) {\n _console.log(\"reset to upload to the new firmware image\");\n newStatus = \"pending\";\n } else {\n _console.log(\"Slot 1 has a valid image. run testImage() to test it or upload a different image.\");\n newStatus = \"uploaded\";\n }\n }\n }\n\n if (this.#images.length == 1) {\n this.#images.push({\n slot: 1,\n empty: true,\n version: \"Empty\",\n pending: false,\n confirmed: false,\n bootable: false,\n active: false,\n permanent: false,\n });\n\n _console.log(\"Select a firmware upload image to upload to slot 1.\");\n }\n\n this.#updateStatus(newStatus);\n this.#dispatchEvent(\"firmwareImages\", { firmwareImages: this.#images });\n }\n}\n\nexport default FirmwareManager;\n","import { ConnectionStatus } from \"./connection/BaseConnectionManager.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport Device, { BoundDeviceEventListeners, DeviceEventMap } from \"./Device.ts\";\nimport { DeviceType } from \"./InformationManager.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInBluefy, isInBrowser } from \"./utils/environment.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport { addEventListeners } from \"./utils/EventUtils.ts\";\n\nconst _console = createConsole(\"DeviceManager\", { log: true });\n\nexport interface LocalStorageDeviceInformation {\n type: DeviceType;\n bluetoothId: string;\n}\n\nexport interface LocalStorageConfiguration {\n devices: LocalStorageDeviceInformation[];\n}\n\nexport const DeviceManagerEventTypes = [\n \"deviceConnected\",\n \"deviceDisconnected\",\n \"deviceIsConnected\",\n \"availableDevices\",\n \"connectedDevices\",\n] as const;\nexport type DeviceManagerEventType = (typeof DeviceManagerEventTypes)[number];\n\ninterface DeviceManagerEventMessage {\n device: Device;\n}\nexport interface DeviceManagerEventMessages {\n deviceConnected: DeviceManagerEventMessage;\n deviceDisconnected: DeviceManagerEventMessage;\n deviceIsConnected: DeviceManagerEventMessage;\n availableDevices: { availableDevices: Device[] };\n connectedDevices: { connectedDevices: Device[] };\n}\n\nexport type DeviceManagerEventDispatcher = EventDispatcher<\n DeviceManager,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEventMap = EventMap<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type DeviceManagerEventListenerMap = EventListenerMap<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEvent = Event<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type BoundDeviceManagerEventListeners = BoundEventListeners<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\n\nclass DeviceManager {\n static readonly shared = new DeviceManager();\n\n constructor() {\n if (DeviceManager.shared && this != DeviceManager.shared) {\n throw Error(\"DeviceManager is a singleton - use DeviceManager.shared\");\n }\n\n if (this.CanUseLocalStorage) {\n this.UseLocalStorage = true;\n }\n }\n\n // DEVICE LISTENERS\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n getType: this.#onDeviceType.bind(this),\n isConnected: this.#OnDeviceIsConnected.bind(this),\n };\n /** @private */\n onDevice(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n }\n\n #onDeviceType(event: DeviceEventMap[\"getType\"]) {\n if (this.#UseLocalStorage) {\n this.#UpdateLocalStorageConfigurationForDevice(event.target);\n }\n }\n\n // CONNECTION STATUS\n /** @private */\n OnDeviceConnectionStatusUpdated(device: Device, connectionStatus: ConnectionStatus) {\n if (connectionStatus == \"notConnected\" && !device.canReconnect && this.#AvailableDevices.includes(device)) {\n const deviceIndex = this.#AvailableDevices.indexOf(device);\n this.AvailableDevices.splice(deviceIndex, 1);\n this.#DispatchAvailableDevices();\n }\n }\n\n // CONNECTED DEVICES\n\n #ConnectedDevices: Device[] = [];\n get ConnectedDevices() {\n return this.#ConnectedDevices;\n }\n\n #UseLocalStorage = false;\n get UseLocalStorage() {\n return this.#UseLocalStorage;\n }\n set UseLocalStorage(newUseLocalStorage) {\n this.#AssertLocalStorage();\n _console.assertTypeWithError(newUseLocalStorage, \"boolean\");\n this.#UseLocalStorage = newUseLocalStorage;\n if (this.#UseLocalStorage && !this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n }\n\n #DefaultLocalStorageConfiguration: LocalStorageConfiguration = {\n devices: [],\n };\n #LocalStorageConfiguration?: LocalStorageConfiguration;\n\n get CanUseLocalStorage() {\n return isInBrowser && window.localStorage;\n }\n\n #AssertLocalStorage() {\n _console.assertWithError(isInBrowser, \"localStorage is only available in the browser\");\n _console.assertWithError(window.localStorage, \"localStorage not found\");\n }\n #LocalStorageKey = \"BS.Device\";\n #SaveToLocalStorage() {\n this.#AssertLocalStorage();\n localStorage.setItem(this.#LocalStorageKey, JSON.stringify(this.#LocalStorageConfiguration));\n }\n async #LoadFromLocalStorage() {\n this.#AssertLocalStorage();\n let localStorageString = localStorage.getItem(this.#LocalStorageKey);\n if (typeof localStorageString != \"string\") {\n _console.log(\"no info found in localStorage\");\n this.#LocalStorageConfiguration = Object.assign({}, this.#DefaultLocalStorageConfiguration);\n this.#SaveToLocalStorage();\n return;\n }\n try {\n const configuration = JSON.parse(localStorageString);\n _console.log({ configuration });\n this.#LocalStorageConfiguration = configuration;\n if (this.CanGetDevices) {\n await this.GetDevices(); // redundant?\n }\n } catch (error) {\n _console.error(error);\n }\n }\n\n #UpdateLocalStorageConfigurationForDevice(device: Device) {\n if (device.connectionType != \"webBluetooth\") {\n _console.log(\"localStorage is only for webBluetooth devices\");\n return;\n }\n this.#AssertLocalStorage();\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex((deviceInformation) => {\n return deviceInformation.bluetoothId == device.bluetoothId;\n });\n if (deviceInformationIndex == -1) {\n return;\n }\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex].type = device.type;\n this.#SaveToLocalStorage();\n }\n\n // AVAILABLE DEVICES\n #AvailableDevices: Device[] = [];\n get AvailableDevices() {\n return this.#AvailableDevices;\n }\n\n get CanGetDevices() {\n return isInBrowser && navigator.bluetooth?.getDevices;\n }\n /**\n * retrieves devices already connected via web bluetooth in other tabs/windows\n *\n * _only available on web-bluetooth enabled browsers_\n */\n async GetDevices(): Promise<Device[] | undefined> {\n if (!isInBrowser) {\n _console.warn(\"GetDevices is only available in the browser\");\n return;\n }\n\n if (!navigator.bluetooth) {\n _console.warn(\"bluetooth is not available in this browser\");\n return;\n }\n\n if (isInBluefy) {\n _console.warn(\"bluefy lists too many devices...\");\n return;\n }\n\n if (!navigator.bluetooth.getDevices) {\n _console.warn(\"bluetooth.getDevices() is not available in this browser\");\n return;\n }\n\n if (!this.CanGetDevices) {\n _console.log(\"CanGetDevices is false\");\n return;\n }\n\n if (!this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n\n const configuration = this.#LocalStorageConfiguration!;\n if (!configuration.devices || configuration.devices.length == 0) {\n _console.log(\"no devices found in configuration\");\n return;\n }\n\n const bluetoothDevices = await navigator.bluetooth.getDevices();\n\n _console.log({ bluetoothDevices });\n\n bluetoothDevices.forEach((bluetoothDevice) => {\n if (!bluetoothDevice.gatt) {\n return;\n }\n let deviceInformation = configuration.devices.find(\n (deviceInformation) => bluetoothDevice.id == deviceInformation.bluetoothId\n );\n if (!deviceInformation) {\n return;\n }\n\n let existingConnectedDevice = this.ConnectedDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n\n const existingAvailableDevice = this.AvailableDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n if (existingAvailableDevice) {\n if (\n existingConnectedDevice &&\n existingConnectedDevice?.bluetoothId == existingAvailableDevice.bluetoothId &&\n existingConnectedDevice != existingAvailableDevice\n ) {\n this.AvailableDevices[this.#AvailableDevices.indexOf(existingAvailableDevice)] = existingConnectedDevice;\n }\n return;\n }\n\n if (existingConnectedDevice) {\n this.AvailableDevices.push(existingConnectedDevice);\n return;\n }\n\n const device = new Device();\n const connectionManager = new WebBluetoothConnectionManager();\n connectionManager.device = bluetoothDevice;\n if (bluetoothDevice.name) {\n device._informationManager.updateName(bluetoothDevice.name);\n }\n device._informationManager.updateType(deviceInformation.type);\n device.connectionManager = connectionManager;\n this.AvailableDevices.push(device);\n });\n this.#DispatchAvailableDevices();\n return this.AvailableDevices;\n }\n\n // STATIC EVENTLISTENERS\n\n #EventDispatcher: DeviceManagerEventDispatcher = new EventDispatcher(this as DeviceManager, DeviceManagerEventTypes);\n\n get AddEventListener() {\n return this.#EventDispatcher.addEventListener;\n }\n get #DispatchEvent() {\n return this.#EventDispatcher.dispatchEvent;\n }\n get RemoveEventListener() {\n return this.#EventDispatcher.removeEventListener;\n }\n get RemoveEventListeners() {\n return this.#EventDispatcher.removeEventListeners;\n }\n get RemoveAllEventListeners() {\n return this.#EventDispatcher.removeAllEventListeners;\n }\n\n #OnDeviceIsConnected(event: DeviceEventMap[\"isConnected\"]) {\n const { target: device } = event;\n if (device.isConnected) {\n if (!this.#ConnectedDevices.includes(device)) {\n _console.log(\"adding device\", device);\n this.#ConnectedDevices.push(device);\n if (this.UseLocalStorage && device.connectionType == \"webBluetooth\") {\n const deviceInformation: LocalStorageDeviceInformation = {\n type: device.type,\n bluetoothId: device.bluetoothId!,\n };\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex(\n (_deviceInformation) => _deviceInformation.bluetoothId == deviceInformation.bluetoothId\n );\n if (deviceInformationIndex == -1) {\n this.#LocalStorageConfiguration!.devices.push(deviceInformation);\n } else {\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex] = deviceInformation;\n }\n this.#SaveToLocalStorage();\n }\n this.#DispatchEvent(\"deviceConnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already included\");\n }\n } else {\n if (this.#ConnectedDevices.includes(device)) {\n _console.log(\"removing device\", device);\n this.#ConnectedDevices.splice(this.#ConnectedDevices.indexOf(device), 1);\n this.#DispatchEvent(\"deviceDisconnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already not included\");\n }\n }\n if (this.CanGetDevices) {\n this.GetDevices();\n }\n if (device.isConnected && !this.AvailableDevices.includes(device)) {\n const existingAvailableDevice = this.AvailableDevices.find(\n (_device) => _device.bluetoothId == device.bluetoothId\n );\n _console.log({ existingAvailableDevice });\n if (existingAvailableDevice) {\n this.AvailableDevices[this.AvailableDevices.indexOf(existingAvailableDevice)] = device;\n } else {\n this.AvailableDevices.push(device);\n }\n this.#DispatchAvailableDevices();\n }\n }\n\n #DispatchAvailableDevices() {\n _console.log({ AvailableDevices: this.AvailableDevices });\n this.#DispatchEvent(\"availableDevices\", { availableDevices: this.AvailableDevices });\n }\n #DispatchConnectedDevices() {\n _console.log({ ConnectedDevices: this.ConnectedDevices });\n this.#DispatchEvent(\"connectedDevices\", { connectedDevices: this.ConnectedDevices });\n }\n}\n\nexport default DeviceManager.shared;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport BaseConnectionManager, {\n TxMessage,\n TxRxMessageType,\n ConnectionStatus,\n ConnectionMessageType,\n MetaConnectionMessageTypes,\n BatteryLevelMessageTypes,\n ConnectionEventTypes,\n ConnectionStatusEventMessages,\n} from \"./connection/BaseConnectionManager.ts\";\nimport { isInBrowser, isInNode } from \"./utils/environment.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport SensorConfigurationManager, {\n SendSensorConfigurationMessageCallback,\n SensorConfiguration,\n SensorConfigurationEventDispatcher,\n SensorConfigurationEventMessages,\n SensorConfigurationEventTypes,\n SensorConfigurationMessageType,\n SensorConfigurationMessageTypes,\n} from \"./sensor/SensorConfigurationManager.ts\";\nimport SensorDataManager, {\n SensorDataEventMessages,\n SensorDataEventTypes,\n SensorDataMessageType,\n SensorDataMessageTypes,\n SensorType,\n ContinuousSensorTypes,\n SensorDataEventDispatcher,\n} from \"./sensor/SensorDataManager.ts\";\nimport VibrationManager, {\n SendVibrationMessageCallback,\n VibrationConfiguration,\n} from \"./vibration/VibrationManager.ts\";\nimport FileTransferManager, {\n FileTransferEventTypes,\n FileTransferEventMessages,\n FileTransferEventDispatcher,\n SendFileTransferMessageCallback,\n FileTransferMessageTypes,\n FileTransferMessageType,\n FileType,\n} from \"./FileTransferManager.ts\";\nimport TfliteManager, {\n TfliteEventTypes,\n TfliteEventMessages,\n TfliteEventDispatcher,\n SendTfliteMessageCallback,\n TfliteMessageTypes,\n TfliteMessageType,\n TfliteSensorTypes,\n} from \"./TfliteManager.ts\";\nimport FirmwareManager, {\n FirmwareEventDispatcher,\n FirmwareEventMessages,\n FirmwareEventTypes,\n FirmwareMessageType,\n FirmwareMessageTypes,\n} from \"./FirmwareManager.ts\";\nimport DeviceInformationManager, {\n DeviceInformationEventDispatcher,\n DeviceInformationEventTypes,\n DeviceInformationMessageType,\n DeviceInformationMessageTypes,\n DeviceInformationEventMessages,\n} from \"./DeviceInformationManager.ts\";\nimport InformationManager, {\n DeviceType,\n InformationEventDispatcher,\n InformationEventTypes,\n InformationMessageType,\n InformationMessageTypes,\n InformationEventMessages,\n SendInformationMessageCallback,\n} from \"./InformationManager.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport DeviceManager from \"./DeviceManager.ts\";\n\nconst _console = createConsole(\"Device\", { log: true });\n\nexport const DeviceEventTypes = [\n \"connectionMessage\",\n ...ConnectionEventTypes,\n ...MetaConnectionMessageTypes,\n ...BatteryLevelMessageTypes,\n ...InformationEventTypes,\n ...DeviceInformationEventTypes,\n ...SensorConfigurationEventTypes,\n ...SensorDataEventTypes,\n ...FileTransferEventTypes,\n ...TfliteEventTypes,\n ...FirmwareEventTypes,\n] as const;\nexport type DeviceEventType = (typeof DeviceEventTypes)[number];\n\nexport interface DeviceEventMessages\n extends ConnectionStatusEventMessages,\n DeviceInformationEventMessages,\n InformationEventMessages,\n SensorDataEventMessages,\n SensorConfigurationEventMessages,\n TfliteEventMessages,\n FileTransferEventMessages,\n FirmwareEventMessages {\n batteryLevel: { batteryLevel: number };\n connectionMessage: { messageType: ConnectionMessageType; dataView: DataView };\n}\n\nexport type SendMessageCallback<MessageType extends string> = (\n messages?: { type: MessageType; data?: ArrayBuffer }[],\n sendImmediately?: boolean\n) => Promise<void>;\n\nexport type SendSmpMessageCallback = (data: ArrayBuffer) => Promise<void>;\n\nexport type DeviceEventDispatcher = EventDispatcher<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEvent = Event<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventMap = EventMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventListenerMap = EventListenerMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type BoundDeviceEventListeners = BoundEventListeners<Device, DeviceEventType, DeviceEventMessages>;\n\nexport const RequiredInformationConnectionMessages: TxRxMessageType[] = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getId\",\n \"getMtu\",\n\n \"getName\",\n \"getType\",\n \"getCurrentTime\",\n \"getSensorConfiguration\",\n \"getSensorScalars\",\n \"getPressurePositions\",\n\n \"maxFileLength\",\n \"getFileLength\",\n \"getFileChecksum\",\n \"getFileType\",\n \"fileTransferStatus\",\n\n \"getTfliteName\",\n \"getTfliteTask\",\n \"getTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n];\n\nclass Device {\n get bluetoothId() {\n return this.#connectionManager?.bluetoothId;\n }\n\n constructor() {\n this.#deviceInformationManager.eventDispatcher = this.#eventDispatcher as DeviceInformationEventDispatcher;\n\n this._informationManager.sendMessage = this.sendTxMessages as SendInformationMessageCallback;\n this._informationManager.eventDispatcher = this.#eventDispatcher as InformationEventDispatcher;\n\n this.#sensorConfigurationManager.sendMessage = this.sendTxMessages as SendSensorConfigurationMessageCallback;\n this.#sensorConfigurationManager.eventDispatcher = this.#eventDispatcher as SensorConfigurationEventDispatcher;\n\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as SensorDataEventDispatcher;\n\n this.#vibrationManager.sendMessage = this.sendTxMessages as SendVibrationMessageCallback;\n\n this.#tfliteManager.sendMessage = this.sendTxMessages as SendTfliteMessageCallback;\n this.#tfliteManager.eventDispatcher = this.#eventDispatcher as TfliteEventDispatcher;\n\n this.#fileTransferManager.sendMessage = this.sendTxMessages as SendFileTransferMessageCallback;\n this.#fileTransferManager.eventDispatcher = this.#eventDispatcher as FileTransferEventDispatcher;\n\n this.#firmwareManager.sendMessage = this.sendSmpMessage as SendSmpMessageCallback;\n this.#firmwareManager.eventDispatcher = this.#eventDispatcher as FirmwareEventDispatcher;\n\n this.addEventListener(\"getMtu\", () => {\n this.#firmwareManager.mtu = this.mtu;\n this.#fileTransferManager.mtu = this.mtu;\n this.connectionManager!.mtu = this.mtu;\n });\n DeviceManager.onDevice(this);\n if (isInBrowser) {\n window.addEventListener(\"beforeunload\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n if (isInNode) {\n /** can add more node leave handlers https://gist.github.com/hyrious/30a878f6e6a057f09db87638567cb11a */\n process.on(\"exit\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n }\n\n static #DefaultConnectionManager(): BaseConnectionManager {\n return new WebBluetoothConnectionManager();\n }\n\n #eventDispatcher: DeviceEventDispatcher = new EventDispatcher(this as Device, DeviceEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // CONNECTION MANAGER\n\n #connectionManager?: BaseConnectionManager;\n get connectionManager() {\n return this.#connectionManager;\n }\n set connectionManager(newConnectionManager) {\n if (this.connectionManager == newConnectionManager) {\n _console.log(\"same connectionManager is already assigned\");\n return;\n }\n\n if (this.connectionManager) {\n this.connectionManager.onStatusUpdated = undefined;\n this.connectionManager.onMessageReceived = undefined;\n this.connectionManager.onMessagesReceived = undefined;\n }\n if (newConnectionManager) {\n newConnectionManager.onStatusUpdated = this.#onConnectionStatusUpdated.bind(this);\n newConnectionManager.onMessageReceived = this.#onConnectionMessageReceived.bind(this);\n newConnectionManager.onMessagesReceived = this.#onConnectionMessagesReceived.bind(this);\n }\n\n this.#connectionManager = newConnectionManager;\n _console.log(\"assigned new connectionManager\", this.#connectionManager);\n }\n async #sendTxMessages(messages?: TxMessage[], sendImmediately?: boolean) {\n await this.#connectionManager?.sendTxMessages(messages, sendImmediately);\n }\n private sendTxMessages = this.#sendTxMessages.bind(this);\n\n async connect() {\n if (!this.connectionManager) {\n this.connectionManager = Device.#DefaultConnectionManager();\n }\n this.#clear();\n return this.connectionManager.connect();\n }\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n get #hasRequiredInformation() {\n return RequiredInformationConnectionMessages.every((messageType) => {\n return this.latestConnectionMessage.has(messageType);\n });\n }\n #requestRequiredInformation() {\n const messages: TxMessage[] = RequiredInformationConnectionMessages.map((messageType) => ({\n type: messageType,\n }));\n this.#sendTxMessages(messages);\n }\n\n get canReconnect() {\n return this.connectionManager?.canReconnect;\n }\n #assertCanReconnect() {\n _console.assertWithError(this.canReconnect, \"cannot reconnect to device\");\n }\n async reconnect() {\n this.#assertCanReconnect();\n this.#clear();\n return this.connectionManager?.reconnect();\n }\n\n static async Connect() {\n const device = new Device();\n await device.connect();\n return device;\n }\n\n static #ReconnectOnDisconnection = false;\n static get ReconnectOnDisconnection() {\n return this.#ReconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#ReconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n #reconnectOnDisconnection = Device.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this.#reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n #reconnectIntervalId?: NodeJS.Timeout | number;\n\n get connectionType() {\n return this.connectionManager?.type;\n }\n async disconnect() {\n this.#assertIsConnected();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.addEventListener(\n \"isConnected\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n\n return this.connectionManager!.disconnect();\n }\n\n toggleConnection() {\n if (this.isConnected) {\n this.disconnect();\n } else if (this.canReconnect) {\n this.reconnect();\n } else {\n this.connect();\n }\n }\n\n get connectionStatus(): ConnectionStatus {\n switch (this.#connectionManager?.status) {\n case \"connected\":\n return this.isConnected ? \"connected\" : \"connecting\";\n case \"notConnected\":\n case \"connecting\":\n case \"disconnecting\":\n return this.#connectionManager.status;\n default:\n return \"notConnected\";\n }\n }\n get isConnectionBusy() {\n return this.connectionStatus == \"connecting\" || this.connectionStatus == \"disconnecting\";\n }\n\n #onConnectionStatusUpdated(connectionStatus: ConnectionStatus) {\n _console.log({ connectionStatus });\n\n if (connectionStatus == \"notConnected\") {\n //this.#clear();\n\n if (this.canReconnect && this.reconnectOnDisconnection) {\n _console.log(\"starting reconnect interval...\");\n this.#reconnectIntervalId = setInterval(() => {\n _console.log(\"attempting reconnect...\");\n this.reconnect();\n }, 1000);\n }\n } else {\n if (this.#reconnectIntervalId != undefined) {\n _console.log(\"clearing reconnect interval\");\n clearInterval(this.#reconnectIntervalId);\n this.#reconnectIntervalId = undefined;\n }\n }\n\n this.#checkConnection();\n\n if (connectionStatus == \"connected\" && !this.#isConnected) {\n this.#requestRequiredInformation();\n }\n\n DeviceManager.OnDeviceConnectionStatusUpdated(this, connectionStatus);\n }\n\n #dispatchConnectionEvents(includeIsConnected: boolean = false) {\n this.#dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.#dispatchEvent(this.connectionStatus, {});\n if (includeIsConnected) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n }\n #checkConnection() {\n this.#isConnected =\n Boolean(this.connectionManager?.isConnected) &&\n this.#hasRequiredInformation &&\n this._informationManager.isCurrentTimeSet;\n\n switch (this.connectionStatus) {\n case \"connected\":\n if (this.#isConnected) {\n this.#dispatchConnectionEvents(true);\n }\n break;\n case \"notConnected\":\n this.#dispatchConnectionEvents(true);\n break;\n default:\n this.#dispatchConnectionEvents(false);\n break;\n }\n }\n\n #clear() {\n this.latestConnectionMessage.clear();\n this._informationManager.clear();\n this.#deviceInformationManager.clear();\n }\n\n #onConnectionMessageReceived(messageType: ConnectionMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n switch (messageType) {\n case \"batteryLevel\":\n const batteryLevel = dataView.getUint8(0);\n _console.log(\"received battery level\", { batteryLevel });\n this.#updateBatteryLevel(batteryLevel);\n break;\n\n default:\n if (FileTransferMessageTypes.includes(messageType as FileTransferMessageType)) {\n this.#fileTransferManager.parseMessage(messageType as FileTransferMessageType, dataView);\n } else if (TfliteMessageTypes.includes(messageType as TfliteMessageType)) {\n this.#tfliteManager.parseMessage(messageType as TfliteMessageType, dataView);\n } else if (SensorDataMessageTypes.includes(messageType as SensorDataMessageType)) {\n this.#sensorDataManager.parseMessage(messageType as SensorDataMessageType, dataView);\n } else if (FirmwareMessageTypes.includes(messageType as FirmwareMessageType)) {\n this.#firmwareManager.parseMessage(messageType as FirmwareMessageType, dataView);\n } else if (DeviceInformationMessageTypes.includes(messageType as DeviceInformationMessageType)) {\n this.#deviceInformationManager.parseMessage(messageType as DeviceInformationMessageType, dataView);\n } else if (InformationMessageTypes.includes(messageType as InformationMessageType)) {\n this._informationManager.parseMessage(messageType as InformationMessageType, dataView);\n } else if (SensorConfigurationMessageTypes.includes(messageType as SensorConfigurationMessageType)) {\n this.#sensorConfigurationManager.parseMessage(messageType as SensorConfigurationMessageType, dataView);\n } else {\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n this.latestConnectionMessage.set(messageType, dataView);\n this.#dispatchEvent(\"connectionMessage\", { messageType, dataView });\n }\n #onConnectionMessagesReceived() {\n if (!this.isConnected && this.#hasRequiredInformation) {\n this.#checkConnection();\n }\n if (this.connectionStatus == \"notConnected\") {\n return;\n }\n this.#sendTxMessages();\n }\n\n latestConnectionMessage: Map<ConnectionMessageType, DataView> = new Map();\n\n // DEVICE INFORMATION\n #deviceInformationManager = new DeviceInformationManager();\n get deviceInformation() {\n return this.#deviceInformationManager.information;\n }\n\n // BATTERY LEVEL\n #batteryLevel = 0;\n get batteryLevel() {\n return this.#batteryLevel;\n }\n #updateBatteryLevel(updatedBatteryLevel: number) {\n _console.assertTypeWithError(updatedBatteryLevel, \"number\");\n if (this.#batteryLevel == updatedBatteryLevel) {\n _console.log(`duplicate batteryLevel assignment ${updatedBatteryLevel}`);\n return;\n }\n this.#batteryLevel = updatedBatteryLevel;\n _console.log({ updatedBatteryLevel: this.#batteryLevel });\n this.#dispatchEvent(\"batteryLevel\", { batteryLevel: this.#batteryLevel });\n }\n\n // INFORMATION\n /** @private */\n _informationManager = new InformationManager();\n\n get id() {\n return this._informationManager.id;\n }\n\n get isCharging() {\n return this._informationManager.isCharging;\n }\n get batteryCurrent() {\n return this._informationManager.batteryCurrent;\n }\n get getBatteryCurrent() {\n return this._informationManager.getBatteryCurrent;\n }\n\n get name() {\n return this._informationManager.name;\n }\n get setName() {\n return this._informationManager.setName;\n }\n\n get type() {\n return this._informationManager.type;\n }\n get setType() {\n return this._informationManager.setType;\n }\n\n get isInsole() {\n return this._informationManager.isInsole;\n }\n get insoleSide() {\n return this._informationManager.insoleSide;\n }\n\n get mtu() {\n return this._informationManager.mtu;\n }\n\n // SENSOR TYPES\n get sensorTypes() {\n return Object.keys(this.sensorConfiguration) as SensorType[];\n }\n get continuousSensorTypes() {\n return ContinuousSensorTypes.filter((sensorType) => this.sensorTypes.includes(sensorType));\n }\n\n // SENSOR CONFIGURATION\n\n #sensorConfigurationManager = new SensorConfigurationManager();\n\n get sensorConfiguration() {\n return this.#sensorConfigurationManager.configuration;\n }\n\n async setSensorConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n await this.#sensorConfigurationManager.setConfiguration(newSensorConfiguration, clearRest);\n }\n\n async clearSensorConfiguration() {\n return this.#sensorConfigurationManager.clearSensorConfiguration();\n }\n\n static #ClearSensorConfigurationOnLeave = true;\n static get ClearSensorConfigurationOnLeave() {\n return this.#ClearSensorConfigurationOnLeave;\n }\n static set ClearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#ClearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n #clearSensorConfigurationOnLeave = Device.ClearSensorConfigurationOnLeave;\n get clearSensorConfigurationOnLeave() {\n return this.#clearSensorConfigurationOnLeave;\n }\n set clearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#clearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n // PRESSURE\n get numberOfPressureSensors() {\n return this.#sensorDataManager.pressureSensorDataManager.numberOfSensors;\n }\n\n // SENSOR DATA\n #sensorDataManager = new SensorDataManager();\n resetPressureRange() {\n this.#sensorDataManager.pressureSensorDataManager.resetRange();\n }\n\n // VIBRATION\n #vibrationManager = new VibrationManager();\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n this.#vibrationManager.triggerVibration(vibrationConfigurations, sendImmediately);\n }\n\n // FILE TRANSFER\n #fileTransferManager = new FileTransferManager();\n\n get maxFileLength() {\n return this.#fileTransferManager.maxLength;\n }\n\n async sendFile(fileType: FileType, file: FileLike) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.send(fileType, file);\n await promise;\n }\n async receiveFile(fileType: FileType) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.receive(fileType);\n await promise;\n }\n\n get fileTransferStatus() {\n return this.#fileTransferManager.status;\n }\n\n cancelFileTransfer() {\n this.#fileTransferManager.cancel();\n }\n\n // TFLITE\n #tfliteManager = new TfliteManager();\n\n get tfliteName() {\n return this.#tfliteManager.name;\n }\n get setTfliteName() {\n return this.#tfliteManager.setName;\n }\n\n // TFLITE MODEL CONFIG\n get tfliteTask() {\n return this.#tfliteManager.task;\n }\n get setTfliteTask() {\n return this.#tfliteManager.setTask;\n }\n get tfliteSampleRate() {\n return this.#tfliteManager.sampleRate;\n }\n get setTfliteSampleRate() {\n return this.#tfliteManager.setSampleRate;\n }\n get tfliteSensorTypes() {\n return this.#tfliteManager.sensorTypes;\n }\n get allowedTfliteSensorTypes() {\n return this.sensorTypes.filter((sensorType) => TfliteSensorTypes.includes(sensorType));\n }\n get setTfliteSensorTypes() {\n return this.#tfliteManager.setSensorTypes;\n }\n get tfliteIsReady() {\n return this.#tfliteManager.isReady;\n }\n\n // TFLITE INFERENCING\n\n get tfliteInferencingEnabled() {\n return this.#tfliteManager.inferencingEnabled;\n }\n get setTfliteInferencingEnabled() {\n return this.#tfliteManager.setInferencingEnabled;\n }\n async enableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(true);\n }\n async disableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(false);\n }\n get toggleTfliteInferencing() {\n return this.#tfliteManager.toggleInferencingEnabled;\n }\n\n // TFLITE INFERENCE CONFIG\n\n get tfliteCaptureDelay() {\n return this.#tfliteManager.captureDelay;\n }\n get setTfliteCaptureDelay() {\n return this.#tfliteManager.setCaptureDelay;\n }\n get tfliteThreshold() {\n return this.#tfliteManager.threshold;\n }\n get setTfliteThreshold() {\n return this.#tfliteManager.setThreshold;\n }\n\n // FIRMWARE MANAGER\n\n #firmwareManager = new FirmwareManager();\n\n #sendSmpMessage(data: ArrayBuffer) {\n return this.#connectionManager!.sendSmpMessage(data);\n }\n private sendSmpMessage = this.#sendSmpMessage.bind(this);\n\n get uploadFirmware() {\n return this.#firmwareManager.uploadFirmware;\n }\n async reset() {\n await this.#firmwareManager.reset();\n return this.#connectionManager!.disconnect();\n }\n get firmwareStatus() {\n return this.#firmwareManager.status;\n }\n get getFirmwareImages() {\n return this.#firmwareManager.getImages;\n }\n get firmwareImages() {\n return this.#firmwareManager.images;\n }\n get eraseFirmwareImage() {\n return this.#firmwareManager.eraseImage;\n }\n get confirmFirmwareImage() {\n return this.#firmwareManager.confirmImage;\n }\n get testFirmwareImage() {\n return this.#firmwareManager.testImage;\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n\n this.#fileTransferManager.isServerSide = this.isServerSide;\n }\n}\n\nexport default Device;\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport { PressureData } from \"../sensor/PressureSensorDataManager.ts\";\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { DeviceEventMap } from \"../Device.ts\";\n\nconst _console = createConsole(\"DevicePairPressureSensorDataManager\", { log: true });\n\nexport type DevicePairRawPressureData = { [insoleSide in InsoleSide]: PressureData };\n\nexport interface DevicePairPressureData {\n rawSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface DevicePairPressureDataEventMessage {\n pressure: DevicePairPressureData;\n}\n\nexport interface DevicePairPressureDataEventMessages {\n pressure: DevicePairPressureDataEventMessage;\n}\n\nclass DevicePairPressureSensorDataManager {\n #rawPressure: Partial<DevicePairRawPressureData> = {};\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetPressureRange() {\n this.#centerOfPressureHelper.reset();\n }\n\n onDevicePressureData(event: DeviceEventMap[\"pressure\"]) {\n const { pressure } = event.message;\n const insoleSide = event.target.insoleSide;\n _console.log({ pressure, insoleSide });\n this.#rawPressure[insoleSide] = pressure;\n if (this.#hasAllPressureData) {\n return this.#updatePressureData();\n } else {\n _console.log(\"doesn't have all pressure data yet...\");\n }\n }\n\n get #hasAllPressureData() {\n return InsoleSides.every((side) => side in this.#rawPressure);\n }\n\n #updatePressureData() {\n const pressure: DevicePairPressureData = { rawSum: 0, normalizedSum: 0 };\n\n InsoleSides.forEach((side) => {\n pressure.rawSum += this.#rawPressure[side]!.scaledSum;\n pressure.normalizedSum += this.#rawPressure[side]!.normalizedSum;\n });\n\n if (pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n InsoleSides.forEach((side) => {\n const sidePressure = this.#rawPressure[side]!;\n const normalizedPressureSumWeight = sidePressure.normalizedSum / pressure.normalizedSum;\n if (normalizedPressureSumWeight > 0) {\n if (sidePressure.normalizedCenter?.y != undefined) {\n pressure.center!.y += sidePressure.normalizedCenter.y * normalizedPressureSumWeight;\n }\n if (side == \"right\") {\n pressure.center!.x = normalizedPressureSumWeight;\n }\n }\n });\n\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ devicePairPressure: pressure });\n\n return pressure;\n }\n}\n\nexport default DevicePairPressureSensorDataManager;\n","import DevicePairPressureSensorDataManager, {\n DevicePairPressureDataEventMessages,\n} from \"./DevicePairPressureSensorDataManager.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { InsoleSide } from \"../InformationManager.ts\";\nimport { SensorType } from \"../sensor/SensorDataManager.ts\";\nimport { DeviceEventMap, SpecificDeviceEvent } from \"../Device.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport DevicePair from \"./DevicePair.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"DevicePairSensorDataManager\", { log: true });\n\nexport const DevicePairSensorTypes = [\"pressure\", \"sensorData\"] as const;\nexport type DevicePairSensorType = (typeof DevicePairSensorTypes)[number];\n\nexport const DevicePairSensorDataEventTypes = DevicePairSensorTypes;\nexport type DevicePairSensorDataEventType = (typeof DevicePairSensorDataEventTypes)[number];\n\nexport type DevicePairSensorDataTimestamps = { [insoleSide in InsoleSide]: number };\n\ninterface BaseDevicePairSensorDataEventMessage {\n timestamps: DevicePairSensorDataTimestamps;\n}\n\ntype BaseDevicePairSensorDataEventMessages = DevicePairPressureDataEventMessages;\ntype _DevicePairSensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseDevicePairSensorDataEventMessages, \"sensorType\">,\n BaseDevicePairSensorDataEventMessage\n>;\n\nexport type DevicePairSensorDataEventMessage = ValueOf<_DevicePairSensorDataEventMessages>;\ninterface AnyDevicePairSensorDataEventMessages {\n sensorData: DevicePairSensorDataEventMessage;\n}\nexport type DevicePairSensorDataEventMessages = _DevicePairSensorDataEventMessages &\n AnyDevicePairSensorDataEventMessages;\n\nexport type DevicePairSensorDataEventDispatcher = EventDispatcher<\n DevicePair,\n DevicePairSensorDataEventType,\n DevicePairSensorDataEventMessages\n>;\n\nclass DevicePairSensorDataManager {\n eventDispatcher!: DevicePairSensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #timestamps: { [sensorType in SensorType]?: Partial<DevicePairSensorDataTimestamps> } = {};\n\n pressureSensorDataManager = new DevicePairPressureSensorDataManager();\n resetPressureRange() {\n this.pressureSensorDataManager.resetPressureRange();\n }\n\n onDeviceSensorData(event: DeviceEventMap[\"sensorData\"]) {\n const { timestamp, sensorType } = event.message;\n\n _console.log({ sensorType, timestamp, event });\n\n if (!this.#timestamps[sensorType]) {\n this.#timestamps[sensorType] = {};\n }\n this.#timestamps[sensorType]![event.target.insoleSide] = timestamp;\n\n let value;\n switch (sensorType) {\n case \"pressure\":\n value = this.pressureSensorDataManager.onDevicePressureData(event as unknown as DeviceEventMap[\"pressure\"]);\n break;\n default:\n _console.log(`uncaught sensorType \"${sensorType}\"`);\n break;\n }\n\n if (value) {\n const timestamps = Object.assign({}, this.#timestamps[sensorType]) as DevicePairSensorDataTimestamps;\n // @ts-expect-error\n this.dispatchEvent(sensorType as DevicePairSensorDataEventType, { sensorType, timestamps, [sensorType]: value });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, timestamps, [sensorType]: value });\n } else {\n _console.log(\"no value received\");\n }\n }\n}\n\nexport default DevicePairSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"../utils/EventDispatcher.ts\";\nimport { addEventListeners, removeEventListeners } from \"../utils/EventUtils.ts\";\nimport Device, {\n DeviceEvent,\n DeviceEventType,\n DeviceEventMessages,\n DeviceEventTypes,\n BoundDeviceEventListeners,\n DeviceEventMap,\n} from \"../Device.ts\";\nimport DevicePairSensorDataManager, { DevicePairSensorDataEventDispatcher } from \"./DevicePairSensorDataManager.ts\";\nimport { capitalizeFirstCharacter } from \"../utils/stringUtils.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { VibrationConfiguration } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfiguration } from \"../sensor/SensorConfigurationManager.ts\";\nimport { DevicePairSensorDataEventMessages, DevicePairSensorDataEventTypes } from \"./DevicePairSensorDataManager.ts\";\nimport { AddPrefixToInterfaceKeys, ExtendInterfaceValues, KeyOf } from \"../utils/TypeScriptUtils.ts\";\nimport DeviceManager from \"../DeviceManager.ts\";\n\nconst _console = createConsole(\"DevicePair\", { log: true });\n\ninterface BaseDevicePairDeviceEventMessage {\n device: Device;\n side: InsoleSide;\n}\ntype DevicePairDeviceEventMessages = ExtendInterfaceValues<\n AddPrefixToInterfaceKeys<DeviceEventMessages, \"device\">,\n BaseDevicePairDeviceEventMessage\n>;\ntype DevicePairDeviceEventType = KeyOf<DevicePairDeviceEventMessages>;\nfunction getDevicePairDeviceEventType(deviceEventType: DeviceEventType) {\n return `device${capitalizeFirstCharacter(deviceEventType)}` as DevicePairDeviceEventType;\n}\nconst DevicePairDeviceEventTypes = DeviceEventTypes.map((eventType) =>\n getDevicePairDeviceEventType(eventType)\n) as DevicePairDeviceEventType[];\n\nexport const DevicePairConnectionEventTypes = [\"isConnected\"] as const;\nexport type DevicePairConnectionEventType = (typeof DevicePairConnectionEventTypes)[number];\n\nexport interface DevicePairConnectionEventMessages {\n isConnected: { isConnected: boolean };\n}\n\nexport const DevicePairEventTypes = [\n ...DevicePairConnectionEventTypes,\n ...DevicePairSensorDataEventTypes,\n ...DevicePairDeviceEventTypes,\n] as const;\nexport type DevicePairEventType = (typeof DevicePairEventTypes)[number];\n\nexport type DevicePairEventMessages = DevicePairConnectionEventMessages &\n DevicePairSensorDataEventMessages &\n DevicePairDeviceEventMessages;\n\nexport type DevicePairEventDispatcher = EventDispatcher<DevicePair, DevicePairEventType, DevicePairEventMessages>;\nexport type DevicePairEventMap = EventMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEventListenerMap = EventListenerMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEvent = Event<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type BoundDevicePairEventListeners = BoundEventListeners<DevicePair, DeviceEventType, DevicePairEventMessages>;\n\nclass DevicePair {\n constructor() {\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as DevicePairSensorDataEventDispatcher;\n }\n\n get sides() {\n return InsoleSides;\n }\n\n #eventDispatcher: DevicePairEventDispatcher = new EventDispatcher(this as DevicePair, DevicePairEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // SIDES\n #left?: Device;\n get left() {\n return this.#left;\n }\n\n #right?: Device;\n get right() {\n return this.#right;\n }\n\n get isConnected() {\n return InsoleSides.every((side) => this[side]?.isConnected);\n }\n get isPartiallyConnected() {\n return InsoleSides.some((side) => this[side]?.isConnected);\n }\n get isHalfConnected() {\n return this.isPartiallyConnected && !this.isConnected;\n }\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"devicePair must be connected\");\n }\n\n assignInsole(device: Device) {\n if (!device.isInsole) {\n _console.warn(\"device is not an insole\");\n return;\n }\n const side = device.insoleSide;\n\n const currentDevice = this[side];\n\n if (device == currentDevice) {\n _console.log(\"device already assigned\");\n return;\n }\n\n if (currentDevice) {\n this.#removeDeviceEventListeners(currentDevice);\n }\n this.#addDeviceEventListeners(device);\n\n switch (side) {\n case \"left\":\n this.#left = device;\n break;\n case \"right\":\n this.#right = device;\n break;\n }\n\n _console.log(`assigned ${side} insole`, device);\n\n this.resetPressureRange();\n\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n this.#dispatchEvent(\"deviceIsConnected\", { device, isConnected: device.isConnected, side });\n\n return currentDevice;\n }\n\n #addDeviceEventListeners(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.addEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n #removeDeviceEventListeners(device: Device) {\n removeEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.removeEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n\n #removeInsole(device: Device) {\n const foundDevice = InsoleSides.some((side) => {\n if (this[side] != device) {\n return false;\n }\n\n _console.log(`removing ${side} insole`, device);\n removeEventListeners(device, this.#boundDeviceEventListeners);\n delete this[side];\n\n return true;\n });\n if (foundDevice) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n return foundDevice;\n }\n\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n isConnected: this.#onDeviceIsConnected.bind(this),\n sensorData: this.#onDeviceSensorData.bind(this),\n getType: this.#onDeviceType.bind(this),\n };\n\n #redispatchDeviceEvent(deviceEvent: DeviceEvent) {\n const { type, target: device, message } = deviceEvent;\n this.#dispatchEvent(getDevicePairDeviceEventType(type), {\n ...message,\n device,\n side: device.insoleSide,\n });\n }\n\n #onDeviceIsConnected(deviceEvent: DeviceEventMap[\"isConnected\"]) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n\n #onDeviceType(deviceEvent: DeviceEventMap[\"getType\"]) {\n const { target: device } = deviceEvent;\n if (this[device.insoleSide] == device) {\n return;\n }\n const foundDevice = this.#removeInsole(device);\n if (!foundDevice) {\n return;\n }\n this.assignInsole(device);\n }\n\n // SENSOR CONFIGURATION\n async setSensorConfiguration(sensorConfiguration: SensorConfiguration) {\n for (let i = 0; i < InsoleSides.length; i++) {\n const side = InsoleSides[i];\n if (this[side]?.isConnected) {\n await this[side].setSensorConfiguration(sensorConfiguration);\n }\n }\n }\n\n // SENSOR DATA\n #sensorDataManager = new DevicePairSensorDataManager();\n #onDeviceSensorData(deviceEvent: DeviceEventMap[\"sensorData\"]) {\n if (this.isConnected) {\n this.#sensorDataManager.onDeviceSensorData(deviceEvent);\n }\n }\n resetPressureRange() {\n this.#sensorDataManager.resetPressureRange();\n }\n\n // VIBRATION\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n const promises = InsoleSides.map((side) => {\n return this[side]?.triggerVibration(vibrationConfigurations, sendImmediately);\n }).filter(Boolean);\n return Promise.allSettled(promises);\n }\n\n // SHARED INSTANCE\n static #shared = new DevicePair();\n static get shared() {\n return this.#shared;\n }\n static {\n DeviceManager.AddEventListener(\"deviceConnected\", (event) => {\n const { device } = event.message;\n if (device.isInsole) {\n this.#shared.assignInsole(device);\n }\n });\n }\n}\n\nexport default DevicePair;\n","import { DeviceEventTypes } from \"../Device.ts\";\nimport { ConnectionMessageType, ConnectionMessageTypes } from \"../connection/BaseConnectionManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\n\nconst _console = createConsole(\"ServerUtils\", { log: false });\n\nexport const ServerMessageTypes = [\n \"isScanningAvailable\",\n \"isScanning\",\n \"startScan\",\n \"stopScan\",\n \"discoveredDevice\",\n \"discoveredDevices\",\n \"expiredDiscoveredDevice\",\n \"connectToDevice\",\n \"disconnectFromDevice\",\n \"connectedDevices\",\n \"deviceMessage\",\n] as const;\nexport type ServerMessageType = (typeof ServerMessageTypes)[number];\n\nexport const DeviceMessageTypes = [\"connectionStatus\", \"batteryLevel\", \"deviceInformation\", \"rx\", \"smp\"] as const;\nexport type DeviceMessageType = (typeof DeviceMessageTypes)[number];\n\n// MESSAGING\n\nexport type MessageLike = number | number[] | ArrayBufferLike | DataView | boolean | string | any;\n\nexport interface Message<MessageType extends string> {\n type: MessageType;\n data?: MessageLike | MessageLike[];\n}\n\nexport function createMessage<MessageType extends string>(\n enumeration: readonly MessageType[],\n ...messages: (Message<MessageType> | MessageType)[]\n) {\n _console.log(\"createMessage\", ...messages);\n\n const messageBuffers = messages.map((message) => {\n if (typeof message == \"string\") {\n message = { type: message };\n }\n\n if (message.data != undefined) {\n if (!Array.isArray(message.data)) {\n message.data = [message.data];\n }\n } else {\n message.data = [];\n }\n\n const messageDataArrayBuffer = concatenateArrayBuffers(...message.data);\n const messageDataArrayBufferByteLength = messageDataArrayBuffer.byteLength;\n\n _console.assertEnumWithError(message.type, enumeration);\n const messageTypeEnum = enumeration.indexOf(message.type);\n\n const messageDataLengthDataView = new DataView(new ArrayBuffer(2));\n messageDataLengthDataView.setUint16(0, messageDataArrayBufferByteLength, true);\n\n return concatenateArrayBuffers(messageTypeEnum, messageDataLengthDataView, messageDataArrayBuffer);\n });\n _console.log(\"messageBuffers\", ...messageBuffers);\n return concatenateArrayBuffers(...messageBuffers);\n}\n\nexport type ServerMessage = ServerMessageType | Message<ServerMessageType>;\nexport function createServerMessage(...messages: ServerMessage[]) {\n _console.log(\"createServerMessage\", ...messages);\n return createMessage(ServerMessageTypes, ...messages);\n}\n\nexport type DeviceMessage = DeviceEventType | Message<DeviceEventType>;\nexport function createDeviceMessage(...messages: DeviceMessage[]) {\n _console.log(\"createDeviceMessage\", ...messages);\n return createMessage(DeviceEventTypes, ...messages);\n}\n\nexport type ClientDeviceMessage = ConnectionMessageType | Message<ConnectionMessageType>;\nexport function createClientDeviceMessage(...messages: ClientDeviceMessage[]) {\n _console.log(\"createClientDeviceMessage\", ...messages);\n return createMessage(ConnectionMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const isScanningAvailableRequestMessage = createServerMessage(\"isScanningAvailable\");\nexport const isScanningRequestMessage = createServerMessage(\"isScanning\");\nexport const startScanRequestMessage = createServerMessage(\"startScan\");\nexport const stopScanRequestMessage = createServerMessage(\"stopScan\");\nexport const discoveredDevicesMessage = createServerMessage(\"discoveredDevices\");\n","import { createConsole } from \"../utils/Console.ts\";\nimport { isInBrowser } from \"../utils/environment.ts\";\nimport BaseConnectionManager, { ConnectionType, ConnectionMessageType } from \"./BaseConnectionManager.ts\";\nimport { DeviceEventTypes } from \"../Device.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\nimport { ClientDeviceMessage } from \"../server/ServerUtils.ts\";\n\nconst _console = createConsole(\"ClientConnectionManager\", { log: true });\n\nexport type SendClientMessageCallback = (...messages: ClientDeviceMessage[]) => void;\n\nconst ClientDeviceInformationMessageTypes: ConnectionMessageType[] = [...DeviceInformationMessageTypes, \"batteryLevel\"];\n\nclass ClientConnectionManager extends BaseConnectionManager {\n static get isSupported() {\n return isInBrowser;\n }\n static get type(): ConnectionType {\n return \"client\";\n }\n\n #bluetoothId!: string;\n get bluetoothId() {\n return this.#bluetoothId!;\n }\n set bluetoothId(newBluetoothId) {\n _console.assertTypeWithError(newBluetoothId, \"string\");\n if (this.#bluetoothId == newBluetoothId) {\n _console.log(\"redundant bluetoothId assignment\");\n return;\n }\n this.#bluetoothId = newBluetoothId;\n }\n\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n set isConnected(newIsConnected) {\n _console.assertTypeWithError(newIsConnected, \"boolean\");\n if (this.#isConnected == newIsConnected) {\n _console.log(\"redundant newIsConnected assignment\", newIsConnected);\n return;\n }\n this.#isConnected = newIsConnected;\n\n this.status = this.#isConnected ? \"connected\" : \"notConnected\";\n\n if (this.isConnected) {\n this.#requestDeviceInformation();\n }\n }\n\n async connect() {\n await super.connect();\n this.sendClientConnectMessage();\n }\n async disconnect() {\n await super.disconnect();\n this.sendClientDisconnectMessage();\n }\n\n get canReconnect() {\n return true;\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.connect();\n }\n\n sendClientMessage!: SendClientMessageCallback;\n sendClientConnectMessage!: Function;\n sendClientDisconnectMessage!: Function;\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n this.sendClientMessage({ type: \"smp\", data });\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n this.sendClientMessage({ type: \"tx\", data });\n }\n\n #requestDeviceInformation() {\n this.sendClientMessage(...ClientDeviceInformationMessageTypes);\n }\n\n onClientMessage(dataView: DataView) {\n _console.log({ dataView });\n parseMessage(dataView, DeviceEventTypes, this.#onClientMessageCallback.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onClientMessageCallback(messageType: DeviceEventType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isConnected\":\n const isConnected = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isConnected });\n this.isConnected = isConnected;\n break;\n\n case \"rx\":\n this.parseRxMessage(dataView);\n break;\n\n default:\n this.onMessageReceived!(messageType as ConnectionMessageType, dataView);\n break;\n }\n }\n}\n\nexport default ClientConnectionManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport {\n ServerMessageTypes,\n discoveredDevicesMessage,\n ServerMessage,\n MessageLike,\n ClientDeviceMessage,\n createClientDeviceMessage,\n ServerMessageType,\n} from \"./ServerUtils.ts\";\nimport { parseMessage, parseStringFromDataView } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher, { BoundEventListeners, Event } from \"../utils/EventDispatcher.ts\";\nimport Device from \"../Device.ts\";\nimport { sliceDataView } from \"../utils/ArrayBufferUtils.ts\";\nimport { DiscoveredDevice, DiscoveredDevicesMap, ScannerEventMessages } from \"../scanner/BaseScanner.ts\";\nimport ClientConnectionManager from \"../connection/ClientConnectionManager.ts\";\n\nconst _console = createConsole(\"BaseClient\", { log: true });\n\nexport const ClientConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ClientConnectionStatus = (typeof ClientConnectionStatuses)[number];\n\nexport const ClientEventTypes = [\n ...ClientConnectionStatuses,\n \"connectionStatus\",\n \"isConnected\",\n \"isScanningAvailable\",\n \"isScanning\",\n \"discoveredDevice\",\n \"expiredDiscoveredDevice\",\n] as const;\nexport type ClientEventType = (typeof ClientEventTypes)[number];\n\ninterface ClientConnectionEventMessages {\n connectionStatus: { connectionStatus: ClientConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport type ClientEventMessages = ClientConnectionEventMessages & ScannerEventMessages;\n\nexport type ClientEventDispatcher = EventDispatcher<BaseClient, ClientEventType, ClientEventMessages>;\nexport type ClientEvent = Event<BaseClient, ClientEventType, ClientEventMessages>;\nexport type BoundClientEventListeners = BoundEventListeners<BaseClient, ClientEventType, ClientEventMessages>;\n\nexport type ServerURL = string | URL;\n\ntype DevicesMap = { [deviceId: string]: Device };\n\nabstract class BaseClient {\n protected get baseConstructor() {\n return this.constructor as typeof BaseClient;\n }\n\n #reset() {\n this.#isScanningAvailable = false;\n this.#isScanning = false;\n for (const id in this.#devices) {\n const device = this.#devices[id];\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n //device.removeAllEventListeners();\n }\n //this.#devices = {};\n }\n\n // DEVICES\n #devices: DevicesMap = {};\n get devices(): Readonly<DevicesMap> {\n return this.#devices;\n }\n\n #eventDispatcher: ClientEventDispatcher = new EventDispatcher(this as BaseClient, ClientEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n protected get dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n\n abstract isConnected: boolean;\n protected assertConnection() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n abstract isDisconnected: boolean;\n protected assertDisconnection() {\n _console.assertWithError(this.isDisconnected, \"not disconnected\");\n }\n\n abstract connect(): void;\n abstract disconnect(): void;\n abstract reconnect(): void;\n abstract toggleConnection(url?: ServerURL): void;\n\n static _reconnectOnDisconnection = true;\n static get ReconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n protected _reconnectOnDisconnection = this.baseConstructor.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n abstract sendServerMessage(...messages: ServerMessage[]): void;\n\n // CONNECTION STATUS\n #_connectionStatus: ClientConnectionStatus = \"notConnected\";\n protected get _connectionStatus() {\n return this.#_connectionStatus;\n }\n protected set _connectionStatus(newConnectionStatus) {\n _console.assertTypeWithError(newConnectionStatus, \"string\");\n _console.log({ newConnectionStatus });\n this.#_connectionStatus = newConnectionStatus;\n\n this.dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.dispatchEvent(this.connectionStatus, {});\n\n switch (newConnectionStatus) {\n case \"connected\":\n case \"notConnected\":\n this.dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n if (this.isConnected) {\n this.sendServerMessage(\"isScanningAvailable\", \"discoveredDevices\", \"connectedDevices\");\n } else {\n this.#reset();\n }\n break;\n }\n }\n get connectionStatus() {\n return this._connectionStatus;\n }\n\n protected parseMessage(dataView: DataView) {\n _console.log(\"parseMessage\", { dataView });\n parseMessage(dataView, ServerMessageTypes, this.#parseMessageCallback.bind(this), null, true);\n }\n\n #parseMessageCallback(messageType: ServerMessageType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isScanningAvailable\":\n {\n const isScanningAvailable = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanningAvailable });\n this.#isScanningAvailable = isScanningAvailable;\n }\n break;\n case \"isScanning\":\n {\n const isScanning = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanning });\n this.#isScanning = isScanning;\n }\n break;\n case \"discoveredDevice\":\n {\n const { string: discoveredDeviceString } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ discoveredDeviceString });\n\n const discoveredDevice: DiscoveredDevice = JSON.parse(discoveredDeviceString);\n _console.log({ discoveredDevice });\n\n this.onDiscoveredDevice(discoveredDevice);\n }\n break;\n case \"expiredDiscoveredDevice\":\n {\n const { string: bluetoothId } = parseStringFromDataView(dataView, byteOffset);\n this.#onExpiredDiscoveredDevice(bluetoothId);\n }\n break;\n case \"connectedDevices\":\n {\n if (dataView.byteLength == 0) {\n break;\n }\n const { string: connectedBluetoothDeviceIdStrings } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ connectedBluetoothDeviceIdStrings });\n const connectedBluetoothDeviceIds = JSON.parse(connectedBluetoothDeviceIdStrings).connectedDevices;\n _console.log({ connectedBluetoothDeviceIds });\n this.onConnectedBluetoothDeviceIds(connectedBluetoothDeviceIds);\n }\n break;\n case \"deviceMessage\":\n {\n const { string: bluetoothId, byteOffset: _byteOffset } = parseStringFromDataView(dataView, byteOffset);\n byteOffset = _byteOffset;\n const device = this.#devices[bluetoothId];\n _console.assertWithError(device, `no device found for id ${bluetoothId}`);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n const _dataView = sliceDataView(dataView, byteOffset);\n connectionManager.onClientMessage(_dataView);\n }\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // SCANNING\n #_isScanningAvailable = false;\n get #isScanningAvailable() {\n return this.#_isScanningAvailable;\n }\n set #isScanningAvailable(newIsAvailable) {\n _console.assertTypeWithError(newIsAvailable, \"boolean\");\n this.#_isScanningAvailable = newIsAvailable;\n this.dispatchEvent(\"isScanningAvailable\", { isScanningAvailable: this.isScanningAvailable });\n if (this.isScanningAvailable) {\n this.#requestIsScanning();\n }\n }\n get isScanningAvailable() {\n return this.#isScanningAvailable;\n }\n #assertIsScanningAvailable() {\n this.assertConnection();\n _console.assertWithError(this.isScanningAvailable, \"scanning is not available\");\n }\n protected requestIsScanningAvailable() {\n this.sendServerMessage(\"isScanningAvailable\");\n }\n\n #_isScanning = false;\n get #isScanning() {\n return this.#_isScanning;\n }\n set #isScanning(newIsScanning) {\n _console.assertTypeWithError(newIsScanning, \"boolean\");\n this.#_isScanning = newIsScanning;\n this.dispatchEvent(\"isScanning\", { isScanning: this.isScanning });\n }\n get isScanning() {\n return this.#isScanning;\n }\n #requestIsScanning() {\n this.sendServerMessage(\"isScanning\");\n }\n\n #assertIsScanning() {\n _console.assertWithError(this.isScanning, \"is not scanning\");\n }\n #assertIsNotScanning() {\n _console.assertWithError(!this.isScanning, \"is already scanning\");\n }\n\n startScan() {\n this.#assertIsNotScanning();\n this.sendServerMessage(\"startScan\");\n }\n stopScan() {\n this.#assertIsScanning();\n this.sendServerMessage(\"stopScan\");\n }\n toggleScan() {\n this.#assertIsScanningAvailable();\n\n if (this.isScanning) {\n this.stopScan();\n } else {\n this.startScan();\n }\n }\n\n // PERIPHERALS\n #discoveredDevices: DiscoveredDevicesMap = {};\n get discoveredDevices(): Readonly<DiscoveredDevicesMap> {\n return this.#discoveredDevices;\n }\n\n protected onDiscoveredDevice(discoveredDevice: DiscoveredDevice) {\n _console.log({ discoveredDevice });\n this.#discoveredDevices[discoveredDevice.bluetoothId] = discoveredDevice;\n this.dispatchEvent(\"discoveredDevice\", { discoveredDevice });\n }\n requestDiscoveredDevices() {\n this.sendServerMessage({ type: \"discoveredDevices\" });\n }\n #onExpiredDiscoveredDevice(bluetoothId: string) {\n _console.log({ expiredBluetoothDeviceId: bluetoothId });\n const discoveredDevice = this.#discoveredDevices[bluetoothId];\n if (!discoveredDevice) {\n _console.warn(`no discoveredDevice found with id \"${bluetoothId}\"`);\n return;\n }\n _console.log({ expiredDiscoveredDevice: discoveredDevice });\n delete this.#discoveredDevices[bluetoothId];\n this.dispatchEvent(\"expiredDiscoveredDevice\", { discoveredDevice });\n }\n\n // DEVICE CONNECTION\n connectToDevice(bluetoothId: string) {\n return this.requestConnectionToDevice(bluetoothId);\n }\n protected requestConnectionToDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.#getOrCreateDevice(bluetoothId);\n device.connect();\n return device;\n }\n protected sendConnectToDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"connectToDevice\", data: bluetoothId });\n }\n\n // DEVICE CONNECTION\n createDevice(bluetoothId: string) {\n const device = new Device();\n const clientConnectionManager = new ClientConnectionManager();\n clientConnectionManager.bluetoothId = bluetoothId;\n clientConnectionManager.sendClientMessage = this.sendDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientConnectMessage = this.sendConnectToDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientDisconnectMessage = this.sendDisconnectFromDeviceMessage.bind(this, bluetoothId);\n device.connectionManager = clientConnectionManager;\n return device;\n }\n\n #getOrCreateDevice(bluetoothId: string) {\n let device = this.#devices[bluetoothId];\n if (!device) {\n device = this.createDevice(bluetoothId);\n this.#devices[bluetoothId] = device;\n }\n return device;\n }\n protected onConnectedBluetoothDeviceIds(bluetoothIds: string[]) {\n _console.log({ bluetoothIds });\n bluetoothIds.forEach((bluetoothId) => {\n const device = this.#getOrCreateDevice(bluetoothId);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = true;\n });\n }\n\n disconnectFromDevice(bluetoothId: string) {\n this.requestDisconnectionFromDevice(bluetoothId);\n }\n protected requestDisconnectionFromDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.devices[bluetoothId];\n _console.assertWithError(device, `no device found with id ${bluetoothId}`);\n device.disconnect();\n return device;\n }\n protected sendDisconnectFromDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"disconnectFromDevice\", data: bluetoothId });\n }\n\n protected sendDeviceMessage(bluetoothId: string, ...messages: ClientDeviceMessage[]) {\n this.sendServerMessage({\n type: \"deviceMessage\",\n data: [bluetoothId, createClientDeviceMessage(...messages)],\n });\n }\n}\n\nexport default BaseClient;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createMessage, Message } from \"../ServerUtils.ts\";\n\nconst _console = createConsole(\"WebSocketUtils\", { log: false });\n\nexport const webSocketPingTimeout = 30_000_000;\nexport const webSocketReconnectTimeout = 3_000;\n\nexport const WebSocketMessageTypes = [\"ping\", \"pong\", \"serverMessage\"] as const;\nexport type WebSocketMessageType = (typeof WebSocketMessageTypes)[number];\n\nexport type WebSocketMessage = WebSocketMessageType | Message<WebSocketMessageType>;\nexport function createWebSocketMessage(...messages: WebSocketMessage[]) {\n _console.log(\"createWebSocketMessage\", ...messages);\n return createMessage(WebSocketMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const webSocketPingMessage = createWebSocketMessage(\"ping\");\nexport const webSocketPongMessage = createWebSocketMessage(\"pong\");\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createServerMessage, MessageLike, ServerMessage } from \"../ServerUtils.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport ClientConnectionManager from \"../../connection/ClientConnectionManager.ts\";\nimport BaseClient, { ServerURL } from \"../BaseClient.ts\";\nimport type * as ws from \"ws\";\nimport Timer from \"../../utils/Timer.ts\";\nimport {\n createWebSocketMessage,\n WebSocketMessageType,\n WebSocketMessageTypes,\n webSocketPingTimeout,\n webSocketReconnectTimeout,\n WebSocketMessage,\n} from \"./WebSocketUtils.ts\";\nimport { parseMessage } from \"../../utils/ParseUtils.ts\";\n\nconst _console = createConsole(\"WebSocketClient\", { log: true });\n\nclass WebSocketClient extends BaseClient {\n // WEBSOCKET\n #webSocket?: WebSocket;\n get webSocket() {\n return this.#webSocket;\n }\n set webSocket(newWebSocket) {\n if (this.#webSocket == newWebSocket) {\n _console.log(\"redundant webSocket assignment\");\n return;\n }\n\n _console.log(\"assigning webSocket\", newWebSocket);\n\n if (this.#webSocket) {\n removeEventListeners(this.#webSocket, this.#boundWebSocketEventListeners);\n }\n\n addEventListeners(newWebSocket, this.#boundWebSocketEventListeners);\n this.#webSocket = newWebSocket;\n\n _console.log(\"assigned webSocket\");\n }\n get readyState() {\n return this.webSocket?.readyState;\n }\n get isConnected() {\n return this.readyState == WebSocket.OPEN;\n }\n get isDisconnected() {\n return this.readyState == WebSocket.CLOSED;\n }\n\n connect(url: string | URL = `wss://${location.host}`) {\n if (this.webSocket) {\n this.assertDisconnection();\n }\n this._connectionStatus = \"connecting\";\n this.webSocket = new WebSocket(url);\n }\n\n disconnect() {\n this.assertConnection();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.webSocket!.addEventListener(\n \"close\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n this._connectionStatus = \"disconnecting\";\n this.webSocket!.close();\n }\n\n reconnect() {\n this.assertDisconnection();\n this.connect(this.webSocket!.url);\n }\n\n toggleConnection(url?: ServerURL) {\n if (this.isConnected) {\n this.disconnect();\n } else if (url && this.webSocket?.url == url) {\n this.reconnect();\n } else {\n this.connect(url);\n }\n }\n\n // WEBSOCKET MESSAGING\n sendMessage(message: MessageLike) {\n this.assertConnection();\n this.#webSocket!.send(message);\n }\n\n sendServerMessage(...messages: ServerMessage[]) {\n this.sendMessage(createWebSocketMessage({ type: \"serverMessage\", data: createServerMessage(...messages) }));\n }\n\n #sendWebSocketMessage(...messages: WebSocketMessage[]) {\n this.sendMessage(createWebSocketMessage(...messages));\n }\n\n // WEBSOCKET EVENTS\n #boundWebSocketEventListeners: { [eventType: string]: Function } = {\n open: this.#onWebSocketOpen.bind(this),\n message: this.#onWebSocketMessage.bind(this),\n close: this.#onWebSocketClose.bind(this),\n error: this.#onWebSocketError.bind(this),\n };\n\n #onWebSocketOpen(event: ws.Event) {\n _console.log(\"webSocket.open\", event);\n this.#pingTimer.start();\n this._connectionStatus = \"connected\";\n }\n async #onWebSocketMessage(event: ws.MessageEvent) {\n _console.log(\"webSocket.message\", event);\n this.#pingTimer.restart();\n //@ts-expect-error\n const arrayBuffer = await event.data.arrayBuffer();\n const dataView = new DataView(arrayBuffer);\n this.#parseWebSocketMessage(dataView);\n }\n #onWebSocketClose(event: ws.CloseEvent) {\n _console.log(\"webSocket.close\", event);\n\n this._connectionStatus = \"notConnected\";\n\n Object.entries(this.devices).forEach(([id, device]) => {\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n });\n\n this.#pingTimer.stop();\n if (this.reconnectOnDisconnection) {\n setTimeout(() => {\n this.reconnect();\n }, webSocketReconnectTimeout);\n }\n }\n #onWebSocketError(event: ws.ErrorEvent) {\n _console.error(\"webSocket.error\", event.message);\n }\n\n // PARSING\n #parseWebSocketMessage(dataView: DataView) {\n parseMessage(dataView, WebSocketMessageTypes, this.#onServerMessage.bind(this), null, true);\n }\n\n #onServerMessage(messageType: WebSocketMessageType, dataView: DataView) {\n switch (messageType) {\n case \"ping\":\n this.#pong();\n break;\n case \"pong\":\n break;\n case \"serverMessage\":\n this.parseMessage(dataView);\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // PING\n #pingTimer = new Timer(this.#ping.bind(this), webSocketPingTimeout);\n #ping() {\n this.#sendWebSocketMessage(\"ping\");\n }\n #pong() {\n this.#sendWebSocketMessage(\"pong\");\n }\n}\n\nexport default WebSocketClient;\n"],"names":[],"mappings":"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CE;;AAEF;;AAEA;AACF;AAEE;AACA;;AAEE;;AAEF;AACA;AACA;AACF;AAGE;AACA;AACF;;;AAKE;;;AAGA;AACA;AACA;AACF;;;ACnQF;AAKO;AAGA;AAiBA;AACL;AACA;AACA;AACA;AACA;AACA;;AAIK;AAGA;AAGA;AAGA;AACL;AACA;AACA;AACA;AACA;;AAQF;AAYE;;;AAGA;AACE;;AAEF;AACE;;AAIF;AACE;;AAQF;;AAIA;AA4EA;AACA;AAuEA;AAvJE;;AAIF;;;;AAIE;AACA;AACE;;;AAGF;AACA;AACA;AAEA;AACE;;;AAEA;;AAGF;AACE;;;AAIJ;AACE;;AAyBF;AACE;AACA;AACA;;AAEF;AACE;;AAEF;AACE;AACA;;;AAGF;AACE;AACA;AACA;AACA;;;AAIA;AACA;;AAKF;AACE;;;;;;;AAUA;;;AAGA;AAEA;;;;;AAME;;AAEF;AAEA;AACE;;;AAGE;AACE;AACE;;AAEF;AACA;AACF;;;AAIA;AACA;AACA;;;;AAGF;AACA;AACA;;AAEF;AAEA;;;AAMA;;AAGF;AACE;;;AAiBH;;AA9LG;AACF;AA0BE;AACF;;AAsCA;;AAIA;;AAIA;;AAIA;AAGE;AACA;AACF;;AAgGE;AACF;AAKE;AACE;AACA;;AAEJ;;AC7PI;AACJ;AACF;;ACPA;AAqBgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;AAEgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;;ACrCA;AAOA;AACE;AACF;AAGA;AACE;;;AAGF;AAEA;AACE;AACF;AAEA;AACE;AACF;AAgBA;AACE;AACE;AACE;AACA;AACE;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACF;AACF;AACD;AACE;AACA;AACE;AACE;AACD;AACF;AACF;AACD;AACE;AACA;;;AAGC;AACF;AACD;AACE;AACA;AACE;AACD;AACF;AACF;AACF;AAEM;AACA;AACL;AACA;AACA;;AAE6B;AAEzB;;;AAGJ;;;AAGE;;;;;;;AAOF;AACF;AAEO;AACA;AAKP;AACE;;;;AAIA;;;AAGI;AACA;;AAEF;AAEF;AACF;AAIM;;AAKJ;AACA;;;;;AAKI;;;;;;;AAOF;AACA;AACF;AACA;AACF;AAEM;AAGJ;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAKA;AACA;AACA;AACE;;;;AAMF;AACA;AACA;AACE;;;;AAMF;AACE;;;;AAMF;AACE;;;AAIJ;AACF;;ACpNA;AAIA;AAAA;;;;;AAII;AACE;;;;;;AAMM;;;;AAKR;;;;AAKA;AACA;;;;;AAKH;;;ACpBD;AAWA;AASA;AACE;AACF;AAGA;AAAA;;;;;;;;;;;;;AACE;AACE;;AAUF;AACE;;AAEF;AACE;;AAIF;;;;AAIE;AACE;;;AAGF;;;;AAIE;;AAEF;;AAGF;AACE;;AAEF;AACE;;AAMF;AACE;AAEA;AACE;AACE;;AAED;AAED;AACA;AAEA;;;AAIA;AAEA;AAEA;;;AAEA;AACA;AACA;AACA;;;AAmEJ;AACE;AACA;AACA;AACA;;AA+BF;AACE;;;;;AAMA;AACE;AACA;;;AAEA;AACA;;AAEF;;AAGE;AACA;AACA;AACE;;;;AAUN;AACE;;AAEF;AACE;AACA;AACA;AACA;AACE;;;AAEA;AACA;;AAGF;AACE;AACA;AACA;;;AAEA;AACA;;;AAGL;;AAvJG;AAEA;;;AAKA;AACA;AACE;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACE;AACA;;AAEA;;AAKA;;AAEA;;AAEA;AACE;AACA;;AAEF;AACE;AACA;AACA;AACE;;;;;AAKV;AAEE;;;AAIA;;AAEE;;AAEA;AACE;AACA;;AAEJ;AAEA;AACF;AASE;AAEA;AACA;AACF;AAGE;AAEA;AACA;AAKA;AACA;;;AAIA;AACE;;;AAEA;;AAEJ;AA4BE;AACA;AACF;;ACzNF;AACA;AACA;AAEO;AACP;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;AACA;;ACzYA;AAEO;AAEP;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;;;ACnbA;AAEO;AAGA;AACL;;;;;;AAQK;AAyBP;AAGE;;AAkDA;;;;AAjDE;;;AAKF;AACE;;AAKF;AACE;;AAEF;AACE;;;AAIA;;AAGE;AACE;;;AAGF;AACE;;;;AAKJ;;AAIA;AAEA;;AAEA;;AAIA;AAEA;;AAIF;;;AAkBA;;;AAUA;;AAGE;AACA;AAEA;;AAGF;AACE;AACA;;AAEE;;;;AAIA;;;;AAIA;;;;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","x_google_ignoreList":[0,8]}
1
+ {"version":3,"file":"brilliantsole.module.js","sources":["../node_modules/tslib/tslib.es6.js","../brilliantsole/utils/environment.ts","../brilliantsole/utils/Console.ts","../brilliantsole/utils/EventDispatcher.ts","../brilliantsole/utils/Timer.ts","../brilliantsole/utils/checksum.ts","../brilliantsole/utils/Text.ts","../brilliantsole/utils/ArrayBufferUtils.ts","../node_modules/auto-bind/index.js","../brilliantsole/FileTransferManager.ts","../brilliantsole/utils/MathUtils.ts","../brilliantsole/utils/RangeHelper.ts","../brilliantsole/utils/CenterOfPressureHelper.ts","../brilliantsole/utils/ArrayUtils.ts","../brilliantsole/sensor/PressureSensorDataManager.ts","../brilliantsole/sensor/MotionSensorDataManager.ts","../brilliantsole/sensor/BarometerSensorDataManager.ts","../brilliantsole/utils/ParseUtils.ts","../brilliantsole/sensor/SensorDataManager.ts","../brilliantsole/sensor/SensorConfigurationManager.ts","../brilliantsole/TfliteManager.ts","../brilliantsole/DeviceInformationManager.ts","../brilliantsole/InformationManager.ts","../brilliantsole/vibration/VibrationWaveformEffects.ts","../brilliantsole/vibration/VibrationManager.ts","../brilliantsole/connection/BaseConnectionManager.ts","../brilliantsole/utils/stringUtils.ts","../brilliantsole/utils/EventUtils.ts","../brilliantsole/connection/bluetooth/bluetoothUUIDs.ts","../brilliantsole/connection/bluetooth/BluetoothConnectionManager.ts","../brilliantsole/connection/bluetooth/WebBluetoothConnectionManager.ts","../brilliantsole/utils/cbor.js","../brilliantsole/utils/mcumgr.js","../brilliantsole/FirmwareManager.ts","../brilliantsole/DeviceManager.ts","../brilliantsole/Device.ts","../brilliantsole/devicePair/DevicePairPressureSensorDataManager.ts","../brilliantsole/devicePair/DevicePairSensorDataManager.ts","../brilliantsole/devicePair/DevicePair.ts","../brilliantsole/server/ServerUtils.ts","../brilliantsole/connection/ClientConnectionManager.ts","../brilliantsole/server/BaseClient.ts","../brilliantsole/server/websocket/WebSocketUtils.ts","../brilliantsole/server/websocket/WebSocketClient.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","type ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\" | \"__BRILLIANTSOLE__PROD__\";\nconst __BRILLIANTSOLE__ENVIRONMENT__: ENVIRONMENT_FLAG = \"__BRILLIANTSOLE__DEV__\";\n\n//@ts-expect-error\nconst isInProduction = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__PROD__\";\nconst isInDev = __BRILLIANTSOLE__ENVIRONMENT__ == \"__BRILLIANTSOLE__DEV__\";\n\n// https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts\nconst isInBrowser = typeof window !== \"undefined\" && typeof window?.document !== \"undefined\";\nconst isInNode = typeof process !== \"undefined\" && process?.versions?.node != null;\n\nconst userAgent = (isInBrowser && navigator.userAgent) || \"\";\n\nlet isBluetoothSupported = false;\nif (isInBrowser) {\n isBluetoothSupported = Boolean(navigator.bluetooth);\n} else if (isInNode) {\n isBluetoothSupported = true;\n}\n\nconst isInBluefy = isInBrowser && /Bluefy/i.test(userAgent);\nconst isInWebBLE = isInBrowser && /WebBLE/i.test(userAgent);\n\nconst isAndroid = isInBrowser && /Android/i.test(userAgent);\nconst isSafari = isInBrowser && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);\n\nconst isIOS = isInBrowser && /iPad|iPhone|iPod/i.test(userAgent);\nconst isMac = isInBrowser && /Macintosh/i.test(userAgent);\n\n// @ts-expect-error\nconst isInLensStudio = !isInBrowser && !isInNode && typeof global !== \"undefined\" && typeof Studio !== \"undefined\";\n\nexport {\n isInDev,\n isInProduction,\n isInBrowser,\n isInNode,\n isAndroid,\n isInBluefy,\n isInWebBLE,\n isSafari,\n isInLensStudio,\n isIOS,\n isMac,\n isBluetoothSupported,\n};\n","import { isInDev, isInLensStudio } from \"./environment.ts\";\n\ndeclare var Studio: any | undefined;\n\nexport type LogFunction = (...data: any[]) => void;\nexport type AssertLogFunction = (condition: boolean, ...data: any[]) => void;\n\nexport interface ConsoleLevelFlags {\n log?: boolean;\n warn?: boolean;\n error?: boolean;\n assert?: boolean;\n table?: boolean;\n}\n\ninterface ConsoleLike {\n log?: LogFunction;\n warn?: LogFunction;\n error?: LogFunction;\n assert?: AssertLogFunction;\n table?: LogFunction;\n}\n\nvar __console: ConsoleLike;\nif (isInLensStudio) {\n const log = function (...args: any[]) {\n Studio.log(args.map((value) => new String(value)).join(\",\"));\n };\n __console = {};\n __console.log = log;\n __console.warn = log.bind(__console, \"WARNING\");\n __console.error = log.bind(__console, \"ERROR\");\n} else {\n __console = console;\n}\n\n// console.assert not supported in WebBLE\nif (!__console.assert) {\n const assert: AssertLogFunction = (condition, ...data) => {\n if (!condition) {\n __console.warn!(...data);\n }\n };\n __console.assert = assert;\n}\n\n// console.table not supported in WebBLE\nif (!__console.table) {\n const table: LogFunction = (...data) => {\n __console.log!(...data);\n };\n __console.table = table;\n}\n\nfunction emptyFunction() {}\n\nconst log: LogFunction = __console.log!.bind(__console);\nconst warn: LogFunction = __console.warn!.bind(__console);\nconst error: LogFunction = __console.error!.bind(__console);\nconst table: LogFunction = __console.table!.bind(__console);\nconst assert: AssertLogFunction = __console.assert.bind(__console);\n\nclass Console {\n static #consoles: { [type: string]: Console } = {};\n\n constructor(type: string) {\n if (Console.#consoles[type]) {\n throw new Error(`\"${type}\" console already exists`);\n }\n Console.#consoles[type] = this;\n }\n\n #levelFlags: ConsoleLevelFlags = {\n log: isInDev,\n warn: isInDev,\n assert: true,\n error: true,\n table: true,\n };\n\n setLevelFlags(levelFlags: ConsoleLevelFlags) {\n Object.assign(this.#levelFlags, levelFlags);\n }\n\n /** @throws {Error} if no console with type \"type\" is found */\n static setLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n if (!this.#consoles[type]) {\n throw new Error(`no console found with type \"${type}\"`);\n }\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n\n static setAllLevelFlags(levelFlags: ConsoleLevelFlags) {\n for (const type in this.#consoles) {\n this.#consoles[type].setLevelFlags(levelFlags);\n }\n }\n\n static create(type: string, levelFlags?: ConsoleLevelFlags): Console {\n const console = this.#consoles[type] || new Console(type);\n if (isInDev && levelFlags) {\n console.setLevelFlags(levelFlags);\n }\n return console;\n }\n\n get log() {\n return this.#levelFlags.log ? log : emptyFunction;\n }\n\n get warn() {\n return this.#levelFlags.warn ? warn : emptyFunction;\n }\n\n get error() {\n return this.#levelFlags.error ? error : emptyFunction;\n }\n\n get assert() {\n return this.#levelFlags.assert ? assert : emptyFunction;\n }\n\n get table() {\n return this.#levelFlags.table ? table : emptyFunction;\n }\n\n /** @throws {Error} if condition is not met */\n assertWithError(condition: any, message: string) {\n if (!Boolean(condition)) {\n throw new Error(message);\n }\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertTypeWithError(value: any, type: string) {\n this.assertWithError(typeof value == type, `value ${value} of type \"${typeof value}\" not of type \"${type}\"`);\n }\n\n /** @throws {Error} if value's type doesn't match */\n assertEnumWithError(value: string, enumeration: readonly string[]) {\n this.assertWithError(enumeration.includes(value), `invalid enum \"${value}\"`);\n }\n}\n\nexport function createConsole(type: string, levelFlags?: ConsoleLevelFlags): Console {\n return Console.create(type, levelFlags);\n}\n\n/** @throws {Error} if no console with type is found */\nexport function setConsoleLevelFlagsForType(type: string, levelFlags: ConsoleLevelFlags) {\n Console.setLevelFlagsForType(type, levelFlags);\n}\n\nexport function setAllConsoleLevelFlags(levelFlags: ConsoleLevelFlags) {\n Console.setAllLevelFlags(levelFlags);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { deepEqual } from \"./ObjectUtils.ts\";\n\nconst _console = createConsole(\"EventDispatcher\", { log: false });\n\nexport type EventMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: { type: T; target: Target; message: EventMessages[T] };\n};\nexport type EventListenerMap<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [T in keyof EventMessages]: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n};\n\nexport type Event<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = EventMap<Target, EventType, EventMessages>[keyof EventMessages];\n\ntype SpecificEvent<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>,\n SpecificEventType extends EventType\n> = { type: SpecificEventType; target: Target; message: EventMessages[SpecificEventType] };\n\nexport type BoundEventListeners<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> = {\n [SpecificEventType in keyof EventMessages]?: (\n // @ts-expect-error\n event: SpecificEvent<Target, EventType, EventMessages, SpecificEventType>\n ) => void;\n};\n\nclass EventDispatcher<\n Target extends any,\n EventType extends string,\n EventMessages extends Partial<Record<EventType, any>>\n> {\n private listeners: {\n [T in EventType]?: {\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void;\n once?: boolean;\n shouldRemove?: boolean;\n }[];\n } = {};\n\n constructor(private target: Target, private validEventTypes: readonly EventType[]) {\n this.addEventListener = this.addEventListener.bind(this);\n this.removeEventListener = this.removeEventListener.bind(this);\n this.removeEventListeners = this.removeEventListeners.bind(this);\n this.removeAllEventListeners = this.removeAllEventListeners.bind(this);\n this.dispatchEvent = this.dispatchEvent.bind(this);\n this.waitForEvent = this.waitForEvent.bind(this);\n }\n\n private isValidEventType(type: any): type is EventType {\n return this.validEventTypes.includes(type);\n }\n\n private updateEventListeners(type: EventType) {\n if (!this.listeners[type]) return;\n this.listeners[type] = this.listeners[type]!.filter((listenerObj) => {\n if (listenerObj.shouldRemove) {\n _console.log(`removing \"${type}\" eventListener`, listenerObj);\n }\n return !listenerObj.shouldRemove;\n });\n }\n\n addEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void,\n options: { once?: boolean } = { once: false }\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) {\n this.listeners[type] = [];\n _console.log(`creating \"${type}\" listeners array`, this.listeners[type]!);\n }\n const alreadyAdded = this.listeners[type].find((listenerObject) => {\n return listenerObject.listener == listener && listenerObject.once == options.once;\n });\n if (alreadyAdded) {\n _console.log(\"already added listener\");\n return;\n }\n _console.log(`adding \"${type}\" listener`, listener, options);\n this.listeners[type]!.push({ listener, once: options.once });\n\n _console.log(`currently have ${this.listeners[type]!.length} \"${type}\" listeners`);\n }\n\n removeEventListener<T extends EventType>(\n type: T,\n listener: (event: { type: T; target: Target; message: EventMessages[T] }) => void\n ): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listener...`, listener);\n this.listeners[type]!.forEach((listenerObj) => {\n const isListenerToRemove = listenerObj.listener === listener;\n if (isListenerToRemove) {\n _console.log(`flagging \"${type}\" listener`, listener);\n listenerObj.shouldRemove = true;\n }\n });\n\n this.updateEventListeners(type);\n }\n\n removeEventListeners<T extends EventType>(type: T): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n _console.log(`removing \"${type}\" listeners...`);\n this.listeners[type] = [];\n }\n\n removeAllEventListeners(): void {\n _console.log(`removing listeners...`);\n this.listeners = {};\n }\n\n dispatchEvent<T extends EventType>(type: T, message: EventMessages[T]): void {\n if (!this.isValidEventType(type)) {\n throw new Error(`Invalid event type: ${type}`);\n }\n\n if (!this.listeners[type]) return;\n\n this.listeners[type]!.forEach((listenerObj) => {\n if (listenerObj.shouldRemove) {\n return;\n }\n\n _console.log(`dispatching \"${type}\" listener`, listenerObj);\n listenerObj.listener({ type, target: this.target, message });\n\n if (listenerObj.once) {\n _console.log(`flagging \"${type}\" listener`, listenerObj);\n listenerObj.shouldRemove = true;\n }\n });\n this.updateEventListeners(type);\n }\n\n waitForEvent<T extends EventType>(type: T): Promise<{ type: T; target: Target; message: EventMessages[T] }> {\n return new Promise((resolve) => {\n const onceListener = (event: { type: T; target: Target; message: EventMessages[T] }) => {\n resolve(event);\n };\n\n this.addEventListener(type, onceListener, { once: true });\n });\n }\n}\n\nexport default EventDispatcher;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"Timer\", { log: false });\n\nexport async function wait(delay: number) {\n _console.log(`waiting for ${delay} ms`);\n return new Promise((resolve: Function) => {\n setTimeout(() => resolve(), delay);\n });\n}\n\nclass Timer {\n #callback!: Function;\n get callback() {\n return this.#callback;\n }\n set callback(newCallback) {\n _console.assertTypeWithError(newCallback, \"function\");\n _console.log({ newCallback });\n this.#callback = newCallback;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n #interval!: number;\n get interval() {\n return this.#interval;\n }\n set interval(newInterval) {\n _console.assertTypeWithError(newInterval, \"number\");\n _console.assertWithError(newInterval > 0, \"interval must be above 0\");\n _console.log({ newInterval });\n this.#interval = newInterval;\n if (this.isRunning) {\n this.restart();\n }\n }\n\n constructor(callback: Function, interval: number) {\n this.interval = interval;\n this.callback = callback;\n }\n\n #intervalId: number | undefined;\n get isRunning() {\n return this.#intervalId != undefined;\n }\n\n start(immediately = false) {\n if (this.isRunning) {\n _console.log(\"interval already running\");\n return;\n }\n _console.log(\"starting interval\");\n this.#intervalId = setInterval(this.#callback, this.#interval);\n if (immediately) {\n this.#callback();\n }\n }\n stop() {\n if (!this.isRunning) {\n _console.log(\"interval already not running\");\n return;\n }\n _console.log(\"stopping interval\");\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n restart(startImmediately = false) {\n this.stop();\n this.start(startImmediately);\n }\n}\nexport default Timer;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"checksum\", { log: true });\n\n// https://github.com/googlecreativelab/tiny-motion-trainer/blob/5fceb49f018ae0c403bf9f0ccc437309c2acb507/frontend/src/tf4micro-motion-kit/modules/bleFileTransfer#L195\n\n// See http://home.thep.lu.se/~bjorn/crc/ for more information on simple CRC32 calculations.\nexport function crc32ForByte(r: number) {\n for (let j = 0; j < 8; ++j) {\n r = (r & 1 ? 0 : 0xedb88320) ^ (r >>> 1);\n }\n return r ^ 0xff000000;\n}\n\nconst tableSize = 256;\nconst crc32Table = new Uint32Array(tableSize);\nfor (let i = 0; i < tableSize; ++i) {\n crc32Table[i] = crc32ForByte(i);\n}\n\nexport function crc32(dataIterable: ArrayBuffer | number[]) {\n let dataBytes = new Uint8Array(dataIterable);\n let crc = 0;\n for (let i = 0; i < dataBytes.byteLength; ++i) {\n const crcLowByte = crc & 0x000000ff;\n const dataByte = dataBytes[i];\n const tableIndex = crcLowByte ^ dataByte;\n // The last >>> is to convert this into an unsigned 32-bit integer.\n crc = (crc32Table[tableIndex] ^ (crc >>> 8)) >>> 0;\n }\n return crc;\n}\n\n// This is a small test function for the CRC32 implementation, not normally called but left in\n// for debugging purposes. We know the expected CRC32 of [97, 98, 99, 100, 101] is 2240272485,\n// or 0x8587d865, so if anything else is output we know there's an error in the implementation.\nexport function testCrc32() {\n const testArray = [97, 98, 99, 100, 101];\n const testArrayCrc32 = crc32(testArray);\n _console.log(\"CRC32 for [97, 98, 99, 100, 101] is 0x\" + testArrayCrc32.toString(16) + \" (\" + testArrayCrc32 + \")\");\n}\n","var _TextEncoder;\nif (typeof TextEncoder == \"undefined\") {\n _TextEncoder = class {\n encode(string: string) {\n const encoding = Array.from(string).map((char) => char.charCodeAt(0));\n return Uint8Array.from(encoding);\n }\n };\n} else {\n _TextEncoder = TextEncoder;\n}\n\nvar _TextDecoder;\nif (typeof TextDecoder == \"undefined\") {\n _TextDecoder = class {\n decode(data: ArrayBuffer) {\n const byteArray = Array.from(new Uint8Array(data));\n return byteArray\n .map((value) => {\n return String.fromCharCode(value);\n })\n .join(\"\");\n }\n };\n} else {\n _TextDecoder = TextDecoder;\n}\n\nexport const textEncoder = new _TextEncoder();\nexport const textDecoder = new _TextDecoder();\n","import { createConsole } from \"./Console.ts\";\nimport { textEncoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ArrayBufferUtils\", { log: false });\n\nexport function concatenateArrayBuffers(...arrayBuffers: any[]): ArrayBuffer {\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer != undefined || arrayBuffer != null);\n arrayBuffers = arrayBuffers.map((arrayBuffer) => {\n if (typeof arrayBuffer == \"number\") {\n const number = arrayBuffer;\n return Uint8Array.from([Math.floor(number)]);\n } else if (typeof arrayBuffer == \"boolean\") {\n const boolean = arrayBuffer;\n return Uint8Array.from([boolean ? 1 : 0]);\n } else if (typeof arrayBuffer == \"string\") {\n const string = arrayBuffer;\n return stringToArrayBuffer(string);\n } else if (arrayBuffer instanceof Array) {\n const array = arrayBuffer;\n return concatenateArrayBuffers(...array);\n } else if (arrayBuffer instanceof ArrayBuffer) {\n return arrayBuffer;\n } else if (\"buffer\" in arrayBuffer && arrayBuffer.buffer instanceof ArrayBuffer) {\n const bufferContainer = arrayBuffer;\n return bufferContainer.buffer;\n } else if (arrayBuffer instanceof DataView) {\n const dataView = arrayBuffer;\n return dataView.buffer;\n } else if (typeof arrayBuffer == \"object\") {\n const object = arrayBuffer;\n return objectToArrayBuffer(object);\n } else {\n return arrayBuffer;\n }\n });\n arrayBuffers = arrayBuffers.filter((arrayBuffer) => arrayBuffer && \"byteLength\" in arrayBuffer);\n const length = arrayBuffers.reduce((length, arrayBuffer) => length + arrayBuffer.byteLength, 0);\n const uint8Array = new Uint8Array(length);\n let byteOffset = 0;\n arrayBuffers.forEach((arrayBuffer) => {\n uint8Array.set(new Uint8Array(arrayBuffer), byteOffset);\n byteOffset += arrayBuffer.byteLength;\n });\n return uint8Array.buffer;\n}\n\nexport function dataToArrayBuffer(data: Buffer) {\n return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n}\n\nexport function stringToArrayBuffer(string: string) {\n const encoding = textEncoder.encode(string);\n return concatenateArrayBuffers(encoding.byteLength, encoding);\n}\n\nexport function objectToArrayBuffer(object: object) {\n return stringToArrayBuffer(JSON.stringify(object));\n}\n\nexport function sliceDataView(dataView: DataView, begin: number, length?: number) {\n let end;\n if (length != undefined) {\n end = dataView.byteOffset + begin + length;\n }\n _console.log({ dataView, begin, end, length });\n return new DataView(dataView.buffer.slice(dataView.byteOffset + begin, end));\n}\n\nexport type FileLike = number[] | ArrayBuffer | DataView | URL | string | File;\n\nexport async function getFileBuffer(file: FileLike) {\n let fileBuffer;\n if (file instanceof Array) {\n fileBuffer = Uint8Array.from(file);\n } else if (file instanceof DataView) {\n fileBuffer = file.buffer;\n } else if (typeof file == \"string\" || file instanceof URL) {\n const response = await fetch(file);\n fileBuffer = await response.arrayBuffer();\n } else if (file instanceof File) {\n fileBuffer = await file.arrayBuffer();\n } else if (file instanceof ArrayBuffer) {\n fileBuffer = file;\n } else {\n throw { error: \"invalid file type\", file };\n }\n return fileBuffer;\n}\n","// Gets all non-builtin properties up the prototype chain.\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nexport default function autoBind(self, {include, exclude} = {}) {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match); // eslint-disable-line unicorn/no-array-callback-reference\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n}\n","import { createConsole } from \"./utils/Console.ts\";\nimport { crc32 } from \"./utils/checksum.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FileTransferManager\", { log: true });\n\nexport const FileTransferMessageTypes = [\n \"maxFileLength\",\n \"getFileType\",\n \"setFileType\",\n \"getFileLength\",\n \"setFileLength\",\n \"getFileChecksum\",\n \"setFileChecksum\",\n \"setFileTransferCommand\",\n \"fileTransferStatus\",\n \"getFileBlock\",\n \"setFileBlock\",\n \"fileBytesTransferred\",\n] as const;\nexport type FileTransferMessageType = (typeof FileTransferMessageTypes)[number];\n\nexport const FileTypes = [\"tflite\"] as const;\nexport type FileType = (typeof FileTypes)[number];\n\nexport const FileTransferStatuses = [\"idle\", \"sending\", \"receiving\"] as const;\nexport type FileTransferStatus = (typeof FileTransferStatuses)[number];\n\nexport const FileTransferCommands = [\"startSend\", \"startReceive\", \"cancel\"] as const;\nexport type FileTransferCommand = (typeof FileTransferCommands)[number];\n\nexport const FileTransferDirections = [\"sending\", \"receiving\"] as const;\nexport type FileTransferDirection = (typeof FileTransferDirections)[number];\n\nexport const FileTransferEventTypes = [\n ...FileTransferMessageTypes,\n \"fileTransferProgress\",\n \"fileTransferComplete\",\n \"fileReceived\",\n] as const;\nexport type FileTransferEventType = (typeof FileTransferEventTypes)[number];\n\nexport interface FileTransferEventMessages {\n maxFileLength: { maxFileLength: number };\n getFileType: { fileType: FileType };\n getFileLength: { fileLength: number };\n getFileChecksum: { fileChecksum: number };\n fileTransferStatus: { fileTransferStatus: FileTransferStatus };\n getFileBlock: { fileTransferBlock: DataView };\n fileTransferProgress: { progress: number };\n fileTransferComplete: { direction: FileTransferDirection };\n fileReceived: { file: File | Blob };\n}\n\nexport type FileTransferEventDispatcher = EventDispatcher<Device, FileTransferEventType, FileTransferEventMessages>;\nexport type SendFileTransferMessageCallback = SendMessageCallback<FileTransferMessageType>;\n\nclass FileTransferManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendFileTransferMessageCallback;\n\n eventDispatcher!: FileTransferEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #assertValidType(type: FileType) {\n _console.assertEnumWithError(type, FileTypes);\n }\n #assertValidTypeEnum(typeEnum: number) {\n _console.assertWithError(typeEnum in FileTypes, `invalid typeEnum ${typeEnum}`);\n }\n\n #assertValidStatusEnum(statusEnum: number) {\n _console.assertWithError(statusEnum in FileTransferStatuses, `invalid statusEnum ${statusEnum}`);\n }\n #assertValidCommand(command: FileTransferCommand) {\n _console.assertEnumWithError(command, FileTransferCommands);\n }\n\n static #MaxLength = 0; // kB\n static get MaxLength() {\n return this.#MaxLength;\n }\n #maxLength = FileTransferManager.MaxLength;\n /** kB */\n get maxLength() {\n return this.#maxLength;\n }\n #parseMaxLength(dataView: DataView) {\n _console.log(\"parseFileMaxLength\", dataView);\n const maxLength = dataView.getUint32(0, true);\n _console.log(`maxLength: ${maxLength / 1024}kB`);\n this.#updateMaxLength(maxLength);\n }\n #updateMaxLength(maxLength: number) {\n _console.log({ maxLength });\n this.#maxLength = maxLength;\n this.#dispatchEvent(\"maxFileLength\", { maxFileLength: maxLength });\n }\n #assertValidLength(length: number) {\n _console.assertWithError(\n length <= this.maxLength,\n `file length ${length}kB too large - must be ${this.maxLength}kB or less`\n );\n }\n\n #type: FileType | undefined;\n get type() {\n return this.#type;\n }\n #parseType(dataView: DataView) {\n _console.log(\"parseFileType\", dataView);\n const typeEnum = dataView.getUint8(0);\n this.#assertValidTypeEnum(typeEnum);\n const type = FileTypes[typeEnum];\n this.#updateType(type);\n }\n #updateType(type: FileType) {\n _console.log({ fileTransferType: type });\n this.#type = type;\n this.#dispatchEvent(\"getFileType\", { fileType: type });\n }\n async #setType(newType: FileType, sendImmediately?: boolean) {\n this.#assertValidType(newType);\n if (this.type == newType) {\n _console.log(`redundant type assignment ${newType}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileType\");\n\n const typeEnum = FileTypes.indexOf(newType);\n this.sendMessage([{ type: \"setFileType\", data: Uint8Array.from([typeEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #length = 0;\n get length() {\n return this.#length;\n }\n #parseLength(dataView: DataView) {\n _console.log(\"parseFileLength\", dataView);\n const length = dataView.getUint32(0, true);\n\n this.#updateLength(length);\n }\n #updateLength(length: number) {\n _console.log(`length: ${length / 1024}kB`);\n this.#length = length;\n this.#dispatchEvent(\"getFileLength\", { fileLength: length });\n }\n async #setLength(newLength: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newLength, \"number\");\n this.#assertValidLength(newLength);\n if (this.length == newLength) {\n _console.log(`redundant length assignment ${newLength}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileLength\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newLength, true);\n this.sendMessage([{ type: \"setFileLength\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #checksum = 0;\n get checksum() {\n return this.#checksum;\n }\n #parseChecksum(dataView: DataView) {\n _console.log(\"checksum\", dataView);\n const checksum = dataView.getUint32(0, true);\n this.#updateChecksum(checksum);\n }\n #updateChecksum(checksum: number) {\n _console.log({ checksum });\n this.#checksum = checksum;\n this.#dispatchEvent(\"getFileChecksum\", { fileChecksum: checksum });\n }\n async #setChecksum(newChecksum: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newChecksum, \"number\");\n if (this.checksum == newChecksum) {\n _console.log(`redundant checksum assignment ${newChecksum}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getFileChecksum\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, newChecksum, true);\n this.sendMessage([{ type: \"setFileChecksum\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n async #setCommand(command: FileTransferCommand, sendImmediately?: boolean) {\n this.#assertValidCommand(command);\n\n const promise = this.waitForEvent(\"fileTransferStatus\");\n _console.log(`setting command ${command}`);\n const commandEnum = FileTransferCommands.indexOf(command);\n this.sendMessage(\n [{ type: \"setFileTransferCommand\", data: Uint8Array.from([commandEnum]).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #status: FileTransferStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #parseStatus(dataView: DataView) {\n _console.log(\"parseFileStatus\", dataView);\n const statusEnum = dataView.getUint8(0);\n this.#assertValidStatusEnum(statusEnum);\n const status = FileTransferStatuses[statusEnum];\n this.#updateStatus(status);\n }\n #updateStatus(status: FileTransferStatus) {\n _console.log({ status });\n this.#status = status;\n this.#dispatchEvent(\"fileTransferStatus\", { fileTransferStatus: status });\n this.#receivedBlocks.length = 0;\n }\n #assertIsIdle() {\n _console.assertWithError(this.#status == \"idle\", \"status is not idle\");\n }\n #assertIsNotIdle() {\n _console.assertWithError(this.#status != \"idle\", \"status is idle\");\n }\n\n // BLOCK\n\n #receivedBlocks: ArrayBuffer[] = [];\n\n async #parseBlock(dataView: DataView) {\n _console.log(\"parseFileBlock\", dataView);\n this.#receivedBlocks.push(dataView.buffer);\n\n const bytesReceived = this.#receivedBlocks.reduce((sum, arrayBuffer) => (sum += arrayBuffer.byteLength), 0);\n const progress = bytesReceived / this.#length;\n\n _console.log(`received ${bytesReceived} of ${this.#length} bytes (${progress * 100}%)`);\n\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n\n if (bytesReceived != this.#length) {\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setUint32(0, bytesReceived, true);\n\n if (this.isServerSide) {\n return;\n }\n await this.sendMessage([{ type: \"fileBytesTransferred\", data: dataView.buffer }]);\n return;\n }\n\n _console.log(\"file transfer complete\");\n\n let fileName = new Date().toLocaleString();\n switch (this.type) {\n case \"tflite\":\n fileName += \".tflite\";\n break;\n }\n\n let file: File | Blob;\n if (typeof File !== \"undefined\") {\n file = new File(this.#receivedBlocks, fileName);\n } else {\n file = new Blob(this.#receivedBlocks);\n }\n\n const arrayBuffer = await file.arrayBuffer();\n const checksum = crc32(arrayBuffer);\n _console.log({ checksum });\n\n if (checksum != this.#checksum) {\n _console.error(`wrong checksum - expected ${this.#checksum}, got ${checksum}`);\n return;\n }\n\n _console.log(\"received file\", file);\n\n this.#dispatchEvent(\"getFileBlock\", { fileTransferBlock: dataView });\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"receiving\" });\n this.#dispatchEvent(\"fileReceived\", { file });\n }\n\n parseMessage(messageType: FileTransferMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"maxFileLength\":\n this.#parseMaxLength(dataView);\n break;\n case \"getFileType\":\n case \"setFileType\":\n this.#parseType(dataView);\n break;\n case \"getFileLength\":\n case \"setFileLength\":\n this.#parseLength(dataView);\n break;\n case \"getFileChecksum\":\n case \"setFileChecksum\":\n this.#parseChecksum(dataView);\n break;\n case \"fileTransferStatus\":\n this.#parseStatus(dataView);\n break;\n case \"getFileBlock\":\n this.#parseBlock(dataView);\n break;\n case \"fileBytesTransferred\":\n this.#parseBytesTransferred(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async send(type: FileType, file: FileLike) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n const fileBuffer = await getFileBuffer(file);\n\n const promises: Promise<any>[] = [];\n\n promises.push(this.#setType(type, false));\n const fileLength = fileBuffer.byteLength;\n promises.push(this.#setLength(fileLength, false));\n const checksum = crc32(fileBuffer);\n promises.push(this.#setChecksum(checksum, false));\n promises.push(this.#setCommand(\"startSend\", false));\n\n this.sendMessage();\n\n await Promise.all(promises);\n\n await this.#send(fileBuffer);\n }\n\n #buffer?: ArrayBuffer;\n #bytesTransferred = 0;\n async #send(buffer: ArrayBuffer) {\n this.#buffer = buffer;\n this.#bytesTransferred = 0;\n return this.#sendBlock();\n }\n\n mtu!: number;\n async #sendBlock(): Promise<void> {\n if (this.status != \"sending\") {\n return;\n }\n if (!this.#buffer) {\n if (!this.isServerSide) {\n _console.error(\"no buffer defined\");\n }\n return;\n }\n\n const buffer = this.#buffer;\n let offset = this.#bytesTransferred;\n\n const slicedBuffer = buffer.slice(offset, offset + (this.mtu - 3 - 3));\n _console.log(\"slicedBuffer\", slicedBuffer);\n const bytesLeft = buffer.byteLength - offset;\n\n const progress = 1 - bytesLeft / buffer.byteLength;\n _console.log(\n `sending bytes ${offset}-${offset + slicedBuffer.byteLength} of ${buffer.byteLength} bytes (${progress * 100}%)`\n );\n this.#dispatchEvent(\"fileTransferProgress\", { progress });\n if (slicedBuffer.byteLength == 0) {\n _console.log(\"finished sending buffer\");\n this.#dispatchEvent(\"fileTransferComplete\", { direction: \"sending\" });\n } else {\n await this.sendMessage([{ type: \"setFileBlock\", data: slicedBuffer }]);\n this.#bytesTransferred = offset + slicedBuffer.byteLength;\n //return this.#sendBlock(buffer, offset + slicedBuffer.byteLength);\n }\n }\n\n async #parseBytesTransferred(dataView: DataView) {\n _console.log(\"parseBytesTransferred\", dataView);\n const bytesTransferred = dataView.getUint32(0, true);\n _console.log({ bytesTransferred });\n if (this.status != \"sending\") {\n _console.error(`not currently sending file`);\n return;\n }\n if (!this.isServerSide && this.#bytesTransferred != bytesTransferred) {\n _console.error(`bytesTransferred are not equal - got ${bytesTransferred}, expected ${this.#bytesTransferred}`);\n this.cancel();\n return;\n }\n this.#sendBlock();\n }\n\n async receive(type: FileType) {\n this.#assertIsIdle();\n\n this.#assertValidType(type);\n\n await this.#setType(type);\n await this.#setCommand(\"startReceive\");\n }\n\n async cancel() {\n this.#assertIsNotIdle();\n _console.log(\"cancelling file transfer...\");\n await this.#setCommand(\"cancel\");\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n }\n}\n\nexport default FileTransferManager;\n","import { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"MathUtils\", { log: true });\n\nexport function getInterpolation(value: number, min: number, max: number, span: number) {\n if (span == undefined) {\n span = max - min;\n }\n return (value - min) / span;\n}\n\nexport const Uint16Max = 2 ** 16;\n\nfunction removeLower2Bytes(number: number) {\n const lower2Bytes = number % Uint16Max;\n return number - lower2Bytes;\n}\n\nconst timestampThreshold = 60_000;\n\nexport function parseTimestamp(dataView: DataView, byteOffset: number) {\n const now = Date.now();\n const nowWithoutLower2Bytes = removeLower2Bytes(now);\n const lower2Bytes = dataView.getUint16(byteOffset, true);\n let timestamp = nowWithoutLower2Bytes + lower2Bytes;\n if (Math.abs(now - timestamp) > timestampThreshold) {\n _console.log(\"correcting timestamp delta\");\n timestamp += Uint16Max * Math.sign(now - timestamp);\n }\n return timestamp;\n}\n\nexport interface Vector2 {\n x: number;\n y: number;\n}\n\nexport interface Vector3 extends Vector2 {\n z: number;\n}\n\nexport interface Quaternion {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\nexport interface Euler {\n heading: number;\n pitch: number;\n roll: number;\n}\n","import { getInterpolation } from \"./MathUtils.ts\";\n\ninterface Range {\n min: number;\n max: number;\n span: number;\n}\n\nconst initialRange: Range = { min: Infinity, max: -Infinity, span: 0 };\n\nclass RangeHelper {\n #range: Range = Object.assign({}, initialRange);\n get min() {\n return this.#range.min;\n }\n get max() {\n return this.#range.max;\n }\n\n set min(newMin) {\n this.#range.min = newMin;\n this.#range.max = Math.max(newMin, this.#range.max);\n this.#updateSpan();\n }\n set max(newMax) {\n this.#range.max = newMax;\n this.#range.min = Math.min(newMax, this.#range.min);\n this.#updateSpan();\n }\n\n #updateSpan() {\n this.#range.span = this.#range.max - this.#range.min;\n }\n\n reset() {\n Object.assign(this.#range, initialRange);\n }\n\n update(value: number) {\n this.#range.min = Math.min(value, this.#range.min);\n this.#range.max = Math.max(value, this.#range.max);\n this.#updateSpan();\n }\n\n getNormalization(value: number, weightByRange: boolean) {\n let normalization = getInterpolation(value, this.#range.min, this.#range.max, this.#range.span);\n if (weightByRange) {\n normalization *= this.#range.span;\n }\n return normalization || 0;\n }\n\n updateAndGetNormalization(value: number, weightByRange: boolean) {\n this.update(value);\n return this.getNormalization(value, weightByRange);\n }\n}\n\nexport default RangeHelper;\n","import RangeHelper from \"./RangeHelper.ts\";\n\nimport { Vector2 } from \"./MathUtils.ts\";\n\nexport type CenterOfPressure = Vector2;\n\nexport interface CenterOfPressureRange {\n x: RangeHelper;\n y: RangeHelper;\n}\n\nclass CenterOfPressureHelper {\n #range: CenterOfPressureRange = {\n x: new RangeHelper(),\n y: new RangeHelper(),\n };\n reset() {\n this.#range.x.reset();\n this.#range.y.reset();\n }\n\n update(centerOfPressure: CenterOfPressure) {\n this.#range.x.update(centerOfPressure.x);\n this.#range.y.update(centerOfPressure.y);\n }\n getNormalization(centerOfPressure: CenterOfPressure): CenterOfPressure {\n return {\n x: this.#range.x.getNormalization(centerOfPressure.x, false),\n y: this.#range.y.getNormalization(centerOfPressure.y, false),\n };\n }\n\n updateAndGetNormalization(centerOfPressure: CenterOfPressure) {\n this.update(centerOfPressure);\n return this.getNormalization(centerOfPressure);\n }\n}\n\nexport default CenterOfPressureHelper;\n","export function createArray(arrayLength: number, objectOrCallback: ((index: number) => any) | object) {\n return new Array(arrayLength).fill(1).map((_, index) => {\n if (typeof objectOrCallback == \"function\") {\n const callback = objectOrCallback;\n return callback(index);\n } else {\n const object = objectOrCallback;\n return Object.assign({}, object);\n }\n });\n}\n\nexport function arrayWithoutDuplicates(array: any[]) {\n return array.filter((value, index) => array.indexOf(value) == index);\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport RangeHelper from \"../utils/RangeHelper.ts\";\nimport { createArray } from \"../utils/ArrayUtils.ts\";\n\nconst _console = createConsole(\"PressureDataManager\", { log: true });\n\nexport const PressureSensorTypes = [\"pressure\"] as const;\nexport type PressureSensorType = (typeof PressureSensorTypes)[number];\n\nexport const ContinuousPressureSensorTypes = PressureSensorTypes;\nexport type ContinuousPressureSensorType = (typeof ContinuousPressureSensorTypes)[number];\n\nimport { Vector2 } from \"../utils/MathUtils.ts\";\nexport type PressureSensorPosition = Vector2;\n\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\n\nexport interface PressureSensorValue {\n position: PressureSensorPosition;\n rawValue: number;\n scaledValue: number;\n normalizedValue: number;\n weightedValue: number;\n}\n\nexport interface PressureData {\n sensors: PressureSensorValue[];\n scaledSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface PressureDataEventMessages {\n pressure: { pressure: PressureData };\n}\n\nexport const DefaultNumberOfPressureSensors = 8;\n\nclass PressureSensorDataManager {\n #positions: PressureSensorPosition[] = [];\n get positions() {\n return this.#positions;\n }\n\n get numberOfSensors() {\n return this.positions.length;\n }\n\n parsePositions(dataView: DataView) {\n const positions: PressureSensorPosition[] = [];\n\n for (\n let pressureSensorIndex = 0, byteOffset = 0;\n byteOffset < dataView.byteLength;\n pressureSensorIndex++, byteOffset += 2\n ) {\n positions.push({\n x: dataView.getUint8(byteOffset) / 2 ** 8,\n y: dataView.getUint8(byteOffset + 1) / 2 ** 8,\n });\n }\n\n _console.log({ positions });\n\n this.#positions = positions;\n\n this.#sensorRangeHelpers = createArray(this.numberOfSensors, () => new RangeHelper());\n\n this.resetRange();\n }\n\n #sensorRangeHelpers!: RangeHelper[];\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetRange() {\n this.#sensorRangeHelpers.forEach((rangeHelper) => rangeHelper.reset());\n this.#centerOfPressureHelper.reset();\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure: PressureData = { sensors: [], scaledSum: 0, normalizedSum: 0 };\n for (let index = 0, byteOffset = 0; byteOffset < dataView.byteLength; index++, byteOffset += 2) {\n const rawValue = dataView.getUint16(byteOffset, true);\n const scaledValue = rawValue * scalar;\n const rangeHelper = this.#sensorRangeHelpers[index];\n const normalizedValue = rangeHelper.updateAndGetNormalization(scaledValue, true);\n const position = this.positions[index];\n pressure.sensors[index] = { rawValue, scaledValue, normalizedValue, position, weightedValue: 0 };\n\n pressure.scaledSum += scaledValue;\n pressure.normalizedSum += normalizedValue / this.numberOfSensors;\n }\n\n if (pressure.scaledSum > 0 && pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n pressure.sensors.forEach((sensor) => {\n sensor.weightedValue = sensor.scaledValue / pressure.scaledSum;\n pressure.center!.x += sensor.position.x * sensor.weightedValue;\n pressure.center!.y += sensor.position.y * sensor.weightedValue;\n });\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ pressure });\n return pressure;\n }\n}\n\nexport default PressureSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nconst _console = createConsole(\"MotionSensorDataManager\", { log: true });\n\nexport const MotionSensorTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n \"orientation\",\n \"activity\",\n \"stepCounter\",\n \"stepDetector\",\n \"deviceOrientation\",\n] as const;\nexport type MotionSensorType = (typeof MotionSensorTypes)[number];\n\nexport const ContinuousMotionTypes = [\n \"acceleration\",\n \"gravity\",\n \"linearAcceleration\",\n \"gyroscope\",\n \"magnetometer\",\n \"gameRotation\",\n \"rotation\",\n] as const;\nexport type ContinuousMotionType = (typeof ContinuousMotionTypes)[number];\n\nimport { Vector3, Quaternion, Euler } from \"../utils/MathUtils.ts\";\nimport { ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nexport const Vector2Size = 2 * 2;\nexport const Vector3Size = 3 * 2;\nexport const QuaternionSize = 4 * 2;\n\nexport const ActivityTypes = [\"still\", \"walking\", \"running\", \"bicycle\", \"vehicle\", \"tilting\"] as const;\nexport type ActivityType = (typeof ActivityTypes)[number];\n\nexport interface Activity {\n still: boolean;\n walking: boolean;\n running: boolean;\n bicycle: boolean;\n vehicle: boolean;\n tilting: boolean;\n}\n\nexport const DeviceOrientations = [\n \"portraitUpright\",\n \"landscapeLeft\",\n \"portraitUpsideDown\",\n \"landscapeRight\",\n \"unknown\",\n] as const;\nexport type DeviceOrientation = (typeof DeviceOrientations)[number];\n\nexport interface MotionSensorDataEventMessages {\n acceleration: { acceleration: Vector3 };\n gravity: { gravity: Vector3 };\n linearAcceleration: { linearAcceleration: Vector3 };\n gyroscope: { gyroscope: Vector3 };\n magnetometer: { magnetometer: Vector3 };\n gameRotation: { gameRotation: Quaternion };\n rotation: { rotation: Quaternion };\n orientation: { orientation: Euler };\n stepDetector: { stepDetector: Object };\n stepCounter: { stepCounter: number };\n activity: { activity: Activity };\n deviceOrientation: { deviceOrientation: DeviceOrientation };\n}\n\nexport type MotionSensorDataEventMessage = ValueOf<MotionSensorDataEventMessages>;\n\nclass MotionSensorDataManager {\n parseVector3(dataView: DataView, scalar: number): Vector3 {\n let [x, y, z] = [dataView.getInt16(0, true), dataView.getInt16(2, true), dataView.getInt16(4, true)].map(\n (value) => value * scalar\n );\n\n const vector: Vector3 = { x, y, z };\n\n _console.log({ vector });\n return vector;\n }\n\n parseQuaternion(dataView: DataView, scalar: number): Quaternion {\n let [x, y, z, w] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n dataView.getInt16(6, true),\n ].map((value) => value * scalar);\n\n const quaternion: Quaternion = { x, y, z, w };\n\n _console.log({ quaternion });\n return quaternion;\n }\n\n parseEuler(dataView: DataView, scalar: number): Euler {\n let [heading, pitch, roll] = [\n dataView.getInt16(0, true),\n dataView.getInt16(2, true),\n dataView.getInt16(4, true),\n ].map((value) => value * scalar);\n\n pitch *= -1;\n heading *= -1;\n heading += 360;\n\n const euler: Euler = { heading, pitch, roll };\n\n _console.log({ euler });\n return euler;\n }\n\n parseStepCounter(dataView: DataView) {\n _console.log(\"parseStepCounter\", dataView);\n const stepCount = dataView.getUint32(0, true);\n _console.log({ stepCount });\n return stepCount;\n }\n\n parseActivity(dataView: DataView) {\n _console.log(\"parseActivity\", dataView);\n const activity: Partial<Activity> = {};\n\n const activityBitfield = dataView.getUint8(0);\n _console.log(\"activityBitfield\", activityBitfield.toString(2));\n ActivityTypes.forEach((activityType, index) => {\n activity[activityType] = Boolean(activityBitfield & (1 << index));\n });\n\n _console.log(\"activity\", activity);\n\n return activity as Activity;\n }\n\n parseDeviceOrientation(dataView: DataView) {\n _console.log(\"parseDeviceOrientation\", dataView);\n const index = dataView.getUint8(0);\n const deviceOrientation = DeviceOrientations[index];\n _console.assertWithError(deviceOrientation, \"undefined deviceOrientation\");\n _console.log({ deviceOrientation });\n return deviceOrientation;\n }\n}\n\nexport default MotionSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\n\nexport const BarometerSensorTypes = [\"barometer\"] as const;\nexport type BarometerSensorType = (typeof BarometerSensorTypes)[number];\n\nexport const ContinuousBarometerSensorTypes = BarometerSensorTypes;\nexport type ContinuousBarometerSensorType = (typeof ContinuousBarometerSensorTypes)[number];\n\nexport interface BarometerSensorDataEventMessages {\n barometer: {\n barometer: number;\n //altitude: number;\n };\n}\n\nconst _console = createConsole(\"BarometerSensorDataManager\", { log: true });\n\nclass BarometerSensorDataManager {\n #calculcateAltitude(pressure: number) {\n const P0 = 101325; // Standard atmospheric pressure at sea level in Pascals\n const T0 = 288.15; // Standard temperature at sea level in Kelvin\n const L = 0.0065; // Temperature lapse rate in K/m\n const R = 8.3144598; // Universal gas constant in J/(mol·K)\n const g = 9.80665; // Acceleration due to gravity in m/s²\n const M = 0.0289644; // Molar mass of Earth's air in kg/mol\n\n const exponent = (R * L) / (g * M);\n const h = (T0 / L) * (1 - Math.pow(pressure / P0, exponent));\n\n return h;\n }\n\n parseData(dataView: DataView, scalar: number) {\n const pressure = dataView.getUint32(0, true) * scalar;\n const altitude = this.#calculcateAltitude(pressure);\n _console.log({ pressure, altitude });\n return { pressure };\n }\n}\n\nexport default BarometerSensorDataManager;\n","import { sliceDataView } from \"./ArrayBufferUtils.ts\";\nimport { createConsole } from \"./Console.ts\";\nimport { textDecoder } from \"./Text.ts\";\n\nconst _console = createConsole(\"ParseUtils\", { log: true });\n\nexport function parseStringFromDataView(dataView: DataView, byteOffset: number = 0) {\n const stringLength = dataView.getUint8(byteOffset++);\n const string = textDecoder.decode(\n dataView.buffer.slice(dataView.byteOffset + byteOffset, dataView.byteOffset + byteOffset + stringLength)\n );\n byteOffset += stringLength;\n return { string, byteOffset };\n}\n\nexport function parseMessage<MessageType extends string>(\n dataView: DataView,\n messageTypes: readonly MessageType[],\n callback: (messageType: MessageType, dataView: DataView, context?: any) => void,\n context?: any,\n parseMessageLengthAsUint16: boolean = false\n) {\n let byteOffset = 0;\n while (byteOffset < dataView.byteLength) {\n const messageTypeEnum = dataView.getUint8(byteOffset++);\n _console.assertWithError(messageTypeEnum in messageTypes, `invalid messageTypeEnum ${messageTypeEnum}`);\n const messageType = messageTypes[messageTypeEnum];\n\n let messageLength: number;\n if (parseMessageLengthAsUint16) {\n messageLength = dataView.getUint16(byteOffset, true);\n byteOffset += 2;\n } else {\n messageLength = dataView.getUint8(byteOffset++);\n }\n\n _console.log({ messageTypeEnum, messageType, messageLength, dataView, byteOffset });\n\n const _dataView = sliceDataView(dataView, byteOffset, messageLength);\n _console.log({ _dataView });\n\n callback(messageType, _dataView, context);\n\n byteOffset += messageLength;\n }\n}\n","import { createConsole } from \"../utils/Console.ts\";\nimport { parseTimestamp } from \"../utils/MathUtils.ts\";\nimport PressureSensorDataManager, { PressureDataEventMessages } from \"./PressureSensorDataManager.ts\";\nimport MotionSensorDataManager, { MotionSensorDataEventMessages } from \"./MotionSensorDataManager.ts\";\nimport BarometerSensorDataManager, { BarometerSensorDataEventMessages } from \"./BarometerSensorDataManager.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport { MotionSensorTypes, ContinuousMotionTypes } from \"./MotionSensorDataManager.ts\";\nimport { PressureSensorTypes, ContinuousPressureSensorTypes } from \"./PressureSensorDataManager.ts\";\nimport { BarometerSensorTypes, ContinuousBarometerSensorTypes } from \"./BarometerSensorDataManager.ts\";\nimport Device from \"../Device.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"SensorDataManager\", { log: true });\n\nexport const SensorTypes = [...PressureSensorTypes, ...MotionSensorTypes, ...BarometerSensorTypes] as const;\nexport type SensorType = (typeof SensorTypes)[number];\n\nexport const ContinuousSensorTypes = [\n ...ContinuousPressureSensorTypes,\n ...ContinuousMotionTypes,\n ...ContinuousBarometerSensorTypes,\n] as const;\nexport type ContinuousSensorType = (typeof ContinuousSensorTypes)[number];\n\nexport const SensorDataMessageTypes = [\"getPressurePositions\", \"getSensorScalars\", \"sensorData\"] as const;\nexport type SensorDataMessageType = (typeof SensorDataMessageTypes)[number];\n\nexport const SensorDataEventTypes = [...SensorDataMessageTypes, ...SensorTypes] as const;\nexport type SensorDataEventType = (typeof SensorDataEventTypes)[number];\n\ninterface BaseSensorDataEventMessage {\n timestamp: number;\n}\n\ntype BaseSensorDataEventMessages = BarometerSensorDataEventMessages &\n MotionSensorDataEventMessages &\n PressureDataEventMessages;\ntype _SensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseSensorDataEventMessages, \"sensorType\">,\n BaseSensorDataEventMessage\n>;\nexport type SensorDataEventMessage = ValueOf<_SensorDataEventMessages>;\ninterface AnySensorDataEventMessages {\n sensorData: SensorDataEventMessage;\n}\nexport type SensorDataEventMessages = _SensorDataEventMessages & AnySensorDataEventMessages;\n\nexport type SensorDataEventDispatcher = EventDispatcher<Device, SensorDataEventType, SensorDataEventMessages>;\n\nclass SensorDataManager {\n pressureSensorDataManager = new PressureSensorDataManager();\n motionSensorDataManager = new MotionSensorDataManager();\n barometerSensorDataManager = new BarometerSensorDataManager();\n\n #scalars: Map<SensorType, number> = new Map();\n\n static AssertValidSensorType(sensorType: SensorType) {\n _console.assertEnumWithError(sensorType, SensorTypes);\n }\n static AssertValidSensorTypeEnum(sensorTypeEnum: number) {\n _console.assertTypeWithError(sensorTypeEnum, \"number\");\n _console.assertWithError(sensorTypeEnum in SensorTypes, `invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n\n eventDispatcher!: SensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n parseMessage(messageType: SensorDataMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorScalars\":\n this.parseScalars(dataView);\n break;\n case \"getPressurePositions\":\n this.pressureSensorDataManager.parsePositions(dataView);\n break;\n case \"sensorData\":\n this.parseData(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n parseScalars(dataView: DataView) {\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 5) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorScalar = dataView.getFloat32(byteOffset + 1, true);\n _console.log({ sensorType, sensorScalar });\n this.#scalars.set(sensorType, sensorScalar);\n }\n }\n\n private parseData(dataView: DataView) {\n _console.log(\"sensorData\", Array.from(new Uint8Array(dataView.buffer)));\n\n let byteOffset = 0;\n const timestamp = parseTimestamp(dataView, byteOffset);\n byteOffset += 2;\n\n const _dataView = new DataView(dataView.buffer, byteOffset);\n\n parseMessage(_dataView, SensorTypes, this.parseDataCallback.bind(this), { timestamp });\n }\n\n private parseDataCallback(sensorType: SensorType, dataView: DataView, { timestamp }: { timestamp: number }) {\n const scalar = this.#scalars.get(sensorType) || 1;\n\n let sensorData = null;\n switch (sensorType) {\n case \"pressure\":\n sensorData = this.pressureSensorDataManager.parseData(dataView, scalar);\n break;\n case \"acceleration\":\n case \"gravity\":\n case \"linearAcceleration\":\n case \"gyroscope\":\n case \"magnetometer\":\n sensorData = this.motionSensorDataManager.parseVector3(dataView, scalar);\n break;\n case \"gameRotation\":\n case \"rotation\":\n sensorData = this.motionSensorDataManager.parseQuaternion(dataView, scalar);\n break;\n case \"orientation\":\n sensorData = this.motionSensorDataManager.parseEuler(dataView, scalar);\n break;\n case \"stepCounter\":\n sensorData = this.motionSensorDataManager.parseStepCounter(dataView);\n break;\n case \"stepDetector\":\n sensorData = {};\n break;\n case \"activity\":\n sensorData = this.motionSensorDataManager.parseActivity(dataView);\n break;\n case \"deviceOrientation\":\n sensorData = this.motionSensorDataManager.parseDeviceOrientation(dataView);\n break;\n case \"barometer\":\n sensorData = this.barometerSensorDataManager.parseData(dataView, scalar);\n break;\n default:\n _console.error(`uncaught sensorType \"${sensorType}\"`);\n }\n\n _console.assertWithError(sensorData != null, `no sensorData defined for sensorType \"${sensorType}\"`);\n\n _console.log({ sensorType, sensorData });\n // @ts-expect-error\n this.dispatchEvent(sensorType, { sensorType, [sensorType]: sensorData, timestamp });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, [sensorType]: sensorData, timestamp });\n }\n}\n\nexport default SensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport SensorDataManager, { SensorTypes, SensorType } from \"./SensorDataManager.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport Device, { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"../../node_modules/auto-bind/index.js\";\n\nconst _console = createConsole(\"SensorConfigurationManager\", { log: true });\n\nexport type SensorConfiguration = { [sensorType in SensorType]?: number };\n\nexport const MaxSensorRate = 2 ** 16 - 1;\nexport const SensorRateStep = 5;\n\nexport const SensorConfigurationMessageTypes = [\"getSensorConfiguration\", \"setSensorConfiguration\"] as const;\nexport type SensorConfigurationMessageType = (typeof SensorConfigurationMessageTypes)[number];\n\nexport const SensorConfigurationEventTypes = SensorConfigurationMessageTypes;\nexport type SensorConfigurationEventType = (typeof SensorConfigurationEventTypes)[number];\n\nexport interface SensorConfigurationEventMessages {\n getSensorConfiguration: { sensorConfiguration: SensorConfiguration };\n}\n\nexport type SensorConfigurationEventDispatcher = EventDispatcher<\n Device,\n SensorConfigurationEventType,\n SensorConfigurationEventMessages\n>;\n\nexport type SendSensorConfigurationMessageCallback = SendMessageCallback<SensorConfigurationMessageType>;\n\nclass SensorConfigurationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendSensorConfigurationMessageCallback;\n\n eventDispatcher!: SensorConfigurationEventDispatcher;\n get addEventListener() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n #availableSensorTypes!: SensorType[];\n #assertAvailableSensorType(sensorType: SensorType) {\n _console.assertWithError(this.#availableSensorTypes, \"must get initial sensorConfiguration\");\n const isSensorTypeAvailable = this.#availableSensorTypes?.includes(sensorType);\n _console.assert(isSensorTypeAvailable, `unavailable sensor type \"${sensorType}\"`);\n return isSensorTypeAvailable;\n }\n\n #configuration!: SensorConfiguration;\n get configuration() {\n return this.#configuration;\n }\n\n #updateConfiguration(updatedConfiguration: SensorConfiguration) {\n this.#configuration = updatedConfiguration;\n _console.log({ updatedConfiguration: this.#configuration });\n this.#dispatchEvent(\"getSensorConfiguration\", { sensorConfiguration: this.configuration });\n }\n\n #isRedundant(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n return sensorTypes.every((sensorType) => {\n return this.configuration[sensorType] == sensorConfiguration[sensorType];\n });\n }\n\n async setConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n if (clearRest) {\n newSensorConfiguration = Object.assign({ ...this.zeroSensorConfiguration }, newSensorConfiguration);\n }\n _console.log({ newSensorConfiguration });\n if (this.#isRedundant(newSensorConfiguration)) {\n _console.log(\"redundant sensor configuration\");\n return;\n }\n const setSensorConfigurationData = this.#createData(newSensorConfiguration);\n _console.log({ setSensorConfigurationData });\n\n const promise = this.waitForEvent(\"getSensorConfiguration\");\n this.sendMessage([{ type: \"setSensorConfiguration\", data: setSensorConfigurationData.buffer }]);\n await promise;\n }\n\n #parse(dataView: DataView) {\n const parsedSensorConfiguration: SensorConfiguration = {};\n for (let byteOffset = 0; byteOffset < dataView.byteLength; byteOffset += 3) {\n const sensorTypeIndex = dataView.getUint8(byteOffset);\n const sensorType = SensorTypes[sensorTypeIndex];\n if (!sensorType) {\n _console.warn(`unknown sensorType index ${sensorTypeIndex}`);\n continue;\n }\n const sensorRate = dataView.getUint16(byteOffset + 1, true);\n _console.log({ sensorType, sensorRate });\n parsedSensorConfiguration[sensorType] = sensorRate;\n }\n _console.log({ parsedSensorConfiguration });\n this.#availableSensorTypes = Object.keys(parsedSensorConfiguration) as SensorType[];\n return parsedSensorConfiguration;\n }\n\n static #AssertValidSensorRate(sensorRate: number) {\n _console.assertTypeWithError(sensorRate, \"number\");\n _console.assertWithError(sensorRate >= 0, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate < MaxSensorRate, `sensorRate must be 0 or greater (got ${sensorRate})`);\n _console.assertWithError(sensorRate % SensorRateStep == 0, `sensorRate must be multiple of ${SensorRateStep}`);\n }\n\n #assertValidSensorRate(sensorRate: number) {\n SensorConfigurationManager.#AssertValidSensorRate(sensorRate);\n }\n\n #createData(sensorConfiguration: SensorConfiguration) {\n let sensorTypes = Object.keys(sensorConfiguration) as SensorType[];\n sensorTypes = sensorTypes.filter((sensorType) => this.#assertAvailableSensorType(sensorType));\n\n const dataView = new DataView(new ArrayBuffer(sensorTypes.length * 3));\n sensorTypes.forEach((sensorType, index) => {\n SensorDataManager.AssertValidSensorType(sensorType);\n const sensorTypeEnum = SensorTypes.indexOf(sensorType);\n dataView.setUint8(index * 3, sensorTypeEnum);\n\n const sensorRate = sensorConfiguration[sensorType]!;\n this.#assertValidSensorRate(sensorRate);\n dataView.setUint16(index * 3 + 1, sensorRate, true);\n });\n _console.log({ sensorConfigurationData: dataView });\n return dataView;\n }\n\n // ZERO\n static #ZeroSensorConfiguration: SensorConfiguration = {};\n static get ZeroSensorConfiguration() {\n return this.#ZeroSensorConfiguration;\n }\n static {\n SensorTypes.forEach((sensorType) => {\n this.#ZeroSensorConfiguration[sensorType] = 0;\n });\n }\n get zeroSensorConfiguration() {\n const zeroSensorConfiguration: SensorConfiguration = {};\n SensorTypes.forEach((sensorType) => {\n zeroSensorConfiguration[sensorType] = 0;\n });\n return zeroSensorConfiguration;\n }\n async clearSensorConfiguration() {\n return this.setConfiguration(this.zeroSensorConfiguration);\n }\n\n // MESSAGE\n parseMessage(messageType: SensorConfigurationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getSensorConfiguration\":\n case \"setSensorConfiguration\":\n const newSensorConfiguration = this.#parse(dataView);\n this.#updateConfiguration(newSensorConfiguration);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default SensorConfigurationManager;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport SensorDataManager, { SensorTypes } from \"./sensor/SensorDataManager.ts\";\nimport { arrayWithoutDuplicates } from \"./utils/ArrayUtils.ts\";\nimport { SensorRateStep } from \"./sensor/SensorConfigurationManager.ts\";\nimport { parseTimestamp } from \"./utils/MathUtils.ts\";\nimport { SensorType } from \"./sensor/SensorDataManager.ts\";\nimport Device, { SendMessageCallback } from \"./Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"TfliteManager\", { log: true });\n\nexport const TfliteMessageTypes = [\n \"getTfliteName\",\n \"setTfliteName\",\n \"getTfliteTask\",\n \"setTfliteTask\",\n \"getTfliteSampleRate\",\n \"setTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"setTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"setTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"setTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n \"setTfliteInferencingEnabled\",\n \"tfliteInference\",\n] as const;\nexport type TfliteMessageType = (typeof TfliteMessageTypes)[number];\n\nexport const TfliteEventTypes = TfliteMessageTypes;\nexport type TfliteEventType = (typeof TfliteEventTypes)[number];\n\nexport const TfliteTasks = [\"classification\", \"regression\"] as const;\nexport type TfliteTask = (typeof TfliteTasks)[number];\n\nexport interface TfliteEventMessages {\n getTfliteName: { tfliteName: string };\n getTfliteTask: { tfliteTask: TfliteTask };\n getTfliteSampleRate: { tfliteSampleRate: number };\n getTfliteSensorTypes: { tfliteSensorTypes: SensorType[] };\n tfliteIsReady: { tfliteIsReady: boolean };\n getTfliteCaptureDelay: { tfliteCaptureDelay: number };\n getTfliteThreshold: { tfliteThreshold: number };\n getTfliteInferencingEnabled: { tfliteInferencingEnabled: boolean };\n tfliteInference: { tfliteInference: TfliteInference };\n}\n\nexport interface TfliteInference {\n timestamp: number;\n values: number[];\n maxValue?: number;\n maxIndex?: number;\n}\n\nexport type TfliteEventDispatcher = EventDispatcher<Device, TfliteEventType, TfliteEventMessages>;\nexport type SendTfliteMessageCallback = SendMessageCallback<TfliteMessageType>;\n\nexport const TfliteSensorTypes: SensorType[] = [\"pressure\", \"linearAcceleration\", \"gyroscope\", \"magnetometer\"] as const;\nexport type TfliteSensorType = (typeof TfliteSensorTypes)[number];\n\nclass TfliteManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendTfliteMessageCallback;\n\n #assertValidTask(task: TfliteTask) {\n _console.assertEnumWithError(task, TfliteTasks);\n }\n #assertValidTaskEnum(taskEnum: number) {\n _console.assertWithError(taskEnum in TfliteTasks, `invalid taskEnum ${taskEnum}`);\n }\n\n eventDispatcher!: TfliteEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #name!: string;\n get name() {\n return this.#name;\n }\n #parseName(dataView: DataView) {\n _console.log(\"parseName\", dataView);\n const name = textDecoder.decode(dataView.buffer);\n this.#updateName(name);\n }\n #updateName(name: string) {\n _console.log({ name });\n this.#name = name;\n this.#dispatchEvent(\"getTfliteName\", { tfliteName: name });\n }\n async setName(newName: string, sendImmediately?: boolean) {\n _console.assertTypeWithError(newName, \"string\");\n if (this.name == newName) {\n _console.log(`redundant name assignment ${newName}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteName\");\n\n const setNameData = textEncoder.encode(newName);\n this.sendMessage([{ type: \"setTfliteName\", data: setNameData.buffer }], sendImmediately);\n\n await promise;\n }\n\n #task!: TfliteTask;\n get task() {\n return this.#task;\n }\n #parseTask(dataView: DataView) {\n _console.log(\"parseTask\", dataView);\n const taskEnum = dataView.getUint8(0);\n this.#assertValidTaskEnum(taskEnum);\n const task = TfliteTasks[taskEnum];\n this.#updateTask(task);\n }\n #updateTask(task: TfliteTask) {\n _console.log({ task });\n this.#task = task;\n this.#dispatchEvent(\"getTfliteTask\", { tfliteTask: task });\n }\n async setTask(newTask: TfliteTask, sendImmediately?: boolean) {\n this.#assertValidTask(newTask);\n if (this.task == newTask) {\n _console.log(`redundant task assignment ${newTask}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteTask\");\n\n const taskEnum = TfliteTasks.indexOf(newTask);\n this.sendMessage([{ type: \"setTfliteTask\", data: Uint8Array.from([taskEnum]).buffer }], sendImmediately);\n\n await promise;\n }\n\n #sampleRate!: number;\n get sampleRate() {\n return this.#sampleRate;\n }\n #parseSampleRate(dataView: DataView) {\n _console.log(\"parseSampleRate\", dataView);\n const sampleRate = dataView.getUint16(0, true);\n this.#updateSampleRate(sampleRate);\n }\n #updateSampleRate(sampleRate: number) {\n _console.log({ sampleRate });\n this.#sampleRate = sampleRate;\n this.#dispatchEvent(\"getTfliteSampleRate\", { tfliteSampleRate: sampleRate });\n }\n async setSampleRate(newSampleRate: number, sendImmediately?: boolean) {\n _console.assertTypeWithError(newSampleRate, \"number\");\n newSampleRate -= newSampleRate % SensorRateStep;\n _console.assertWithError(\n newSampleRate >= SensorRateStep,\n `sampleRate must be multiple of ${SensorRateStep} greater than 0 (got ${newSampleRate})`\n );\n if (this.#sampleRate == newSampleRate) {\n _console.log(`redundant sampleRate assignment ${newSampleRate}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteSampleRate\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newSampleRate, true);\n this.sendMessage([{ type: \"setTfliteSampleRate\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n static AssertValidSensorType(sensorType: SensorType) {\n SensorDataManager.AssertValidSensorType(sensorType);\n _console.assertWithError(TfliteSensorTypes.includes(sensorType), `invalid tflite sensorType \"${sensorType}\"`);\n }\n\n #sensorTypes: SensorType[] = [];\n get sensorTypes() {\n return this.#sensorTypes.slice();\n }\n #parseSensorTypes(dataView: DataView) {\n _console.log(\"parseSensorTypes\", dataView);\n const sensorTypes: SensorType[] = [];\n for (let index = 0; index < dataView.byteLength; index++) {\n const sensorTypeEnum = dataView.getUint8(index);\n const sensorType = SensorTypes[sensorTypeEnum];\n if (sensorType) {\n sensorTypes.push(sensorType);\n } else {\n _console.error(`invalid sensorTypeEnum ${sensorTypeEnum}`);\n }\n }\n this.#updateSensorTypes(sensorTypes);\n }\n #updateSensorTypes(sensorTypes: SensorType[]) {\n _console.log({ sensorTypes });\n this.#sensorTypes = sensorTypes;\n this.#dispatchEvent(\"getTfliteSensorTypes\", { tfliteSensorTypes: sensorTypes });\n }\n async setSensorTypes(newSensorTypes: SensorType[], sendImmediately?: boolean) {\n newSensorTypes.forEach((sensorType) => {\n TfliteManager.AssertValidSensorType(sensorType);\n });\n\n const promise = this.waitForEvent(\"getTfliteSensorTypes\");\n\n newSensorTypes = arrayWithoutDuplicates(newSensorTypes);\n const newSensorTypeEnums = newSensorTypes.map((sensorType) => SensorTypes.indexOf(sensorType)).sort();\n _console.log(newSensorTypes, newSensorTypeEnums);\n this.sendMessage(\n [{ type: \"setTfliteSensorTypes\", data: Uint8Array.from(newSensorTypeEnums).buffer }],\n sendImmediately\n );\n\n await promise;\n }\n\n #isReady!: boolean;\n get isReady() {\n return this.#isReady;\n }\n #parseIsReady(dataView: DataView) {\n _console.log(\"parseIsReady\", dataView);\n const isReady = Boolean(dataView.getUint8(0));\n this.#updateIsReady(isReady);\n }\n #updateIsReady(isReady: boolean) {\n _console.log({ isReady });\n this.#isReady = isReady;\n this.#dispatchEvent(\"tfliteIsReady\", { tfliteIsReady: isReady });\n }\n #assertIsReady() {\n _console.assertWithError(this.isReady, `tflite is not ready`);\n }\n\n #captureDelay!: number;\n get captureDelay() {\n return this.#captureDelay;\n }\n #parseCaptureDelay(dataView: DataView) {\n _console.log(\"parseCaptureDelay\", dataView);\n const captureDelay = dataView.getUint16(0, true);\n this.#updateCaptueDelay(captureDelay);\n }\n #updateCaptueDelay(captureDelay: number) {\n _console.log({ captureDelay });\n this.#captureDelay = captureDelay;\n this.#dispatchEvent(\"getTfliteCaptureDelay\", { tfliteCaptureDelay: captureDelay });\n }\n async setCaptureDelay(newCaptureDelay: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newCaptureDelay, \"number\");\n if (this.#captureDelay == newCaptureDelay) {\n _console.log(`redundant captureDelay assignment ${newCaptureDelay}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteCaptureDelay\");\n\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, newCaptureDelay, true);\n this.sendMessage([{ type: \"setTfliteCaptureDelay\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #threshold!: number;\n get threshold() {\n return this.#threshold;\n }\n #parseThreshold(dataView: DataView) {\n _console.log(\"parseThreshold\", dataView);\n const threshold = dataView.getFloat32(0, true);\n this.#updateThreshold(threshold);\n }\n #updateThreshold(threshold: number) {\n _console.log({ threshold });\n this.#threshold = threshold;\n this.#dispatchEvent(\"getTfliteThreshold\", { tfliteThreshold: threshold });\n }\n async setThreshold(newThreshold: number, sendImmediately: boolean) {\n _console.assertTypeWithError(newThreshold, \"number\");\n _console.assertWithError(newThreshold >= 0, `threshold must be positive (got ${newThreshold})`);\n if (this.#threshold == newThreshold) {\n _console.log(`redundant threshold assignment ${newThreshold}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteThreshold\");\n\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setFloat32(0, newThreshold, true);\n this.sendMessage([{ type: \"setTfliteThreshold\", data: dataView.buffer }], sendImmediately);\n\n await promise;\n }\n\n #inferencingEnabled!: boolean;\n get inferencingEnabled() {\n return this.#inferencingEnabled;\n }\n #parseInferencingEnabled(dataView: DataView) {\n _console.log(\"parseInferencingEnabled\", dataView);\n const inferencingEnabled = Boolean(dataView.getUint8(0));\n this.#updateInferencingEnabled(inferencingEnabled);\n }\n #updateInferencingEnabled(inferencingEnabled: boolean) {\n _console.log({ inferencingEnabled });\n this.#inferencingEnabled = inferencingEnabled;\n this.#dispatchEvent(\"getTfliteInferencingEnabled\", { tfliteInferencingEnabled: inferencingEnabled });\n }\n async setInferencingEnabled(newInferencingEnabled: boolean, sendImmediately: boolean = true) {\n _console.assertTypeWithError(newInferencingEnabled, \"boolean\");\n if (!newInferencingEnabled && !this.isReady) {\n return;\n }\n this.#assertIsReady();\n if (this.#inferencingEnabled == newInferencingEnabled) {\n _console.log(`redundant inferencingEnabled assignment ${newInferencingEnabled}`);\n return;\n }\n\n const promise = this.waitForEvent(\"getTfliteInferencingEnabled\");\n\n this.sendMessage(\n [\n {\n type: \"setTfliteInferencingEnabled\",\n data: Uint8Array.from([Number(newInferencingEnabled)]).buffer,\n },\n ],\n sendImmediately\n );\n\n await promise;\n }\n async toggleInferencingEnabled() {\n return this.setInferencingEnabled(!this.inferencingEnabled);\n }\n\n async enableInferencing() {\n if (this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(true);\n }\n async disableInferencing() {\n if (!this.inferencingEnabled) {\n return;\n }\n this.setInferencingEnabled(false);\n }\n\n #parseInference(dataView: DataView) {\n _console.log(\"parseInference\", dataView);\n\n const timestamp = parseTimestamp(dataView, 0);\n _console.log({ timestamp });\n\n const values: number[] = [];\n for (let index = 0, byteOffset = 2; byteOffset < dataView.byteLength; index++, byteOffset += 4) {\n const value = dataView.getFloat32(byteOffset, true);\n values.push(value);\n }\n _console.log(\"values\", values);\n\n const inference: TfliteInference = {\n timestamp,\n values,\n };\n\n if (this.task == \"classification\") {\n let maxValue = 0;\n let maxIndex = 0;\n values.forEach((value, index) => {\n if (value > maxValue) {\n maxValue = value;\n maxIndex = index;\n }\n });\n _console.log({ maxIndex, maxValue });\n inference.maxIndex = maxIndex;\n inference.maxValue = maxValue;\n }\n\n this.#dispatchEvent(\"tfliteInference\", { tfliteInference: inference });\n }\n\n parseMessage(messageType: TfliteMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"getTfliteName\":\n case \"setTfliteName\":\n this.#parseName(dataView);\n break;\n case \"getTfliteTask\":\n case \"setTfliteTask\":\n this.#parseTask(dataView);\n break;\n case \"getTfliteSampleRate\":\n case \"setTfliteSampleRate\":\n this.#parseSampleRate(dataView);\n break;\n case \"getTfliteSensorTypes\":\n case \"setTfliteSensorTypes\":\n this.#parseSensorTypes(dataView);\n break;\n case \"tfliteIsReady\":\n this.#parseIsReady(dataView);\n break;\n case \"getTfliteCaptureDelay\":\n case \"setTfliteCaptureDelay\":\n this.#parseCaptureDelay(dataView);\n break;\n case \"getTfliteThreshold\":\n case \"setTfliteThreshold\":\n this.#parseThreshold(dataView);\n break;\n case \"getTfliteInferencingEnabled\":\n case \"setTfliteInferencingEnabled\":\n this.#parseInferencingEnabled(dataView);\n break;\n case \"tfliteInference\":\n this.#parseInference(dataView);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default TfliteManager;\n","import Device from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { textDecoder } from \"./utils/Text.ts\";\n\nconst _console = createConsole(\"DeviceInformationManager\", { log: true });\n\nexport interface PnpId {\n source: \"Bluetooth\" | \"USB\";\n vendorId: number;\n productId: number;\n productVersion: number;\n}\n\nexport interface DeviceInformation {\n manufacturerName: string;\n modelNumber: string;\n softwareRevision: string;\n hardwareRevision: string;\n firmwareRevision: string;\n pnpId: PnpId;\n serialNumber: string;\n}\n\nexport const DeviceInformationMessageTypes = [\n \"manufacturerName\",\n \"modelNumber\",\n \"softwareRevision\",\n \"hardwareRevision\",\n \"firmwareRevision\",\n \"pnpId\",\n \"serialNumber\",\n] as const;\nexport type DeviceInformationMessageType = (typeof DeviceInformationMessageTypes)[number];\n\nexport const DeviceInformationEventTypes = [...DeviceInformationMessageTypes, \"deviceInformation\"] as const;\nexport type DeviceInformationEventType = (typeof DeviceInformationEventTypes)[number];\n\nexport interface DeviceInformationEventMessages {\n manufacturerName: { manufacturerName: string };\n modelNumber: { modelNumber: string };\n softwareRevision: { softwareRevision: string };\n hardwareRevision: { hardwareRevision: string };\n firmwareRevision: { firmwareRevision: string };\n pnpId: { pnpId: PnpId };\n serialNumber: { serialNumber: string };\n deviceInformation: { deviceInformation: DeviceInformation };\n}\n\nexport type DeviceInformationEventDispatcher = EventDispatcher<\n Device,\n DeviceInformationEventType,\n DeviceInformationEventMessages\n>;\n\nclass DeviceInformationManager {\n eventDispatcher!: DeviceInformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #information: Partial<DeviceInformation> = {};\n get information() {\n return this.#information as DeviceInformation;\n }\n clear() {\n this.#information = {};\n }\n get #isComplete() {\n return DeviceInformationMessageTypes.every((key) => key in this.#information);\n }\n\n #update(partialDeviceInformation: Partial<DeviceInformation>) {\n _console.log({ partialDeviceInformation });\n const deviceInformationNames = Object.keys(partialDeviceInformation) as (keyof DeviceInformation)[];\n deviceInformationNames.forEach((deviceInformationName) => {\n // @ts-expect-error\n this.#dispatchEvent(deviceInformationName, {\n [deviceInformationName]: partialDeviceInformation[deviceInformationName],\n });\n });\n\n Object.assign(this.#information, partialDeviceInformation);\n _console.log({ deviceInformation: this.#information });\n if (this.#isComplete) {\n _console.log(\"completed deviceInformation\");\n this.#dispatchEvent(\"deviceInformation\", { deviceInformation: this.information });\n }\n }\n\n parseMessage(messageType: DeviceInformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"manufacturerName\":\n const manufacturerName = textDecoder.decode(dataView.buffer);\n _console.log({ manufacturerName });\n this.#update({ manufacturerName });\n break;\n case \"modelNumber\":\n const modelNumber = textDecoder.decode(dataView.buffer);\n _console.log({ modelNumber });\n this.#update({ modelNumber });\n break;\n case \"softwareRevision\":\n const softwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ softwareRevision });\n this.#update({ softwareRevision });\n break;\n case \"hardwareRevision\":\n const hardwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ hardwareRevision });\n this.#update({ hardwareRevision });\n break;\n case \"firmwareRevision\":\n const firmwareRevision = textDecoder.decode(dataView.buffer);\n _console.log({ firmwareRevision });\n this.#update({ firmwareRevision });\n break;\n case \"pnpId\":\n const pnpId: PnpId = {\n source: dataView.getUint8(0) === 1 ? \"Bluetooth\" : \"USB\",\n productId: dataView.getUint16(3, true),\n productVersion: dataView.getUint16(5, true),\n vendorId: 0,\n };\n if (pnpId.source == \"Bluetooth\") {\n pnpId.vendorId = dataView.getUint16(1, true);\n } else {\n // no need to implement\n }\n _console.log({ pnpId });\n this.#update({ pnpId });\n break;\n case \"serialNumber\":\n const serialNumber = textDecoder.decode(dataView.buffer);\n _console.log({ serialNumber });\n // will only be used for node\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n}\n\nexport default DeviceInformationManager;\n","import Device, { SendMessageCallback } from \"./Device.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { Uint16Max } from \"./utils/MathUtils.ts\";\nimport { textDecoder, textEncoder } from \"./utils/Text.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"InformationManager\", { log: true });\n\nexport const DeviceTypes = [\"leftInsole\", \"rightInsole\"] as const;\nexport type DeviceType = (typeof DeviceTypes)[number];\n\nexport const InsoleSides = [\"left\", \"right\"] as const;\nexport type InsoleSide = (typeof InsoleSides)[number];\n\nexport const MinNameLength = 2;\nexport const MaxNameLength = 30;\n\nexport const InformationMessageTypes = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getMtu\",\n \"getId\",\n \"getName\",\n \"setName\",\n \"getType\",\n \"setType\",\n \"getCurrentTime\",\n \"setCurrentTime\",\n] as const;\nexport type InformationMessageType = (typeof InformationMessageTypes)[number];\n\nexport const InformationEventTypes = InformationMessageTypes;\nexport type InformationEventType = (typeof InformationEventTypes)[number];\n\nexport interface InformationEventMessages {\n isCharging: { isCharging: boolean };\n getBatteryCurrent: { batteryCurrent: number };\n getMtu: { mtu: number };\n getId: { id: string };\n getName: { name: string };\n getType: { type: DeviceType };\n getCurrentTime: { currentTime: number };\n}\n\nexport type InformationEventDispatcher = EventDispatcher<Device, InformationEventType, InformationEventMessages>;\nexport type SendInformationMessageCallback = SendMessageCallback<InformationMessageType>;\n\nclass InformationManager {\n constructor() {\n autoBind(this);\n }\n\n sendMessage!: SendInformationMessageCallback;\n\n eventDispatcher!: InformationEventDispatcher;\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n // PROPERTIES\n\n #isCharging = false;\n get isCharging() {\n return this.#isCharging;\n }\n #updateIsCharging(updatedIsCharging: boolean) {\n _console.assertTypeWithError(updatedIsCharging, \"boolean\");\n this.#isCharging = updatedIsCharging;\n _console.log({ isCharging: this.#isCharging });\n this.#dispatchEvent(\"isCharging\", { isCharging: this.#isCharging });\n }\n\n #batteryCurrent!: number;\n get batteryCurrent() {\n return this.#batteryCurrent;\n }\n async getBatteryCurrent() {\n _console.log(\"getting battery current...\");\n const promise = this.waitForEvent(\"getBatteryCurrent\");\n this.sendMessage([{ type: \"getBatteryCurrent\" }]);\n await promise;\n }\n #updateBatteryCurrent(updatedBatteryCurrent: number) {\n _console.assertTypeWithError(updatedBatteryCurrent, \"number\");\n this.#batteryCurrent = updatedBatteryCurrent;\n _console.log({ batteryCurrent: this.#batteryCurrent });\n this.#dispatchEvent(\"getBatteryCurrent\", { batteryCurrent: this.#batteryCurrent });\n }\n\n #id!: string;\n get id() {\n return this.#id;\n }\n #updateId(updatedId: string) {\n _console.assertTypeWithError(updatedId, \"string\");\n this.#id = updatedId;\n _console.log({ id: this.#id });\n this.#dispatchEvent(\"getId\", { id: this.#id });\n }\n\n #name = \"\";\n get name() {\n return this.#name;\n }\n\n updateName(updatedName: string) {\n _console.assertTypeWithError(updatedName, \"string\");\n this.#name = updatedName;\n _console.log({ updatedName: this.#name });\n this.#dispatchEvent(\"getName\", { name: this.#name });\n }\n async setName(newName: string) {\n _console.assertTypeWithError(newName, \"string\");\n _console.assertWithError(\n newName.length >= MinNameLength,\n `name must be greater than ${MinNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n _console.assertWithError(\n newName.length < MaxNameLength,\n `name must be less than ${MaxNameLength} characters long (\"${newName}\" is ${newName.length} characters long)`\n );\n const setNameData = textEncoder.encode(newName);\n _console.log({ setNameData });\n\n const promise = this.waitForEvent(\"getName\");\n this.sendMessage([{ type: \"setName\", data: setNameData.buffer }]);\n await promise;\n }\n\n // TYPE\n #type!: DeviceType;\n get type() {\n return this.#type;\n }\n get typeEnum() {\n return DeviceTypes.indexOf(this.type);\n }\n #assertValidDeviceType(type: DeviceType) {\n _console.assertEnumWithError(type, DeviceTypes);\n }\n #assertValidDeviceTypeEnum(typeEnum: number) {\n _console.assertTypeWithError(typeEnum, \"number\");\n _console.assertWithError(typeEnum in DeviceTypes, `invalid typeEnum ${typeEnum}`);\n }\n updateType(updatedType: DeviceType) {\n this.#assertValidDeviceType(updatedType);\n if (updatedType == this.type) {\n _console.log(\"redundant type assignment\");\n return;\n }\n this.#type = updatedType;\n _console.log({ updatedType: this.#type });\n\n this.#dispatchEvent(\"getType\", { type: this.#type });\n }\n async #setTypeEnum(newTypeEnum: number) {\n this.#assertValidDeviceTypeEnum(newTypeEnum);\n const setTypeData = Uint8Array.from([newTypeEnum]);\n _console.log({ setTypeData });\n const promise = this.waitForEvent(\"getType\");\n this.sendMessage([{ type: \"setType\", data: setTypeData.buffer }]);\n await promise;\n }\n async setType(newType: DeviceType) {\n this.#assertValidDeviceType(newType);\n const newTypeEnum = DeviceTypes.indexOf(newType);\n this.#setTypeEnum(newTypeEnum);\n }\n\n get isInsole() {\n switch (this.type) {\n case \"leftInsole\":\n case \"rightInsole\":\n return true;\n default:\n // for future non-insole device types\n return false;\n }\n }\n\n get insoleSide(): InsoleSide {\n switch (this.type) {\n case \"leftInsole\":\n return \"left\";\n case \"rightInsole\":\n return \"right\";\n }\n }\n\n #mtu = 0;\n get mtu() {\n return this.#mtu;\n }\n #updateMtu(newMtu: number) {\n _console.assertTypeWithError(newMtu, \"number\");\n if (this.#mtu == newMtu) {\n _console.log(\"redundant mtu assignment\", newMtu);\n return;\n }\n this.#mtu = newMtu;\n\n this.#dispatchEvent(\"getMtu\", { mtu: this.#mtu });\n }\n\n #isCurrentTimeSet = false;\n get isCurrentTimeSet() {\n return this.#isCurrentTimeSet;\n }\n\n #onCurrentTime(currentTime: number) {\n _console.log({ currentTime });\n this.#isCurrentTimeSet = currentTime != 0 || Math.abs(Date.now() - currentTime) < Uint16Max;\n if (!this.#isCurrentTimeSet) {\n this.#setCurrentTime(false);\n }\n }\n async #setCurrentTime(sendImmediately?: boolean) {\n _console.log(\"setting current time...\");\n const dataView = new DataView(new ArrayBuffer(8));\n dataView.setBigUint64(0, BigInt(Date.now()), true);\n const promise = this.waitForEvent(\"getCurrentTime\");\n this.sendMessage([{ type: \"setCurrentTime\", data: dataView.buffer }], sendImmediately);\n await promise;\n }\n\n // MESSAGE\n parseMessage(messageType: InformationMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"isCharging\":\n const isCharging = Boolean(dataView.getUint8(0));\n _console.log({ isCharging });\n this.#updateIsCharging(isCharging);\n break;\n case \"getBatteryCurrent\":\n const batteryCurrent = dataView.getFloat32(0, true);\n _console.log({ batteryCurrent });\n this.#updateBatteryCurrent(batteryCurrent);\n break;\n case \"getId\":\n const id = textDecoder.decode(dataView.buffer);\n _console.log({ id });\n this.#updateId(id);\n break;\n case \"getName\":\n case \"setName\":\n const name = textDecoder.decode(dataView.buffer);\n _console.log({ name });\n this.updateName(name);\n break;\n case \"getType\":\n case \"setType\":\n const typeEnum = dataView.getUint8(0);\n const type = DeviceTypes[typeEnum];\n _console.log({ typeEnum, type });\n this.updateType(type);\n break;\n case \"getMtu\":\n const mtu = dataView.getUint16(0, true);\n _console.log({ mtu });\n this.#updateMtu(mtu);\n break;\n case \"getCurrentTime\":\n case \"setCurrentTime\":\n const currentTime = Number(dataView.getBigUint64(0, true));\n this.#onCurrentTime(currentTime);\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n clear() {\n this.#isCurrentTimeSet = false;\n }\n}\n\nexport default InformationManager;\n","export const VibrationWaveformEffects = [\n \"none\",\n \"strongClick100\",\n \"strongClick60\",\n \"strongClick30\",\n \"sharpClick100\",\n \"sharpClick60\",\n \"sharpClick30\",\n \"softBump100\",\n \"softBump60\",\n \"softBump30\",\n \"doubleClick100\",\n \"doubleClick60\",\n \"tripleClick100\",\n \"softFuzz60\",\n \"strongBuzz100\",\n \"alert750ms\",\n \"alert1000ms\",\n \"strongClick1_100\",\n \"strongClick2_80\",\n \"strongClick3_60\",\n \"strongClick4_30\",\n \"mediumClick100\",\n \"mediumClick80\",\n \"mediumClick60\",\n \"sharpTick100\",\n \"sharpTick80\",\n \"sharpTick60\",\n \"shortDoubleClickStrong100\",\n \"shortDoubleClickStrong80\",\n \"shortDoubleClickStrong60\",\n \"shortDoubleClickStrong30\",\n \"shortDoubleClickMedium100\",\n \"shortDoubleClickMedium80\",\n \"shortDoubleClickMedium60\",\n \"shortDoubleSharpTick100\",\n \"shortDoubleSharpTick80\",\n \"shortDoubleSharpTick60\",\n \"longDoubleSharpClickStrong100\",\n \"longDoubleSharpClickStrong80\",\n \"longDoubleSharpClickStrong60\",\n \"longDoubleSharpClickStrong30\",\n \"longDoubleSharpClickMedium100\",\n \"longDoubleSharpClickMedium80\",\n \"longDoubleSharpClickMedium60\",\n \"longDoubleSharpTick100\",\n \"longDoubleSharpTick80\",\n \"longDoubleSharpTick60\",\n \"buzz100\",\n \"buzz80\",\n \"buzz60\",\n \"buzz40\",\n \"buzz20\",\n \"pulsingStrong100\",\n \"pulsingStrong60\",\n \"pulsingMedium100\",\n \"pulsingMedium60\",\n \"pulsingSharp100\",\n \"pulsingSharp60\",\n \"transitionClick100\",\n \"transitionClick80\",\n \"transitionClick60\",\n \"transitionClick40\",\n \"transitionClick20\",\n \"transitionClick10\",\n \"transitionHum100\",\n \"transitionHum80\",\n \"transitionHum60\",\n \"transitionHum40\",\n \"transitionHum20\",\n \"transitionHum10\",\n \"transitionRampDownLongSmooth2_100\",\n \"transitionRampDownLongSmooth1_100\",\n \"transitionRampDownMediumSmooth1_100\",\n \"transitionRampDownMediumSmooth2_100\",\n \"transitionRampDownShortSmooth1_100\",\n \"transitionRampDownShortSmooth2_100\",\n \"transitionRampDownLongSharp1_100\",\n \"transitionRampDownLongSharp2_100\",\n \"transitionRampDownMediumSharp1_100\",\n \"transitionRampDownMediumSharp2_100\",\n \"transitionRampDownShortSharp1_100\",\n \"transitionRampDownShortSharp2_100\",\n \"transitionRampUpLongSmooth1_100\",\n \"transitionRampUpLongSmooth2_100\",\n \"transitionRampUpMediumSmooth1_100\",\n \"transitionRampUpMediumSmooth2_100\",\n \"transitionRampUpShortSmooth1_100\",\n \"transitionRampUpShortSmooth2_100\",\n \"transitionRampUpLongSharp1_100\",\n \"transitionRampUpLongSharp2_100\",\n \"transitionRampUpMediumSharp1_100\",\n \"transitionRampUpMediumSharp2_100\",\n \"transitionRampUpShortSharp1_100\",\n \"transitionRampUpShortSharp2_100\",\n \"transitionRampDownLongSmooth1_50\",\n \"transitionRampDownLongSmooth2_50\",\n \"transitionRampDownMediumSmooth1_50\",\n \"transitionRampDownMediumSmooth2_50\",\n \"transitionRampDownShortSmooth1_50\",\n \"transitionRampDownShortSmooth2_50\",\n \"transitionRampDownLongSharp1_50\",\n \"transitionRampDownLongSharp2_50\",\n \"transitionRampDownMediumSharp1_50\",\n \"transitionRampDownMediumSharp2_50\",\n \"transitionRampDownShortSharp1_50\",\n \"transitionRampDownShortSharp2_50\",\n \"transitionRampUpLongSmooth1_50\",\n \"transitionRampUpLongSmooth2_50\",\n \"transitionRampUpMediumSmooth1_50\",\n \"transitionRampUpMediumSmooth2_50\",\n \"transitionRampUpShortSmooth1_50\",\n \"transitionRampUpShortSmooth2_50\",\n \"transitionRampUpLongSharp1_50\",\n \"transitionRampUpLongSharp2_50\",\n \"transitionRampUpMediumSharp1_50\",\n \"transitionRampUpMediumSharp2_50\",\n \"transitionRampUpShortSharp1_50\",\n \"transitionRampUpShortSharp2_50\",\n \"longBuzz100\",\n \"smoothHum50\",\n \"smoothHum40\",\n \"smoothHum30\",\n \"smoothHum20\",\n \"smoothHum10\",\n] as const;\n\nexport type VibrationWaveformEffect = (typeof VibrationWaveformEffects)[number];\n","import { createConsole } from \"../utils/Console.ts\";\nimport { VibrationWaveformEffect, VibrationWaveformEffects } from \"./VibrationWaveformEffects.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { SendMessageCallback } from \"../Device.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"VibrationManager\");\n\nexport const VibrationLocations = [\"front\", \"rear\"] as const;\nexport type VibrationLocation = (typeof VibrationLocations)[number];\n\nexport const VibrationTypes = [\"waveformEffect\", \"waveform\"] as const;\nexport type VibrationType = (typeof VibrationTypes)[number];\n\nexport interface VibrationWaveformEffectSegment {\n effect?: VibrationWaveformEffect;\n delay?: number;\n loopCount?: number;\n}\n\nexport interface VibrationWaveformSegment {\n duration: number;\n amplitude: number;\n}\n\nexport const VibrationMessageTypes = [\"triggerVibration\"] as const;\nexport type VibrationMessageType = (typeof VibrationMessageTypes)[number];\n\nexport const MaxNumberOfVibrationWaveformEffectSegments = 8;\nexport const MaxVibrationWaveformSegmentDuration = 2550;\nexport const MaxVibrationWaveformEffectSegmentDelay = 1270;\nexport const MaxVibrationWaveformEffectSegmentLoopCount = 3;\nexport const MaxNumberOfVibrationWaveformSegments = 20;\nexport const MaxVibrationWaveformEffectSequenceLoopCount = 6;\n\ninterface BaseVibrationConfiguration {\n type: VibrationType;\n locations?: VibrationLocation[];\n}\n\nexport interface VibrationWaveformEffectConfiguration extends BaseVibrationConfiguration {\n type: \"waveformEffect\";\n segments: VibrationWaveformEffectSegment[];\n loopCount?: number;\n}\n\nexport interface VibrationWaveformConfiguration extends BaseVibrationConfiguration {\n type: \"waveform\";\n segments: VibrationWaveformSegment[];\n}\n\nexport type VibrationConfiguration = VibrationWaveformEffectConfiguration | VibrationWaveformConfiguration;\n\nexport type SendVibrationMessageCallback = SendMessageCallback<VibrationMessageType>;\n\nclass VibrationManager {\n constructor() {\n autoBind(this);\n }\n sendMessage!: SendVibrationMessageCallback;\n\n #verifyLocation(location: VibrationLocation) {\n _console.assertTypeWithError(location, \"string\");\n _console.assertWithError(VibrationLocations.includes(location), `invalid location \"${location}\"`);\n }\n #verifyLocations(locations: VibrationLocation[]) {\n this.#assertNonEmptyArray(locations);\n locations.forEach((location) => {\n this.#verifyLocation(location);\n });\n }\n #createLocationsBitmask(locations: VibrationLocation[]) {\n this.#verifyLocations(locations);\n\n let locationsBitmask = 0;\n locations.forEach((location) => {\n const locationIndex = VibrationLocations.indexOf(location);\n locationsBitmask |= 1 << locationIndex;\n });\n _console.log({ locationsBitmask });\n _console.assertWithError(locationsBitmask > 0, `locationsBitmask must not be zero`);\n return locationsBitmask;\n }\n\n #assertNonEmptyArray(array: any[]) {\n _console.assertWithError(Array.isArray(array), \"passed non-array\");\n _console.assertWithError(array.length > 0, \"passed empty array\");\n }\n\n #verifyWaveformEffect(waveformEffect: VibrationWaveformEffect) {\n _console.assertWithError(\n VibrationWaveformEffects.includes(waveformEffect),\n `invalid waveformEffect \"${waveformEffect}\"`\n );\n }\n\n #verifyWaveformEffectSegment(waveformEffectSegment: VibrationWaveformEffectSegment) {\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n this.#verifyWaveformEffect(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n _console.assertWithError(delay >= 0, `delay must be 0ms or greater (got ${delay})`);\n _console.assertWithError(\n delay <= MaxVibrationWaveformEffectSegmentDelay,\n `delay must be ${MaxVibrationWaveformEffectSegmentDelay}ms or less (got ${delay})`\n );\n } else {\n throw Error(\"no effect or delay found in waveformEffectSegment\");\n }\n\n if (waveformEffectSegment.loopCount != undefined) {\n const { loopCount } = waveformEffectSegment;\n this.#verifyWaveformEffectSegmentLoopCount(loopCount);\n }\n }\n\n #verifyWaveformEffectSegmentLoopCount(waveformEffectSegmentLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSegmentLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSegmentLoopCount >= 0,\n `waveformEffectSegmentLoopCount must be 0 or greater (got ${waveformEffectSegmentLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSegmentLoopCount <= MaxVibrationWaveformEffectSegmentLoopCount,\n `waveformEffectSegmentLoopCount must be ${MaxVibrationWaveformEffectSegmentLoopCount} or fewer (got ${waveformEffectSegmentLoopCount})`\n );\n }\n\n #verifyWaveformEffectSegments(waveformEffectSegments: VibrationWaveformEffectSegment[]) {\n this.#assertNonEmptyArray(waveformEffectSegments);\n _console.assertWithError(\n waveformEffectSegments.length <= MaxNumberOfVibrationWaveformEffectSegments,\n `must have ${MaxNumberOfVibrationWaveformEffectSegments} waveformEffectSegments or fewer (got ${waveformEffectSegments.length})`\n );\n waveformEffectSegments.forEach((waveformEffectSegment) => {\n this.#verifyWaveformEffectSegment(waveformEffectSegment);\n });\n }\n\n #verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount: number) {\n _console.assertTypeWithError(waveformEffectSequenceLoopCount, \"number\");\n _console.assertWithError(\n waveformEffectSequenceLoopCount >= 0,\n `waveformEffectSequenceLoopCount must be 0 or greater (got ${waveformEffectSequenceLoopCount})`\n );\n _console.assertWithError(\n waveformEffectSequenceLoopCount <= MaxVibrationWaveformEffectSequenceLoopCount,\n `waveformEffectSequenceLoopCount must be ${MaxVibrationWaveformEffectSequenceLoopCount} or fewer (got ${waveformEffectSequenceLoopCount})`\n );\n }\n\n #verifyWaveformSegment(waveformSegment: VibrationWaveformSegment) {\n _console.assertTypeWithError(waveformSegment.amplitude, \"number\");\n _console.assertWithError(\n waveformSegment.amplitude >= 0,\n `amplitude must be 0 or greater (got ${waveformSegment.amplitude})`\n );\n _console.assertWithError(\n waveformSegment.amplitude <= 1,\n `amplitude must be 1 or less (got ${waveformSegment.amplitude})`\n );\n\n _console.assertTypeWithError(waveformSegment.duration, \"number\");\n _console.assertWithError(\n waveformSegment.duration > 0,\n `duration must be greater than 0ms (got ${waveformSegment.duration}ms)`\n );\n _console.assertWithError(\n waveformSegment.duration <= MaxVibrationWaveformSegmentDuration,\n `duration must be ${MaxVibrationWaveformSegmentDuration}ms or less (got ${waveformSegment.duration}ms)`\n );\n }\n\n #verifyWaveformSegments(waveformSegments: VibrationWaveformSegment[]) {\n this.#assertNonEmptyArray(waveformSegments);\n _console.assertWithError(\n waveformSegments.length <= MaxNumberOfVibrationWaveformSegments,\n `must have ${MaxNumberOfVibrationWaveformSegments} waveformSegments or fewer (got ${waveformSegments.length})`\n );\n waveformSegments.forEach((waveformSegment) => {\n this.#verifyWaveformSegment(waveformSegment);\n });\n }\n\n #createWaveformEffectsData(\n locations: VibrationLocation[],\n waveformEffectSegments: VibrationWaveformEffectSegment[],\n waveformEffectSequenceLoopCount: number = 0\n ) {\n this.#verifyWaveformEffectSegments(waveformEffectSegments);\n this.#verifyWaveformEffectSequenceLoopCount(waveformEffectSequenceLoopCount);\n\n let dataArray = [];\n let byteOffset = 0;\n\n const hasAtLeast1WaveformEffectWithANonzeroLoopCount = waveformEffectSegments.some((waveformEffectSegment) => {\n const { loopCount } = waveformEffectSegment;\n return loopCount != undefined && loopCount > 0;\n });\n\n const includeAllWaveformEffectSegments =\n hasAtLeast1WaveformEffectWithANonzeroLoopCount || waveformEffectSequenceLoopCount != 0;\n\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegments && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegment = waveformEffectSegments[index] || { effect: \"none\" };\n if (waveformEffectSegment.effect != undefined) {\n const waveformEffect = waveformEffectSegment.effect;\n dataArray[byteOffset++] = VibrationWaveformEffects.indexOf(waveformEffect);\n } else if (waveformEffectSegment.delay != undefined) {\n const { delay } = waveformEffectSegment;\n dataArray[byteOffset++] = (1 << 7) | Math.floor(delay / 10); // set most significant bit to 1\n } else {\n throw Error(\"invalid waveformEffectSegment\");\n }\n }\n\n const includeAllWaveformEffectSegmentLoopCounts = waveformEffectSequenceLoopCount != 0;\n for (\n let index = 0;\n index < waveformEffectSegments.length ||\n (includeAllWaveformEffectSegmentLoopCounts && index < MaxNumberOfVibrationWaveformEffectSegments);\n index++\n ) {\n const waveformEffectSegmentLoopCount = waveformEffectSegments[index]?.loopCount || 0;\n if (index == 0 || index == 4) {\n dataArray[byteOffset] = 0;\n }\n const bitOffset = 2 * (index % 4);\n dataArray[byteOffset] |= waveformEffectSegmentLoopCount << bitOffset;\n if (index == 3 || index == 7) {\n byteOffset++;\n }\n }\n\n if (waveformEffectSequenceLoopCount != 0) {\n dataArray[byteOffset++] = waveformEffectSequenceLoopCount;\n }\n const dataView = new DataView(Uint8Array.from(dataArray).buffer);\n _console.log({ dataArray, dataView });\n return this.#createData(locations, \"waveformEffect\", dataView);\n }\n #createWaveformData(locations: VibrationLocation[], waveformSegments: VibrationWaveformSegment[]) {\n this.#verifyWaveformSegments(waveformSegments);\n const dataView = new DataView(new ArrayBuffer(waveformSegments.length * 2));\n waveformSegments.forEach((waveformSegment, index) => {\n dataView.setUint8(index * 2, Math.floor(waveformSegment.amplitude * 127));\n dataView.setUint8(index * 2 + 1, Math.floor(waveformSegment.duration / 10));\n });\n _console.log({ dataView });\n return this.#createData(locations, \"waveform\", dataView);\n }\n\n #verifyVibrationType(vibrationType: VibrationType) {\n _console.assertTypeWithError(vibrationType, \"string\");\n _console.assertWithError(VibrationTypes.includes(vibrationType), `invalid vibrationType \"${vibrationType}\"`);\n }\n\n #createData(locations: VibrationLocation[], vibrationType: VibrationType, dataView: DataView) {\n _console.assertWithError(dataView?.byteLength > 0, \"no data received\");\n const locationsBitmask = this.#createLocationsBitmask(locations);\n this.#verifyVibrationType(vibrationType);\n const vibrationTypeIndex = VibrationTypes.indexOf(vibrationType);\n _console.log({ locationsBitmask, vibrationTypeIndex, dataView });\n const data = concatenateArrayBuffers(locationsBitmask, vibrationTypeIndex, dataView.byteLength, dataView);\n _console.log({ data });\n return data;\n }\n\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately: boolean = true) {\n let triggerVibrationData!: ArrayBuffer;\n vibrationConfigurations.forEach((vibrationConfiguration) => {\n const { type } = vibrationConfiguration;\n\n let { locations } = vibrationConfiguration;\n locations = locations || VibrationLocations.slice();\n\n let arrayBuffer: ArrayBuffer;\n\n switch (type) {\n case \"waveformEffect\":\n {\n const { segments, loopCount } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformEffectsData(locations, segments, loopCount);\n }\n break;\n case \"waveform\":\n {\n const { segments } = vibrationConfiguration;\n arrayBuffer = this.#createWaveformData(locations, segments);\n }\n break;\n default:\n throw Error(`invalid vibration type \"${type}\"`);\n }\n _console.log({ type, arrayBuffer });\n triggerVibrationData = concatenateArrayBuffers(triggerVibrationData, arrayBuffer);\n });\n await this.sendMessage([{ type: \"triggerVibration\", data: triggerVibrationData }], sendImmediately);\n }\n}\n\nexport default VibrationManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport Timer from \"../utils/Timer.ts\";\n\nimport { FileTransferMessageTypes } from \"../FileTransferManager.ts\";\nimport { TfliteMessageTypes } from \"../TfliteManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { InformationMessageTypes } from \"../InformationManager.ts\";\nimport { VibrationMessageTypes } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfigurationMessageTypes } from \"../sensor/SensorConfigurationManager.ts\";\nimport { SensorDataMessageTypes } from \"../sensor/SensorDataManager.ts\";\n\nconst _console = createConsole(\"BaseConnectionManager\", { log: true });\n\nexport const ConnectionTypes = [\"webBluetooth\", \"noble\", \"client\"] as const;\nexport type ConnectionType = (typeof ConnectionTypes)[number];\n\nexport const ConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ConnectionStatus = (typeof ConnectionStatuses)[number];\n\nexport const ConnectionEventTypes = [...ConnectionStatuses, \"connectionStatus\", \"isConnected\"] as const;\nexport type ConnectionEventType = (typeof ConnectionEventTypes)[number];\n\nexport interface ConnectionStatusEventMessages {\n notConnected: any;\n connecting: any;\n connected: any;\n disconnecting: any;\n connectionStatus: { connectionStatus: ConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport interface TxMessage {\n type: TxRxMessageType;\n data?: ArrayBuffer;\n}\n\nexport const TxRxMessageTypes = [\n ...InformationMessageTypes,\n ...SensorConfigurationMessageTypes,\n ...SensorDataMessageTypes,\n ...VibrationMessageTypes,\n ...TfliteMessageTypes,\n ...FileTransferMessageTypes,\n] as const;\nexport type TxRxMessageType = (typeof TxRxMessageTypes)[number];\n\nexport const SMPMessageTypes = [\"smp\"] as const;\nexport type SMPMessageType = (typeof SMPMessageTypes)[number];\n\nexport const BatteryLevelMessageTypes = [\"batteryLevel\"] as const;\nexport type BatteryLevelMessageType = (typeof BatteryLevelMessageTypes)[number];\n\nexport const MetaConnectionMessageTypes = [\"rx\", \"tx\"] as const;\nexport type MetaConnectionMessageType = (typeof MetaConnectionMessageTypes)[number];\n\nexport const ConnectionMessageTypes = [\n ...BatteryLevelMessageTypes,\n ...DeviceInformationMessageTypes,\n ...MetaConnectionMessageTypes,\n ...TxRxMessageTypes,\n ...SMPMessageTypes,\n] as const;\nexport type ConnectionMessageType = (typeof ConnectionMessageTypes)[number];\n\nexport type ConnectionStatusCallback = (status: ConnectionStatus) => void;\nexport type MessageReceivedCallback = (messageType: ConnectionMessageType, dataView: DataView) => void;\nexport type MessagesReceivedCallback = () => void;\n\nabstract class BaseConnectionManager {\n static #AssertValidTxRxMessageType(messageType: TxRxMessageType) {\n _console.assertEnumWithError(messageType, TxRxMessageTypes);\n }\n\n abstract get bluetoothId(): string;\n\n // CALLBACKS\n onStatusUpdated?: ConnectionStatusCallback;\n onMessageReceived?: MessageReceivedCallback;\n onMessagesReceived?: MessagesReceivedCallback;\n\n protected get baseConstructor() {\n return this.constructor as typeof BaseConnectionManager;\n }\n static get isSupported() {\n return false;\n }\n get isSupported() {\n return this.baseConstructor.isSupported;\n }\n\n static type: ConnectionType;\n get type(): ConnectionType {\n return this.baseConstructor.type;\n }\n\n /** @throws {Error} if not supported */\n #assertIsSupported() {\n _console.assertWithError(this.isSupported, `${this.constructor.name} is not supported`);\n }\n\n constructor() {\n this.#assertIsSupported();\n }\n\n #status: ConnectionStatus = \"notConnected\";\n get status() {\n return this.#status;\n }\n protected set status(newConnectionStatus) {\n _console.assertEnumWithError(newConnectionStatus, ConnectionStatuses);\n if (this.#status == newConnectionStatus) {\n _console.log(`tried to assign same connection status \"${newConnectionStatus}\"`);\n return;\n }\n _console.log(`new connection status \"${newConnectionStatus}\"`);\n this.#status = newConnectionStatus;\n this.onStatusUpdated!(this.status);\n\n if (this.isConnected) {\n this.#timer.start();\n } else {\n this.#timer.stop();\n }\n\n if (this.#status == \"notConnected\") {\n this.mtu = undefined;\n }\n }\n\n get isConnected() {\n return this.status == \"connected\";\n }\n\n /** @throws {Error} if connected */\n #assertIsNotConnected() {\n _console.assertWithError(!this.isConnected, \"device is already connected\");\n }\n /** @throws {Error} if connecting */\n #assertIsNotConnecting() {\n _console.assertWithError(this.status != \"connecting\", \"device is already connecting\");\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"device is not connected\");\n }\n /** @throws {Error} if disconnecting */\n #assertIsNotDisconnecting() {\n _console.assertWithError(this.status != \"disconnecting\", \"device is already disconnecting\");\n }\n /** @throws {Error} if not connected or is disconnecting */\n #assertIsConnectedAndNotDisconnecting() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n }\n\n async connect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n this.status = \"connecting\";\n }\n get canReconnect() {\n return false;\n }\n async reconnect() {\n this.#assertIsNotConnected();\n this.#assertIsNotConnecting();\n _console.assert(this.canReconnect, \"unable to reconnect\");\n }\n async disconnect() {\n this.#assertIsConnected();\n this.#assertIsNotDisconnecting();\n this.status = \"disconnecting\";\n _console.log(\"disconnecting from device...\");\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n this.#assertIsConnectedAndNotDisconnecting();\n _console.log(\"sending smp message\", data);\n }\n\n #pendingMessages: TxMessage[] = [];\n #isSendingMessages = false;\n async sendTxMessages(messages: TxMessage[] | undefined, sendImmediately: boolean = true) {\n this.#assertIsConnectedAndNotDisconnecting();\n\n if (messages) {\n this.#pendingMessages.push(...messages);\n _console.log(`appended ${messages.length} messages`);\n }\n\n if (!sendImmediately) {\n _console.log(\"not sending immediately - waiting until later\");\n return;\n }\n\n if (this.#isSendingMessages) {\n console.log(\"already sending messages - waiting until later\");\n return;\n }\n this.#isSendingMessages = true;\n\n _console.log(\"sendTxMessages\", this.#pendingMessages.slice());\n\n const arrayBuffers = this.#pendingMessages.map((message) => {\n BaseConnectionManager.#AssertValidTxRxMessageType(message.type);\n const messageTypeEnum = TxRxMessageTypes.indexOf(message.type);\n const dataLength = new DataView(new ArrayBuffer(2));\n dataLength.setUint16(0, message.data?.byteLength || 0, true);\n return concatenateArrayBuffers(messageTypeEnum, dataLength, message.data);\n });\n this.#pendingMessages.length = 0;\n\n if (this.mtu) {\n while (arrayBuffers.length > 0) {\n let arrayBufferByteLength = 0;\n let arrayBufferCount = 0;\n arrayBuffers.some((arrayBuffer) => {\n if (arrayBufferByteLength + arrayBuffer.byteLength > this.mtu! - 3) {\n return true;\n }\n arrayBufferCount++;\n arrayBufferByteLength += arrayBuffer.byteLength;\n });\n const arrayBuffersToSend = arrayBuffers.splice(0, arrayBufferCount);\n _console.log({ arrayBufferCount, arrayBuffersToSend });\n\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffersToSend);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n } else {\n const arrayBuffer = concatenateArrayBuffers(...arrayBuffers);\n _console.log(\"sending arrayBuffer\", arrayBuffer);\n await this.sendTxData(arrayBuffer);\n }\n\n this.#isSendingMessages = false;\n }\n\n mtu?: number;\n\n async sendTxData(data: ArrayBuffer) {\n _console.log(\"sendTxData\", data);\n }\n\n parseRxMessage(dataView: DataView) {\n parseMessage(dataView, TxRxMessageTypes, this.#onRxMessage.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onRxMessage(messageType: TxRxMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n this.onMessageReceived!(messageType, dataView);\n }\n\n #timer = new Timer(this.#checkConnection.bind(this), 5000);\n #checkConnection() {\n //console.log(\"checking connection...\");\n if (!this.isConnected) {\n _console.log(\"timer detected disconnection\");\n this.status = \"notConnected\";\n }\n }\n\n clear() {\n this.#isSendingMessages = false;\n this.#pendingMessages.length = 0;\n }\n}\n\nexport default BaseConnectionManager;\n","export function spacesToPascalCase(string: string) {\n return string\n .replace(/(?:^\\w|\\b\\w)/g, function (match) {\n return match.toUpperCase();\n })\n .replace(/\\s+/g, \"\");\n}\n\nexport function capitalizeFirstCharacter(string: string) {\n return string[0].toUpperCase() + string.slice(1);\n}\n","import { createConsole } from \"./Console.ts\";\nimport { spacesToPascalCase } from \"./stringUtils.ts\";\n\nconst _console = createConsole(\"EventUtils\", { log: false });\n\ntype BoundEventListeners = { [eventType: string]: EventListener };\nexport type BoundGenericEventListeners = { [eventType: string]: Function };\n\nexport function bindEventListeners(\n eventTypes: readonly string[],\n boundEventListeners: BoundGenericEventListeners,\n target: any\n) {\n _console.log(\"bindEventListeners\", { eventTypes, boundEventListeners, target });\n eventTypes.forEach((eventType) => {\n const _eventType = `_on${spacesToPascalCase(eventType)}`;\n _console.assertWithError(target[_eventType], `no event \"${_eventType}\" found in target`);\n _console.log(`binding eventType \"${eventType}\" as ${_eventType} from target`, target);\n const boundEvent = target[_eventType].bind(target);\n target[_eventType] = boundEvent;\n boundEventListeners[eventType] = boundEvent;\n });\n}\n\nexport function addEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let addEventListener = target.addEventListener || target.addListener || target.on || target.AddEventListener;\n _console.assertWithError(addEventListener, \"no add listener function found for target\");\n addEventListener = addEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n addEventListener(eventType, eventListener);\n });\n}\n\nexport function removeEventListeners(target: any, boundEventListeners: BoundGenericEventListeners) {\n let removeEventListener = target.removeEventListener || target.removeListener || target.RemoveEventListener;\n _console.assertWithError(removeEventListener, \"no remove listener function found for target\");\n removeEventListener = removeEventListener.bind(target);\n Object.entries(boundEventListeners).forEach(([eventType, eventListener]) => {\n removeEventListener(eventType, eventListener);\n });\n}\n","import { isInBrowser, isInNode } from \"../../utils/environment.ts\";\nimport { createConsole } from \"../../utils/Console.ts\";\n\nconst _console = createConsole(\"bluetoothUUIDs\", { log: false });\n\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nvar BluetoothUUID = webbluetooth.BluetoothUUID;\n/** NODE_END */\n/** BROWSER_START */\nif (isInBrowser) {\n var BluetoothUUID = window.BluetoothUUID;\n}\n/** BROWSER_END */\n\nfunction generateBluetoothUUID(value: string): BluetoothServiceUUID {\n _console.assertTypeWithError(value, \"string\");\n _console.assertWithError(value.length == 4, \"value must be 4 characters long\");\n return `ea6da725-${value}-4f9b-893d-c3913e33b39f`;\n}\n\nfunction stringToCharacteristicUUID(identifier: string): BluetoothCharacteristicUUID {\n return BluetoothUUID?.getCharacteristic?.(identifier);\n}\n\nfunction stringToServiceUUID(identifier: string): BluetoothServiceUUID {\n return BluetoothUUID?.getService?.(identifier);\n}\n\nexport type BluetoothServiceName = \"deviceInformation\" | \"battery\" | \"main\" | \"smp\";\nimport { DeviceInformationMessageType } from \"../../DeviceInformationManager.ts\";\nexport type BluetoothCharacteristicName = DeviceInformationMessageType | \"batteryLevel\" | \"rx\" | \"tx\" | \"smp\";\n\ninterface BluetoothCharacteristicInformation {\n uuid: BluetoothCharacteristicUUID;\n}\ninterface BluetoothServiceInformation {\n uuid: BluetoothServiceUUID;\n characteristics: { [characteristicName in BluetoothCharacteristicName]?: BluetoothCharacteristicInformation };\n}\ninterface BluetoothServicesInformation {\n services: { [serviceName in BluetoothServiceName]: BluetoothServiceInformation };\n}\nconst bluetoothUUIDs: BluetoothServicesInformation = Object.freeze({\n services: {\n deviceInformation: {\n uuid: stringToServiceUUID(\"device_information\"),\n characteristics: {\n manufacturerName: {\n uuid: stringToCharacteristicUUID(\"manufacturer_name_string\"),\n },\n modelNumber: {\n uuid: stringToCharacteristicUUID(\"model_number_string\"),\n },\n hardwareRevision: {\n uuid: stringToCharacteristicUUID(\"hardware_revision_string\"),\n },\n firmwareRevision: {\n uuid: stringToCharacteristicUUID(\"firmware_revision_string\"),\n },\n softwareRevision: {\n uuid: stringToCharacteristicUUID(\"software_revision_string\"),\n },\n pnpId: {\n uuid: stringToCharacteristicUUID(\"pnp_id\"),\n },\n serialNumber: {\n uuid: stringToCharacteristicUUID(\"serial_number_string\"),\n },\n },\n },\n battery: {\n uuid: stringToServiceUUID(\"battery_service\"),\n characteristics: {\n batteryLevel: {\n uuid: stringToCharacteristicUUID(\"battery_level\"),\n },\n },\n },\n main: {\n uuid: generateBluetoothUUID(\"0000\"),\n characteristics: {\n rx: { uuid: generateBluetoothUUID(\"1000\") },\n tx: { uuid: generateBluetoothUUID(\"1001\") },\n },\n },\n smp: {\n uuid: \"8d53dc1d-1db7-4cd3-868b-8a527460aa84\",\n characteristics: {\n smp: { uuid: \"da2e7828-fbce-4e01-ae9e-261174997c48\" },\n },\n },\n },\n});\n\nexport const serviceUUIDs = [bluetoothUUIDs.services.main.uuid];\nexport const optionalServiceUUIDs = [\n bluetoothUUIDs.services.deviceInformation.uuid,\n bluetoothUUIDs.services.battery.uuid,\n bluetoothUUIDs.services.smp.uuid,\n];\nexport const allServiceUUIDs = [...serviceUUIDs, ...optionalServiceUUIDs];\n\nexport function getServiceNameFromUUID(serviceUUID: BluetoothServiceUUID): BluetoothServiceName | undefined {\n serviceUUID = serviceUUID.toString().toLowerCase();\n const serviceNames = Object.keys(bluetoothUUIDs.services) as BluetoothServiceName[];\n return serviceNames.find((serviceName) => {\n const serviceInfo = bluetoothUUIDs.services[serviceName];\n let serviceInfoUUID = serviceInfo.uuid.toString();\n if (serviceUUID.length == 4) {\n serviceInfoUUID = serviceInfoUUID.slice(4, 8);\n }\n if (!serviceUUID.includes(\"-\")) {\n serviceInfoUUID = serviceInfoUUID.replaceAll(\"-\", \"\");\n }\n return serviceUUID == serviceInfoUUID;\n });\n}\n\nexport const characteristicUUIDs: BluetoothCharacteristicUUID[] = [];\nexport const allCharacteristicUUIDs: BluetoothCharacteristicUUID[] = [];\n\nexport const characteristicNames: BluetoothCharacteristicName[] = [];\nexport const allCharacteristicNames: BluetoothCharacteristicName[] = [];\n\nObject.values(bluetoothUUIDs.services).forEach((serviceInfo) => {\n if (!serviceInfo.characteristics) {\n return;\n }\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicNames.forEach((characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[characteristicName]!;\n if (serviceUUIDs.includes(serviceInfo.uuid)) {\n characteristicUUIDs.push(characteristicInfo.uuid);\n characteristicNames.push(characteristicName);\n }\n allCharacteristicUUIDs.push(characteristicInfo.uuid);\n allCharacteristicNames.push(characteristicName);\n });\n}, []);\n\n//_console.log({ characteristicUUIDs, allCharacteristicUUIDs });\n\nexport function getCharacteristicNameFromUUID(\n characteristicUUID: BluetoothCharacteristicUUID\n): BluetoothCharacteristicName | undefined {\n //_console.log({ characteristicUUID });\n characteristicUUID = characteristicUUID.toString().toLowerCase();\n var characteristicName: BluetoothCharacteristicName | undefined;\n Object.values(bluetoothUUIDs.services).some((serviceInfo) => {\n const characteristicNames = Object.keys(serviceInfo.characteristics) as BluetoothCharacteristicName[];\n characteristicName = characteristicNames.find((_characteristicName) => {\n const characteristicInfo = serviceInfo.characteristics[_characteristicName]!;\n let characteristicInfoUUID = characteristicInfo.uuid.toString();\n if (characteristicUUID.length == 4) {\n characteristicInfoUUID = characteristicInfoUUID.slice(4, 8);\n }\n if (!characteristicUUID.includes(\"-\")) {\n characteristicInfoUUID = characteristicInfoUUID.replaceAll(\"-\", \"\");\n }\n return characteristicUUID == characteristicInfoUUID;\n });\n return characteristicName;\n });\n return characteristicName;\n}\n\nexport function getCharacteristicProperties(\n characteristicName: BluetoothCharacteristicName\n): BluetoothCharacteristicProperties {\n const properties = {\n broadcast: false,\n read: true,\n writeWithoutResponse: false,\n write: false,\n notify: false,\n indicate: false,\n authenticatedSignedWrites: false,\n reliableWrite: false,\n writableAuxiliaries: false,\n };\n\n // read\n switch (characteristicName) {\n case \"rx\":\n case \"tx\":\n case \"smp\":\n properties.read = false;\n break;\n }\n\n // notify\n switch (characteristicName) {\n case \"batteryLevel\":\n case \"rx\":\n case \"smp\":\n properties.notify = true;\n break;\n }\n\n // write without response\n switch (characteristicName) {\n case \"smp\":\n properties.writeWithoutResponse = true;\n break;\n }\n\n // write\n switch (characteristicName) {\n case \"tx\":\n properties.write = true;\n break;\n }\n\n return properties;\n}\n\nexport const serviceDataUUID = \"0000\";\n","import { createConsole } from \"../../utils/Console.ts\";\nimport BaseConnectionManager from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"BluetoothConnectionManager\", { log: true });\n\nimport { BluetoothCharacteristicName } from \"./bluetoothUUIDs.ts\";\n\nabstract class BluetoothConnectionManager extends BaseConnectionManager {\n isInRange = true;\n\n protected onCharacteristicValueChanged(characteristicName: BluetoothCharacteristicName, dataView: DataView) {\n if (characteristicName == \"rx\") {\n this.parseRxMessage(dataView);\n } else {\n this.onMessageReceived?.(characteristicName, dataView);\n }\n }\n\n protected async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n _console.log(\"writeCharacteristic\", ...arguments);\n }\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n await this.writeCharacteristic(\"smp\", data);\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n await this.writeCharacteristic(\"tx\", data);\n }\n}\n\nexport default BluetoothConnectionManager;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { isInNode, isInBrowser, isInBluefy, isInWebBLE } from \"../../utils/environment.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport {\n serviceUUIDs,\n optionalServiceUUIDs,\n getServiceNameFromUUID,\n getCharacteristicNameFromUUID,\n getCharacteristicProperties,\n} from \"./bluetoothUUIDs.ts\";\nimport BluetoothConnectionManager from \"./BluetoothConnectionManager.ts\";\nimport { BluetoothCharacteristicName, BluetoothServiceName } from \"./bluetoothUUIDs.ts\";\nimport { ConnectionType } from \"../BaseConnectionManager.ts\";\n\nconst _console = createConsole(\"WebBluetoothConnectionManager\", { log: true });\n\ntype WebBluetoothInterface = webbluetooth.Bluetooth | Bluetooth;\n\ninterface BluetoothService extends BluetoothRemoteGATTService {\n name?: BluetoothServiceName;\n}\ninterface BluetoothCharacteristic extends BluetoothRemoteGATTCharacteristic {\n name?: BluetoothCharacteristicName;\n}\n\nvar bluetooth: WebBluetoothInterface | undefined;\n/** NODE_START */\nimport * as webbluetooth from \"webbluetooth\";\nif (isInNode) {\n bluetooth = webbluetooth.bluetooth;\n}\n/** NODE_END */\n\n/** BROWSER_START */\nif (isInBrowser) {\n bluetooth = window.navigator.bluetooth;\n}\n/** BROWSER_END */\n\nclass WebBluetoothConnectionManager extends BluetoothConnectionManager {\n get bluetoothId() {\n return this.device!.id;\n }\n\n #boundBluetoothCharacteristicEventListeners: { [eventType: string]: EventListener } = {\n characteristicvaluechanged: this.#onCharacteristicvaluechanged.bind(this),\n };\n #boundBluetoothDeviceEventListeners: { [eventType: string]: EventListener } = {\n gattserverdisconnected: this.#onGattserverdisconnected.bind(this),\n };\n\n static get isSupported() {\n return Boolean(bluetooth);\n }\n static get type(): ConnectionType {\n return \"webBluetooth\";\n }\n\n #device!: BluetoothDevice | undefined;\n get device() {\n return this.#device;\n }\n set device(newDevice) {\n if (this.#device == newDevice) {\n _console.log(\"tried to assign the same BluetoothDevice\");\n return;\n }\n if (this.#device) {\n removeEventListeners(this.#device, this.#boundBluetoothDeviceEventListeners);\n }\n if (newDevice) {\n addEventListeners(newDevice, this.#boundBluetoothDeviceEventListeners);\n }\n this.#device = newDevice;\n }\n\n get server(): BluetoothRemoteGATTServer | undefined {\n return this.#device?.gatt;\n }\n get isConnected() {\n return this.server?.connected || false;\n }\n\n #services: Map<BluetoothServiceName, BluetoothService> = new Map();\n #characteristics: Map<BluetoothCharacteristicName, BluetoothCharacteristic> = new Map();\n\n async connect() {\n await super.connect();\n\n try {\n const device = await bluetooth!.requestDevice({\n filters: [{ services: serviceUUIDs }],\n optionalServices: isInBrowser ? optionalServiceUUIDs : [],\n });\n\n _console.log(\"got BluetoothDevice\");\n this.device = device;\n\n _console.log(\"connecting to device...\");\n const server = await this.server!.connect();\n _console.log(`connected to device? ${server.connected}`);\n\n await this.#getServicesAndCharacteristics();\n\n _console.log(\"fully connected\");\n\n this.status = \"connected\";\n } catch (error) {\n _console.error(error);\n this.status = \"notConnected\";\n this.server?.disconnect();\n this.#removeEventListeners();\n }\n }\n async #getServicesAndCharacteristics() {\n this.#removeEventListeners();\n\n _console.log(\"getting services...\");\n const services = await this.server!.getPrimaryServices();\n _console.log(\"got services\", services.length);\n //const service = await this.server!.getPrimaryService(\"8d53dc1d-1db7-4cd3-868b-8a527460aa84\");\n\n _console.log(\"getting characteristics...\");\n for (const serviceIndex in services) {\n const service = services[serviceIndex] as BluetoothService;\n _console.log({ service });\n const serviceName = getServiceNameFromUUID(service.uuid)!;\n _console.assertWithError(serviceName, `no name found for service uuid \"${service.uuid}\"`);\n _console.log(`got \"${serviceName}\" service`);\n service.name = serviceName;\n this.#services.set(serviceName, service);\n _console.log(`getting characteristics for \"${serviceName}\" service`);\n const characteristics = await service.getCharacteristics();\n _console.log(`got characteristics for \"${serviceName}\" service`);\n for (const characteristicIndex in characteristics) {\n const characteristic = characteristics[characteristicIndex] as BluetoothCharacteristic;\n _console.log({ characteristic });\n const characteristicName = getCharacteristicNameFromUUID(characteristic.uuid)!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic uuid \"${characteristic.uuid}\" in \"${serviceName}\" service`\n );\n _console.log(`got \"${characteristicName}\" characteristic in \"${serviceName}\" service`);\n characteristic.name = characteristicName;\n this.#characteristics.set(characteristicName, characteristic);\n addEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`starting notifications for \"${characteristicName}\" characteristic`);\n await characteristic.startNotifications();\n }\n if (characteristicProperties.read) {\n _console.log(`reading \"${characteristicName}\" characteristic...`);\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n }\n }\n async #removeEventListeners() {\n if (this.device) {\n removeEventListeners(this.device, this.#boundBluetoothDeviceEventListeners);\n }\n\n const promises = Array.from(this.#characteristics.keys()).map((characteristicName) => {\n const characteristic = this.#characteristics.get(characteristicName)!;\n removeEventListeners(characteristic, this.#boundBluetoothCharacteristicEventListeners);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.notify) {\n _console.log(`stopping notifications for \"${characteristicName}\" characteristic`);\n return characteristic.stopNotifications();\n }\n });\n\n return Promise.allSettled(promises);\n }\n async disconnect() {\n await this.#removeEventListeners();\n await super.disconnect();\n this.server?.disconnect();\n this.status = \"notConnected\";\n }\n\n #onCharacteristicvaluechanged(event: Event) {\n _console.log(\"oncharacteristicvaluechanged\");\n\n const characteristic = event.target as BluetoothCharacteristic;\n this.#onCharacteristicValueChanged(characteristic);\n }\n\n #onCharacteristicValueChanged(characteristic: BluetoothCharacteristic) {\n _console.log(\"onCharacteristicValue\");\n\n const characteristicName = characteristic.name!;\n _console.assertWithError(\n Boolean(characteristicName),\n `no name found for characteristic with uuid \"${characteristic.uuid}\"`\n );\n\n _console.log(`oncharacteristicvaluechanged for \"${characteristicName}\" characteristic`);\n const dataView = characteristic.value!;\n _console.assertWithError(dataView, `no data found for \"${characteristicName}\" characteristic`);\n _console.log(`data for \"${characteristicName}\" characteristic`, Array.from(new Uint8Array(dataView.buffer)));\n\n try {\n this.onCharacteristicValueChanged(characteristicName, dataView);\n } catch (error) {\n _console.error(error);\n }\n }\n\n async writeCharacteristic(characteristicName: BluetoothCharacteristicName, data: ArrayBuffer) {\n super.writeCharacteristic(characteristicName, data);\n\n const characteristic = this.#characteristics.get(characteristicName)!;\n _console.assertWithError(characteristic, `${characteristicName} characteristic not found`);\n _console.log(\"writing characteristic\", characteristic, data);\n const characteristicProperties = characteristic.properties || getCharacteristicProperties(characteristicName);\n if (characteristicProperties.writeWithoutResponse) {\n _console.log(\"writing without response\");\n await characteristic.writeValueWithoutResponse(data);\n } else {\n _console.log(\"writing with response\");\n await characteristic.writeValueWithResponse(data);\n }\n _console.log(\"wrote characteristic\");\n\n if (characteristicProperties.read && !characteristicProperties.notify) {\n _console.log(\"reading value after write...\");\n await characteristic.readValue();\n if (isInBluefy || isInWebBLE) {\n this.#onCharacteristicValueChanged(characteristic);\n }\n }\n }\n\n #onGattserverdisconnected() {\n _console.log(\"gattserverdisconnected\");\n this.status = \"notConnected\";\n }\n\n get canReconnect() {\n return Boolean(this.server && !this.server.connected && this.isInRange);\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.status = \"connecting\";\n try {\n await this.server!.connect();\n } catch (error) {\n _console.error(error);\n this.isInRange = false;\n }\n\n if (this.isConnected) {\n _console.log(\"successfully reconnected!\");\n await this.#getServicesAndCharacteristics();\n this.status = \"connected\";\n } else {\n _console.log(\"unable to reconnect\");\n this.status = \"notConnected\";\n }\n }\n}\n\nexport default WebBluetoothConnectionManager;\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nconst POW_2_24 = 5.960464477539063e-8;\nconst POW_2_32 = 4294967296;\nconst POW_2_53 = 9007199254740992;\n\nexport function encode(value) {\n let data = new ArrayBuffer(256);\n let dataView = new DataView(data);\n let lastLength;\n let offset = 0;\n\n function prepareWrite(length) {\n let newByteLength = data.byteLength;\n const requiredLength = offset + length;\n while (newByteLength < requiredLength) {\n newByteLength <<= 1;\n }\n if (newByteLength !== data.byteLength) {\n const oldDataView = dataView;\n data = new ArrayBuffer(newByteLength);\n dataView = new DataView(data);\n const uint32count = (offset + 3) >> 2;\n for (let i = 0; i < uint32count; ++i) {\n dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));\n }\n }\n\n lastLength = length;\n return dataView;\n }\n function commitWrite() {\n offset += lastLength;\n }\n function writeFloat64(value) {\n commitWrite(prepareWrite(8).setFloat64(offset, value));\n }\n function writeUint8(value) {\n commitWrite(prepareWrite(1).setUint8(offset, value));\n }\n function writeUint8Array(value) {\n const dataView = prepareWrite(value.length);\n for (let i = 0; i < value.length; ++i) {\n dataView.setUint8(offset + i, value[i]);\n }\n commitWrite();\n }\n function writeUint16(value) {\n commitWrite(prepareWrite(2).setUint16(offset, value));\n }\n function writeUint32(value) {\n commitWrite(prepareWrite(4).setUint32(offset, value));\n }\n function writeUint64(value) {\n const low = value % POW_2_32;\n const high = (value - low) / POW_2_32;\n const dataView = prepareWrite(8);\n dataView.setUint32(offset, high);\n dataView.setUint32(offset + 4, low);\n commitWrite();\n }\n function writeTypeAndLength(type, length) {\n if (length < 24) {\n writeUint8((type << 5) | length);\n } else if (length < 0x100) {\n writeUint8((type << 5) | 24);\n writeUint8(length);\n } else if (length < 0x10000) {\n writeUint8((type << 5) | 25);\n writeUint16(length);\n } else if (length < 0x100000000) {\n writeUint8((type << 5) | 26);\n writeUint32(length);\n } else {\n writeUint8((type << 5) | 27);\n writeUint64(length);\n }\n }\n\n function encodeItem(value) {\n let i;\n const utf8data = [];\n let length;\n\n if (value === false) {\n return writeUint8(0xf4);\n }\n if (value === true) {\n return writeUint8(0xf5);\n }\n if (value === null) {\n return writeUint8(0xf6);\n }\n if (value === undefined) {\n return writeUint8(0xf7);\n }\n\n switch (typeof value) {\n case \"number\":\n if (Math.floor(value) === value) {\n if (value >= 0 && value <= POW_2_53) {\n return writeTypeAndLength(0, value);\n }\n if (-POW_2_53 <= value && value < 0) {\n return writeTypeAndLength(1, -(value + 1));\n }\n }\n writeUint8(0xfb);\n return writeFloat64(value);\n\n case \"string\":\n for (i = 0; i < value.length; ++i) {\n let charCode = value.charCodeAt(i);\n if (charCode < 0x80) {\n utf8data.push(charCode);\n } else if (charCode < 0x800) {\n utf8data.push(0xc0 | (charCode >> 6));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else if (charCode < 0xd800) {\n utf8data.push(0xe0 | (charCode >> 12));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n } else {\n charCode = (charCode & 0x3ff) << 10;\n charCode |= value.charCodeAt(++i) & 0x3ff;\n charCode += 0x10000;\n\n utf8data.push(0xf0 | (charCode >> 18));\n utf8data.push(0x80 | ((charCode >> 12) & 0x3f));\n utf8data.push(0x80 | ((charCode >> 6) & 0x3f));\n utf8data.push(0x80 | (charCode & 0x3f));\n }\n }\n\n writeTypeAndLength(3, utf8data.length);\n return writeUint8Array(utf8data);\n\n default:\n if (Array.isArray(value)) {\n length = value.length;\n writeTypeAndLength(4, length);\n for (i = 0; i < length; ++i) {\n encodeItem(value[i]);\n }\n } else if (value instanceof Uint8Array) {\n writeTypeAndLength(2, value.length);\n writeUint8Array(value);\n } else {\n const keys = Object.keys(value);\n length = keys.length;\n writeTypeAndLength(5, length);\n for (i = 0; i < length; ++i) {\n const key = keys[i];\n encodeItem(key);\n encodeItem(value[key]);\n }\n }\n }\n }\n\n encodeItem(value);\n\n if (\"slice\" in data) {\n return data.slice(0, offset);\n }\n\n const ret = new ArrayBuffer(offset);\n const retView = new DataView(ret);\n for (let i = 0; i < offset; ++i) {\n retView.setUint8(i, dataView.getUint8(i));\n }\n return ret;\n}\n\nexport function decode(data, tagger, simpleValue) {\n const dataView = new DataView(data);\n let offset = 0;\n\n if (typeof tagger !== \"function\") {\n tagger = function (value) {\n return value;\n };\n }\n if (typeof simpleValue !== \"function\") {\n simpleValue = function () {\n return undefined;\n };\n }\n\n function commitRead(length, value) {\n offset += length;\n return value;\n }\n function readArrayBuffer(length) {\n return commitRead(length, new Uint8Array(data, offset, length));\n }\n function readFloat16() {\n const tempArrayBuffer = new ArrayBuffer(4);\n const tempDataView = new DataView(tempArrayBuffer);\n const value = readUint16();\n\n const sign = value & 0x8000;\n let exponent = value & 0x7c00;\n const fraction = value & 0x03ff;\n\n if (exponent === 0x7c00) {\n exponent = 0xff << 10;\n } else if (exponent !== 0) {\n exponent += (127 - 15) << 10;\n } else if (fraction !== 0) {\n return (sign ? -1 : 1) * fraction * POW_2_24;\n }\n\n tempDataView.setUint32(0, (sign << 16) | (exponent << 13) | (fraction << 13));\n return tempDataView.getFloat32(0);\n }\n function readFloat32() {\n return commitRead(4, dataView.getFloat32(offset));\n }\n function readFloat64() {\n return commitRead(8, dataView.getFloat64(offset));\n }\n function readUint8() {\n return commitRead(1, dataView.getUint8(offset));\n }\n function readUint16() {\n return commitRead(2, dataView.getUint16(offset));\n }\n function readUint32() {\n return commitRead(4, dataView.getUint32(offset));\n }\n function readUint64() {\n return readUint32() * POW_2_32 + readUint32();\n }\n function readBreak() {\n if (dataView.getUint8(offset) !== 0xff) {\n return false;\n }\n offset += 1;\n return true;\n }\n function readLength(additionalInformation) {\n if (additionalInformation < 24) {\n return additionalInformation;\n }\n if (additionalInformation === 24) {\n return readUint8();\n }\n if (additionalInformation === 25) {\n return readUint16();\n }\n if (additionalInformation === 26) {\n return readUint32();\n }\n if (additionalInformation === 27) {\n return readUint64();\n }\n if (additionalInformation === 31) {\n return -1;\n }\n throw new Error(\"Invalid length encoding\");\n }\n function readIndefiniteStringLength(majorType) {\n const initialByte = readUint8();\n if (initialByte === 0xff) {\n return -1;\n }\n const length = readLength(initialByte & 0x1f);\n if (length < 0 || initialByte >> 5 !== majorType) {\n throw new Error(\"Invalid indefinite length element\");\n }\n return length;\n }\n\n function appendUtf16Data(utf16data, length) {\n for (let i = 0; i < length; ++i) {\n let value = readUint8();\n if (value & 0x80) {\n if (value < 0xe0) {\n value = ((value & 0x1f) << 6) | (readUint8() & 0x3f);\n length -= 1;\n } else if (value < 0xf0) {\n value = ((value & 0x0f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 2;\n } else {\n value =\n ((value & 0x0f) << 18) | ((readUint8() & 0x3f) << 12) | ((readUint8() & 0x3f) << 6) | (readUint8() & 0x3f);\n length -= 3;\n }\n }\n\n if (value < 0x10000) {\n utf16data.push(value);\n } else {\n value -= 0x10000;\n utf16data.push(0xd800 | (value >> 10));\n utf16data.push(0xdc00 | (value & 0x3ff));\n }\n }\n }\n\n function decodeItem() {\n const initialByte = readUint8();\n const majorType = initialByte >> 5;\n const additionalInformation = initialByte & 0x1f;\n let i;\n let length;\n\n if (majorType === 7) {\n switch (additionalInformation) {\n case 25:\n return readFloat16();\n case 26:\n return readFloat32();\n case 27:\n return readFloat64();\n }\n }\n\n length = readLength(additionalInformation);\n if (length < 0 && (majorType < 2 || majorType > 6)) {\n throw new Error(\"Invalid length\");\n }\n\n const utf16data = [];\n let retArray;\n const retObject = {};\n\n switch (majorType) {\n case 0:\n return length;\n case 1:\n return -1 - length;\n case 2:\n if (length < 0) {\n const elements = [];\n let fullArrayLength = 0;\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n fullArrayLength += length;\n elements.push(readArrayBuffer(length));\n }\n const fullArray = new Uint8Array(fullArrayLength);\n let fullArrayOffset = 0;\n for (i = 0; i < elements.length; ++i) {\n fullArray.set(elements[i], fullArrayOffset);\n fullArrayOffset += elements[i].length;\n }\n return fullArray;\n }\n return readArrayBuffer(length);\n case 3:\n if (length < 0) {\n while ((length = readIndefiniteStringLength(majorType)) >= 0) {\n appendUtf16Data(utf16data, length);\n }\n } else {\n appendUtf16Data(utf16data, length);\n }\n return String.fromCharCode.apply(null, utf16data);\n case 4:\n if (length < 0) {\n retArray = [];\n while (!readBreak()) {\n retArray.push(decodeItem());\n }\n } else {\n retArray = new Array(length);\n for (i = 0; i < length; ++i) {\n retArray[i] = decodeItem();\n }\n }\n return retArray;\n case 5:\n for (i = 0; i < length || (length < 0 && !readBreak()); ++i) {\n const key = decodeItem();\n retObject[key] = decodeItem();\n }\n return retObject;\n case 6:\n return tagger(decodeItem(), length);\n case 7:\n switch (length) {\n case 20:\n return false;\n case 21:\n return true;\n case 22:\n return null;\n case 23:\n return undefined;\n default:\n return simpleValue(length);\n }\n }\n }\n\n const ret = decodeItem();\n if (offset !== data.byteLength) {\n throw new Error(\"Remaining bytes\");\n }\n return ret;\n}\n\nexport const CBOR = {\n encode,\n decode,\n};\n","/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2023 Laird Connectivity\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * @file mcumgr\n * @brief Provides MCU manager operation functions for the Xbit USB Shell.\n * This file is inspired by the MIT licensed mcumgr file originally\n * authored by Andras Barthazi (https://github.com/boogie/mcumgr-web),\n * updated to also support file upload/download over SMP.\n */\n\nimport { CBOR } from \"./cbor.js\";\nimport { createConsole } from \"./Console.ts\";\n\nconst _console = createConsole(\"mcumgr\", { log: true });\n\nexport const constants = {\n // Opcodes\n MGMT_OP_READ: 0,\n MGMT_OP_READ_RSP: 1,\n MGMT_OP_WRITE: 2,\n MGMT_OP_WRITE_RSP: 3,\n\n // Groups\n MGMT_GROUP_ID_OS: 0,\n MGMT_GROUP_ID_IMAGE: 1,\n MGMT_GROUP_ID_STAT: 2,\n MGMT_GROUP_ID_CONFIG: 3,\n MGMT_GROUP_ID_LOG: 4,\n MGMT_GROUP_ID_CRASH: 5,\n MGMT_GROUP_ID_SPLIT: 6,\n MGMT_GROUP_ID_RUN: 7,\n MGMT_GROUP_ID_FS: 8,\n MGMT_GROUP_ID_SHELL: 9,\n\n // OS group\n OS_MGMT_ID_ECHO: 0,\n OS_MGMT_ID_CONS_ECHO_CTRL: 1,\n OS_MGMT_ID_TASKSTAT: 2,\n OS_MGMT_ID_MPSTAT: 3,\n OS_MGMT_ID_DATETIME_STR: 4,\n OS_MGMT_ID_RESET: 5,\n\n // Image group\n IMG_MGMT_ID_STATE: 0,\n IMG_MGMT_ID_UPLOAD: 1,\n IMG_MGMT_ID_FILE: 2,\n IMG_MGMT_ID_CORELIST: 3,\n IMG_MGMT_ID_CORELOAD: 4,\n IMG_MGMT_ID_ERASE: 5,\n\n // Filesystem group\n FS_MGMT_ID_FILE: 0,\n};\n\nexport class MCUManager {\n constructor() {\n this._mtu = 256;\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n onMessage(callback) {\n this._messageCallback = callback;\n return this;\n }\n\n onImageUploadNext(callback) {\n this._imageUploadNextCallback = callback;\n return this;\n }\n\n onImageUploadProgress(callback) {\n this._imageUploadProgressCallback = callback;\n return this;\n }\n\n onImageUploadFinished(callback) {\n this._imageUploadFinishedCallback = callback;\n return this;\n }\n\n onFileUploadNext(callback) {\n this._fileUploadNextCallback = callback;\n return this;\n }\n\n onFileUploadProgress(callback) {\n this._fileUploadProgressCallback = callback;\n return this;\n }\n\n onFileUploadFinished(callback) {\n this._fileUploadFinishedCallback = callback;\n return this;\n }\n\n onFileDownloadNext(callback) {\n this._fileDownloadNextCallback = callback;\n return this;\n }\n\n onFileDownloadProgress(callback) {\n this._fileDownloadProgressCallback = callback;\n return this;\n }\n\n onFileDownloadFinished(callback) {\n this._fileDownloadFinishedCallback = callback;\n return this;\n }\n\n _getMessage(op, group, id, data) {\n const _flags = 0;\n let encodedData = [];\n if (typeof data !== \"undefined\") {\n encodedData = [...new Uint8Array(CBOR.encode(data))];\n }\n const lengthLo = encodedData.length & 255;\n const lengthHi = encodedData.length >> 8;\n const groupLo = group & 255;\n const groupHi = group >> 8;\n const message = [op, _flags, lengthHi, lengthLo, groupHi, groupLo, this._seq, id, ...encodedData];\n this._seq = (this._seq + 1) % 256;\n\n return message;\n }\n\n _notification(buffer) {\n _console.log(\"mcumgr - message received\");\n const message = new Uint8Array(buffer);\n this._buffer = new Uint8Array([...this._buffer, ...message]);\n const messageLength = this._buffer[2] * 256 + this._buffer[3];\n if (this._buffer.length < messageLength + 8) return;\n this._processMessage(this._buffer.slice(0, messageLength + 8));\n this._buffer = this._buffer.slice(messageLength + 8);\n }\n\n _processMessage(message) {\n const [op, , lengthHi, lengthLo, groupHi, groupLo, , id] = message;\n const data = CBOR.decode(message.slice(8).buffer);\n const length = lengthHi * 256 + lengthLo;\n const group = groupHi * 256 + groupLo;\n\n _console.log(\"mcumgr - Process Message - Group: \" + group + \", Id: \" + id + \", Off: \" + data.off);\n if (group === constants.MGMT_GROUP_ID_IMAGE && id === constants.IMG_MGMT_ID_UPLOAD && data.off) {\n this._uploadOffset = data.off;\n this._uploadNext();\n return;\n }\n if (\n op === constants.MGMT_OP_WRITE_RSP &&\n group === constants.MGMT_GROUP_ID_FS &&\n id === constants.FS_MGMT_ID_FILE &&\n data.off\n ) {\n this._uploadFileOffset = data.off;\n this._uploadFileNext();\n return;\n }\n if (op === constants.MGMT_OP_READ_RSP && group === constants.MGMT_GROUP_ID_FS && id === constants.FS_MGMT_ID_FILE) {\n this._downloadFileOffset += data.data.length;\n if (data.len != undefined) {\n this._downloadFileLength = data.len;\n }\n _console.log(\"downloaded \" + this._downloadFileOffset + \" bytes of \" + this._downloadFileLength);\n if (this._downloadFileLength > 0) {\n this._fileDownloadProgressCallback({\n percentage: Math.floor((this._downloadFileOffset / this._downloadFileLength) * 100),\n });\n }\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n this._downloadFileNext();\n return;\n }\n\n if (this._messageCallback) this._messageCallback({ op, group, id, data, length });\n }\n\n cmdReset() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_RESET);\n }\n\n smpEcho(message) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_OS, constants.OS_MGMT_ID_ECHO, {\n d: message,\n });\n }\n\n cmdImageState() {\n return this._getMessage(constants.MGMT_OP_READ, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE);\n }\n\n cmdImageErase() {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_ERASE, {});\n }\n\n cmdImageTest(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: false,\n });\n }\n\n cmdImageConfirm(hash) {\n return this._getMessage(constants.MGMT_OP_WRITE, constants.MGMT_GROUP_ID_IMAGE, constants.IMG_MGMT_ID_STATE, {\n hash,\n confirm: true,\n });\n }\n\n _hash(image) {\n return crypto.subtle.digest(\"SHA-256\", image);\n }\n\n async _uploadNext() {\n if (!this._uploadImage) {\n return;\n }\n\n if (this._uploadOffset >= this._uploadImage.byteLength) {\n this._uploadIsInProgress = false;\n this._imageUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadOffset };\n if (this._uploadOffset === 0) {\n message.len = this._uploadImage.byteLength;\n message.sha = new Uint8Array(await this._hash(this._uploadImage));\n }\n this._imageUploadProgressCallback({\n percentage: Math.floor((this._uploadOffset / this._uploadImage.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead - 3 - 5;\n\n message.data = new Uint8Array(this._uploadImage.slice(this._uploadOffset, this._uploadOffset + length));\n\n this._uploadOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_IMAGE,\n constants.IMG_MGMT_ID_UPLOAD,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._imageUploadNextCallback({ packet });\n }\n async reset() {\n this._messageCallback = null;\n this._imageUploadProgressCallback = null;\n this._imageUploadNextCallback = null;\n this._fileUploadProgressCallback = null;\n this._fileUploadNextCallback = null;\n this._uploadIsInProgress = false;\n this._downloadIsInProgress = false;\n this._buffer = new Uint8Array();\n this._seq = 0;\n }\n\n async cmdUpload(image, slot = 0) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n\n this._uploadOffset = 0;\n this._uploadImage = image;\n this._uploadSlot = slot;\n\n this._uploadNext();\n }\n\n async cmdUploadFile(filebuf, destFilename) {\n if (this._uploadIsInProgress) {\n _console.error(\"Upload is already in progress.\");\n return;\n }\n this._uploadIsInProgress = true;\n this._uploadFileOffset = 0;\n this._uploadFile = filebuf;\n this._uploadFilename = destFilename;\n\n this._uploadFileNext();\n }\n\n async _uploadFileNext() {\n _console.log(\"uploadFileNext - offset: \" + this._uploadFileOffset + \", length: \" + this._uploadFile.byteLength);\n\n if (this._uploadFileOffset >= this._uploadFile.byteLength) {\n this._uploadIsInProgress = false;\n this._fileUploadFinishedCallback();\n return;\n }\n\n const nmpOverhead = 8;\n const message = { data: new Uint8Array(), off: this._uploadFileOffset };\n if (this._uploadFileOffset === 0) {\n message.len = this._uploadFile.byteLength;\n }\n message.name = this._uploadFilename;\n this._fileUploadProgressCallback({\n percentage: Math.floor((this._uploadFileOffset / this._uploadFile.byteLength) * 100),\n });\n\n const length = this._mtu - CBOR.encode(message).byteLength - nmpOverhead;\n\n message.data = new Uint8Array(this._uploadFile.slice(this._uploadFileOffset, this._uploadFileOffset + length));\n\n this._uploadFileOffset += length;\n\n const packet = this._getMessage(\n constants.MGMT_OP_WRITE,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n\n _console.log(\"mcumgr - _uploadNext: Message Length: \" + packet.length);\n\n this._fileUploadNextCallback({ packet });\n }\n\n async cmdDownloadFile(filename, destFilename) {\n if (this._downloadIsInProgress) {\n _console.error(\"Download is already in progress.\");\n return;\n }\n this._downloadIsInProgress = true;\n this._downloadFileOffset = 0;\n this._downloadFileLength = 0;\n this._downloadRemoteFilename = filename;\n this._downloadLocalFilename = destFilename;\n\n this._downloadFileNext();\n }\n\n async _downloadFileNext() {\n if (this._downloadFileLength > 0) {\n if (this._downloadFileOffset >= this._downloadFileLength) {\n this._downloadIsInProgress = false;\n this._fileDownloadFinishedCallback();\n return;\n }\n }\n\n const message = { off: this._downloadFileOffset };\n if (this._downloadFileOffset === 0) {\n message.name = this._downloadRemoteFilename;\n }\n\n const packet = this._getMessage(\n constants.MGMT_OP_READ,\n constants.MGMT_GROUP_ID_FS,\n constants.FS_MGMT_ID_FILE,\n message\n );\n _console.log(\"mcumgr - _downloadNext: Message Length: \" + packet.length);\n this._fileDownloadNextCallback({ packet });\n }\n\n async imageInfo(image) {\n const info = {};\n const view = new Uint8Array(image);\n\n // check header length\n if (view.length < 32) {\n throw new Error(\"Invalid image (too short file)\");\n }\n\n // check MAGIC bytes 0x96f3b83d\n if (view[0] !== 0x3d || view[1] !== 0xb8 || view[2] !== 0xf3 || view[3] !== 0x96) {\n throw new Error(\"Invalid image (wrong magic bytes)\");\n }\n\n // check load address is 0x00000000\n if (view[4] !== 0x00 || view[5] !== 0x00 || view[6] !== 0x00 || view[7] !== 0x00) {\n throw new Error(\"Invalid image (wrong load address)\");\n }\n\n const headerSize = view[8] + view[9] * 2 ** 8;\n\n // check protected TLV area size is 0\n if (view[10] !== 0x00 || view[11] !== 0x00) {\n throw new Error(\"Invalid image (wrong protected TLV area size)\");\n }\n\n const imageSize = view[12] + view[13] * 2 ** 8 + view[14] * 2 ** 16 + view[15] * 2 ** 24;\n info.imageSize = imageSize;\n\n // check image size is correct\n if (view.length < imageSize + headerSize) {\n throw new Error(\"Invalid image (wrong image size)\");\n }\n\n // check flags is 0x00000000\n if (view[16] !== 0x00 || view[17] !== 0x00 || view[18] !== 0x00 || view[19] !== 0x00) {\n throw new Error(\"Invalid image (wrong flags)\");\n }\n\n const version = `${view[20]}.${view[21]}.${view[22] + view[23] * 2 ** 8}`;\n info.version = version;\n\n info.hash = [...new Uint8Array(await this._hash(image.slice(0, imageSize + 32)))]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return info;\n }\n}\n","import Device, { SendSmpMessageCallback } from \"./Device.ts\";\nimport { getFileBuffer } from \"./utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher from \"./utils/EventDispatcher.ts\";\nimport { MCUManager, constants } from \"./utils/mcumgr.js\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport autoBind from \"auto-bind\";\n\nconst _console = createConsole(\"FirmwareManager\", { log: true });\n\nexport const FirmwareMessageTypes = [\"smp\"] as const;\nexport type FirmwareMessageType = (typeof FirmwareMessageTypes)[number];\n\nexport const FirmwareEventTypes = [\n ...FirmwareMessageTypes,\n \"firmwareImages\",\n \"firmwareUploadProgress\",\n \"firmwareStatus\",\n \"firmwareUploadComplete\",\n] as const;\nexport type FirmwareEventType = (typeof FirmwareEventTypes)[number];\n\nexport const FirmwareStatuses = [\"idle\", \"uploading\", \"uploaded\", \"pending\", \"testing\", \"erasing\"] as const;\nexport type FirmwareStatus = (typeof FirmwareStatuses)[number];\n\nexport interface FirmwareImage {\n slot: number;\n active: boolean;\n confirmed: boolean;\n pending: boolean;\n permanent: boolean;\n bootable: boolean;\n version: string;\n hash?: Uint8Array;\n empty?: boolean;\n}\n\nexport interface FirmwareEventMessages {\n smp: { dataView: DataView };\n firmwareImages: { firmwareImages: FirmwareImage[] };\n firmwareUploadProgress: { progress: number };\n firmwareStatus: { firmwareStatus: FirmwareStatus };\n //firmwareUploadComplete: {};\n}\n\nexport type FirmwareEventDispatcher = EventDispatcher<Device, FirmwareEventType, FirmwareEventMessages>;\n\nclass FirmwareManager {\n sendMessage!: SendSmpMessageCallback;\n\n constructor() {\n this.#assignMcuManagerCallbacks();\n autoBind(this);\n }\n\n eventDispatcher!: FirmwareEventDispatcher;\n get addEventListenter() {\n return this.eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.eventDispatcher.waitForEvent;\n }\n\n parseMessage(messageType: FirmwareMessageType, dataView: DataView) {\n _console.log({ messageType });\n\n switch (messageType) {\n case \"smp\":\n this.#mcuManager._notification(Array.from(new Uint8Array(dataView.buffer)));\n this.#dispatchEvent(\"smp\", { dataView });\n break;\n default:\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n async uploadFirmware(file: FileLike) {\n _console.log(\"uploadFirmware\", file);\n\n const promise = this.waitForEvent(\"firmwareUploadComplete\");\n\n await this.getImages();\n\n const arrayBuffer = await getFileBuffer(file);\n const imageInfo = await this.#mcuManager.imageInfo(arrayBuffer);\n _console.log({ imageInfo });\n\n this.#mcuManager.cmdUpload(arrayBuffer, 1);\n\n this.#updateStatus(\"uploading\");\n\n await promise;\n }\n\n #status: FirmwareStatus = \"idle\";\n get status() {\n return this.#status;\n }\n #updateStatus(newStatus: FirmwareStatus) {\n _console.assertEnumWithError(newStatus, FirmwareStatuses);\n if (this.#status == newStatus) {\n _console.log(`redundant firmwareStatus assignment \"${newStatus}\"`);\n return;\n }\n\n this.#status = newStatus;\n _console.log({ firmwareStatus: this.#status });\n this.#dispatchEvent(\"firmwareStatus\", { firmwareStatus: this.#status });\n }\n\n // COMMANDS\n\n #images!: FirmwareImage[];\n get images() {\n return this.#images;\n }\n #assertImages() {\n _console.assertWithError(this.#images, \"didn't get imageState\");\n }\n #assertValidImageIndex(imageIndex: number) {\n _console.assertTypeWithError(imageIndex, \"number\");\n _console.assertWithError(imageIndex == 0 || imageIndex == 1, \"imageIndex must be 0 or 1\");\n }\n async getImages() {\n const promise = this.waitForEvent(\"firmwareImages\");\n\n _console.log(\"getting firmware image state...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageState()).buffer);\n\n await promise;\n }\n\n async testImage(imageIndex: number = 1) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (!this.#images[imageIndex]) {\n _console.log(`image ${imageIndex} not found`);\n return;\n }\n if (this.#images[imageIndex].pending == true) {\n _console.log(`image ${imageIndex} is already pending`);\n return;\n }\n if (this.#images[imageIndex].empty) {\n _console.log(`image ${imageIndex} is empty`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"testing firmware image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageTest(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async eraseImage() {\n this.#assertImages();\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"erasing image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageErase()).buffer);\n\n this.#updateStatus(\"erasing\");\n\n await promise;\n await this.getImages();\n }\n\n async confirmImage(imageIndex: number = 0) {\n this.#assertValidImageIndex(imageIndex);\n this.#assertImages();\n if (this.#images[imageIndex].confirmed === true) {\n _console.log(`image ${imageIndex} is already confirmed`);\n return;\n }\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"confirming image...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdImageConfirm(this.#images[imageIndex].hash)).buffer);\n\n await promise;\n }\n\n async echo(string: string) {\n _console.assertTypeWithError(string, \"string\");\n\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"sending echo...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.smpEcho(string)).buffer);\n\n await promise;\n }\n\n async reset() {\n const promise = this.waitForEvent(\"smp\");\n\n _console.log(\"resetting...\");\n this.sendMessage(Uint8Array.from(this.#mcuManager.cmdReset()).buffer);\n\n await promise;\n }\n\n // MTU\n #mtu!: number;\n get mtu() {\n return this.#mtu;\n }\n set mtu(newMtu: number) {\n this.#mtu = newMtu;\n this.#mcuManager._mtu = newMtu;\n }\n\n // MCUManager\n #mcuManager = new MCUManager();\n\n #assignMcuManagerCallbacks() {\n this.#mcuManager.onMessage(this.#onMcuMessage.bind(this));\n\n this.#mcuManager.onFileDownloadNext(this.#onMcuFileDownloadNext);\n this.#mcuManager.onFileDownloadProgress(this.#onMcuFileDownloadProgress.bind(this));\n this.#mcuManager.onFileDownloadFinished(this.#onMcuFileDownloadFinished.bind(this));\n\n this.#mcuManager.onFileUploadNext(this.#onMcuFileUploadNext.bind(this));\n this.#mcuManager.onFileUploadProgress(this.#onMcuFileUploadProgress.bind(this));\n this.#mcuManager.onFileUploadFinished(this.#onMcuFileUploadFinished.bind(this));\n\n this.#mcuManager.onImageUploadNext(this.#onMcuImageUploadNext.bind(this));\n this.#mcuManager.onImageUploadProgress(this.#onMcuImageUploadProgress.bind(this));\n this.#mcuManager.onImageUploadFinished(this.#onMcuImageUploadFinished.bind(this));\n }\n\n #onMcuMessage({ op, group, id, data, length }: { op: number; group: number; id: number; data: any; length: number }) {\n _console.log(\"onMcuMessage\", ...arguments);\n\n switch (group) {\n case constants.MGMT_GROUP_ID_OS:\n switch (id) {\n case constants.OS_MGMT_ID_ECHO:\n _console.log(`echo \"${data.r}\"`);\n break;\n case constants.OS_MGMT_ID_TASKSTAT:\n _console.table(data.tasks);\n break;\n case constants.OS_MGMT_ID_MPSTAT:\n _console.log(data);\n break;\n }\n break;\n case constants.MGMT_GROUP_ID_IMAGE:\n switch (id) {\n case constants.IMG_MGMT_ID_STATE:\n this.#onMcuImageState(data);\n }\n break;\n default:\n throw Error(`uncaught mcuMessage group ${group}`);\n }\n }\n\n #onMcuFileDownloadNext() {\n _console.log(\"onMcuFileDownloadNext\", ...arguments);\n }\n #onMcuFileDownloadProgress() {\n _console.log(\"onMcuFileDownloadProgress\", ...arguments);\n }\n #onMcuFileDownloadFinished() {\n _console.log(\"onMcuFileDownloadFinished\", ...arguments);\n }\n\n #onMcuFileUploadNext() {\n _console.log(\"onMcuFileUploadNext\");\n }\n #onMcuFileUploadProgress() {\n _console.log(\"onMcuFileUploadProgress\");\n }\n #onMcuFileUploadFinished() {\n _console.log(\"onMcuFileUploadFinished\");\n }\n\n #onMcuImageUploadNext({ packet }: { packet: number[] }) {\n _console.log(\"onMcuImageUploadNext\");\n this.sendMessage(Uint8Array.from(packet).buffer);\n }\n #onMcuImageUploadProgress({ percentage }: { percentage: number }) {\n const progress = percentage / 100;\n _console.log(\"onMcuImageUploadProgress\", ...arguments);\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress });\n }\n async #onMcuImageUploadFinished() {\n _console.log(\"onMcuImageUploadFinished\", ...arguments);\n\n await this.getImages();\n\n this.#dispatchEvent(\"firmwareUploadProgress\", { progress: 100 });\n this.#dispatchEvent(\"firmwareUploadComplete\", {});\n }\n\n #onMcuImageState({ images }: { images?: FirmwareImage[] }) {\n if (images) {\n this.#images = images;\n _console.log(\"images\", this.#images);\n } else {\n _console.log(\"no images found\");\n return;\n }\n\n let newStatus: FirmwareStatus = \"idle\";\n\n if (this.#images.length == 2) {\n if (!this.#images[1].bootable) {\n _console.warn('Slot 1 has a invalid image. Click \"Erase Image\" to erase it or upload a different image');\n } else if (!this.#images[0].confirmed) {\n _console.log(\n 'Slot 0 has a valid image. Click \"Confirm Image\" to confirm it or wait and the device will swap images back.'\n );\n newStatus = \"testing\";\n } else {\n if (this.#images[1].pending) {\n _console.log(\"reset to upload to the new firmware image\");\n newStatus = \"pending\";\n } else {\n _console.log(\"Slot 1 has a valid image. run testImage() to test it or upload a different image.\");\n newStatus = \"uploaded\";\n }\n }\n }\n\n if (this.#images.length == 1) {\n this.#images.push({\n slot: 1,\n empty: true,\n version: \"Empty\",\n pending: false,\n confirmed: false,\n bootable: false,\n active: false,\n permanent: false,\n });\n\n _console.log(\"Select a firmware upload image to upload to slot 1.\");\n }\n\n this.#updateStatus(newStatus);\n this.#dispatchEvent(\"firmwareImages\", { firmwareImages: this.#images });\n }\n}\n\nexport default FirmwareManager;\n","import { ConnectionStatus } from \"./connection/BaseConnectionManager.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport Device, { BoundDeviceEventListeners, DeviceEventMap } from \"./Device.ts\";\nimport { DeviceType } from \"./InformationManager.ts\";\nimport { createConsole } from \"./utils/Console.ts\";\nimport { isInBluefy, isInBrowser } from \"./utils/environment.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport { addEventListeners } from \"./utils/EventUtils.ts\";\n\nconst _console = createConsole(\"DeviceManager\", { log: true });\n\nexport interface LocalStorageDeviceInformation {\n type: DeviceType;\n bluetoothId: string;\n}\n\nexport interface LocalStorageConfiguration {\n devices: LocalStorageDeviceInformation[];\n}\n\nexport const DeviceManagerEventTypes = [\n \"deviceConnected\",\n \"deviceDisconnected\",\n \"deviceIsConnected\",\n \"availableDevices\",\n \"connectedDevices\",\n] as const;\nexport type DeviceManagerEventType = (typeof DeviceManagerEventTypes)[number];\n\ninterface DeviceManagerEventMessage {\n device: Device;\n}\nexport interface DeviceManagerEventMessages {\n deviceConnected: DeviceManagerEventMessage;\n deviceDisconnected: DeviceManagerEventMessage;\n deviceIsConnected: DeviceManagerEventMessage;\n availableDevices: { availableDevices: Device[] };\n connectedDevices: { connectedDevices: Device[] };\n}\n\nexport type DeviceManagerEventDispatcher = EventDispatcher<\n DeviceManager,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEventMap = EventMap<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type DeviceManagerEventListenerMap = EventListenerMap<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\nexport type DeviceManagerEvent = Event<typeof Device, DeviceManagerEventType, DeviceManagerEventMessages>;\nexport type BoundDeviceManagerEventListeners = BoundEventListeners<\n typeof Device,\n DeviceManagerEventType,\n DeviceManagerEventMessages\n>;\n\nclass DeviceManager {\n static readonly shared = new DeviceManager();\n\n constructor() {\n if (DeviceManager.shared && this != DeviceManager.shared) {\n throw Error(\"DeviceManager is a singleton - use DeviceManager.shared\");\n }\n\n if (this.CanUseLocalStorage) {\n this.UseLocalStorage = true;\n }\n }\n\n // DEVICE LISTENERS\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n getType: this.#onDeviceType.bind(this),\n isConnected: this.#OnDeviceIsConnected.bind(this),\n };\n /** @private */\n onDevice(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n }\n\n #onDeviceType(event: DeviceEventMap[\"getType\"]) {\n if (this.#UseLocalStorage) {\n this.#UpdateLocalStorageConfigurationForDevice(event.target);\n }\n }\n\n // CONNECTION STATUS\n /** @private */\n OnDeviceConnectionStatusUpdated(device: Device, connectionStatus: ConnectionStatus) {\n if (connectionStatus == \"notConnected\" && !device.canReconnect && this.#AvailableDevices.includes(device)) {\n const deviceIndex = this.#AvailableDevices.indexOf(device);\n this.AvailableDevices.splice(deviceIndex, 1);\n this.#DispatchAvailableDevices();\n }\n }\n\n // CONNECTED DEVICES\n\n #ConnectedDevices: Device[] = [];\n get ConnectedDevices() {\n return this.#ConnectedDevices;\n }\n\n #UseLocalStorage = false;\n get UseLocalStorage() {\n return this.#UseLocalStorage;\n }\n set UseLocalStorage(newUseLocalStorage) {\n this.#AssertLocalStorage();\n _console.assertTypeWithError(newUseLocalStorage, \"boolean\");\n this.#UseLocalStorage = newUseLocalStorage;\n if (this.#UseLocalStorage && !this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n }\n\n #DefaultLocalStorageConfiguration: LocalStorageConfiguration = {\n devices: [],\n };\n #LocalStorageConfiguration?: LocalStorageConfiguration;\n\n get CanUseLocalStorage() {\n return isInBrowser && window.localStorage;\n }\n\n #AssertLocalStorage() {\n _console.assertWithError(isInBrowser, \"localStorage is only available in the browser\");\n _console.assertWithError(window.localStorage, \"localStorage not found\");\n }\n #LocalStorageKey = \"BS.Device\";\n #SaveToLocalStorage() {\n this.#AssertLocalStorage();\n localStorage.setItem(this.#LocalStorageKey, JSON.stringify(this.#LocalStorageConfiguration));\n }\n async #LoadFromLocalStorage() {\n this.#AssertLocalStorage();\n let localStorageString = localStorage.getItem(this.#LocalStorageKey);\n if (typeof localStorageString != \"string\") {\n _console.log(\"no info found in localStorage\");\n this.#LocalStorageConfiguration = Object.assign({}, this.#DefaultLocalStorageConfiguration);\n this.#SaveToLocalStorage();\n return;\n }\n try {\n const configuration = JSON.parse(localStorageString);\n _console.log({ configuration });\n this.#LocalStorageConfiguration = configuration;\n if (this.CanGetDevices) {\n await this.GetDevices(); // redundant?\n }\n } catch (error) {\n _console.error(error);\n }\n }\n\n #UpdateLocalStorageConfigurationForDevice(device: Device) {\n if (device.connectionType != \"webBluetooth\") {\n _console.log(\"localStorage is only for webBluetooth devices\");\n return;\n }\n this.#AssertLocalStorage();\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex((deviceInformation) => {\n return deviceInformation.bluetoothId == device.bluetoothId;\n });\n if (deviceInformationIndex == -1) {\n return;\n }\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex].type = device.type;\n this.#SaveToLocalStorage();\n }\n\n // AVAILABLE DEVICES\n #AvailableDevices: Device[] = [];\n get AvailableDevices() {\n return this.#AvailableDevices;\n }\n\n get CanGetDevices() {\n return isInBrowser && navigator.bluetooth?.getDevices;\n }\n /**\n * retrieves devices already connected via web bluetooth in other tabs/windows\n *\n * _only available on web-bluetooth enabled browsers_\n */\n async GetDevices(): Promise<Device[] | undefined> {\n if (!isInBrowser) {\n _console.warn(\"GetDevices is only available in the browser\");\n return;\n }\n\n if (!navigator.bluetooth) {\n _console.warn(\"bluetooth is not available in this browser\");\n return;\n }\n\n if (isInBluefy) {\n _console.warn(\"bluefy lists too many devices...\");\n return;\n }\n\n if (!navigator.bluetooth.getDevices) {\n _console.warn(\"bluetooth.getDevices() is not available in this browser\");\n return;\n }\n\n if (!this.CanGetDevices) {\n _console.log(\"CanGetDevices is false\");\n return;\n }\n\n if (!this.#LocalStorageConfiguration) {\n this.#LoadFromLocalStorage();\n }\n\n const configuration = this.#LocalStorageConfiguration!;\n if (!configuration.devices || configuration.devices.length == 0) {\n _console.log(\"no devices found in configuration\");\n return;\n }\n\n const bluetoothDevices = await navigator.bluetooth.getDevices();\n\n _console.log({ bluetoothDevices });\n\n bluetoothDevices.forEach((bluetoothDevice) => {\n if (!bluetoothDevice.gatt) {\n return;\n }\n let deviceInformation = configuration.devices.find(\n (deviceInformation) => bluetoothDevice.id == deviceInformation.bluetoothId\n );\n if (!deviceInformation) {\n return;\n }\n\n let existingConnectedDevice = this.ConnectedDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n\n const existingAvailableDevice = this.AvailableDevices.filter(\n (device) => device.connectionType == \"webBluetooth\"\n ).find((device) => device.bluetoothId == bluetoothDevice.id);\n if (existingAvailableDevice) {\n if (\n existingConnectedDevice &&\n existingConnectedDevice?.bluetoothId == existingAvailableDevice.bluetoothId &&\n existingConnectedDevice != existingAvailableDevice\n ) {\n this.AvailableDevices[this.#AvailableDevices.indexOf(existingAvailableDevice)] = existingConnectedDevice;\n }\n return;\n }\n\n if (existingConnectedDevice) {\n this.AvailableDevices.push(existingConnectedDevice);\n return;\n }\n\n const device = new Device();\n const connectionManager = new WebBluetoothConnectionManager();\n connectionManager.device = bluetoothDevice;\n if (bluetoothDevice.name) {\n device._informationManager.updateName(bluetoothDevice.name);\n }\n device._informationManager.updateType(deviceInformation.type);\n device.connectionManager = connectionManager;\n this.AvailableDevices.push(device);\n });\n this.#DispatchAvailableDevices();\n return this.AvailableDevices;\n }\n\n // STATIC EVENTLISTENERS\n\n #EventDispatcher: DeviceManagerEventDispatcher = new EventDispatcher(this as DeviceManager, DeviceManagerEventTypes);\n\n get AddEventListener() {\n return this.#EventDispatcher.addEventListener;\n }\n get #DispatchEvent() {\n return this.#EventDispatcher.dispatchEvent;\n }\n get RemoveEventListener() {\n return this.#EventDispatcher.removeEventListener;\n }\n get RemoveEventListeners() {\n return this.#EventDispatcher.removeEventListeners;\n }\n get RemoveAllEventListeners() {\n return this.#EventDispatcher.removeAllEventListeners;\n }\n\n #OnDeviceIsConnected(event: DeviceEventMap[\"isConnected\"]) {\n const { target: device } = event;\n if (device.isConnected) {\n if (!this.#ConnectedDevices.includes(device)) {\n _console.log(\"adding device\", device);\n this.#ConnectedDevices.push(device);\n if (this.UseLocalStorage && device.connectionType == \"webBluetooth\") {\n const deviceInformation: LocalStorageDeviceInformation = {\n type: device.type,\n bluetoothId: device.bluetoothId!,\n };\n const deviceInformationIndex = this.#LocalStorageConfiguration!.devices.findIndex(\n (_deviceInformation) => _deviceInformation.bluetoothId == deviceInformation.bluetoothId\n );\n if (deviceInformationIndex == -1) {\n this.#LocalStorageConfiguration!.devices.push(deviceInformation);\n } else {\n this.#LocalStorageConfiguration!.devices[deviceInformationIndex] = deviceInformation;\n }\n this.#SaveToLocalStorage();\n }\n this.#DispatchEvent(\"deviceConnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already included\");\n }\n } else {\n if (this.#ConnectedDevices.includes(device)) {\n _console.log(\"removing device\", device);\n this.#ConnectedDevices.splice(this.#ConnectedDevices.indexOf(device), 1);\n this.#DispatchEvent(\"deviceDisconnected\", { device });\n this.#DispatchEvent(\"deviceIsConnected\", { device });\n this.#DispatchConnectedDevices();\n } else {\n _console.log(\"device already not included\");\n }\n }\n if (this.CanGetDevices) {\n this.GetDevices();\n }\n if (device.isConnected && !this.AvailableDevices.includes(device)) {\n const existingAvailableDevice = this.AvailableDevices.find(\n (_device) => _device.bluetoothId == device.bluetoothId\n );\n _console.log({ existingAvailableDevice });\n if (existingAvailableDevice) {\n this.AvailableDevices[this.AvailableDevices.indexOf(existingAvailableDevice)] = device;\n } else {\n this.AvailableDevices.push(device);\n }\n this.#DispatchAvailableDevices();\n }\n }\n\n #DispatchAvailableDevices() {\n _console.log({ AvailableDevices: this.AvailableDevices });\n this.#DispatchEvent(\"availableDevices\", { availableDevices: this.AvailableDevices });\n }\n #DispatchConnectedDevices() {\n _console.log({ ConnectedDevices: this.ConnectedDevices });\n this.#DispatchEvent(\"connectedDevices\", { connectedDevices: this.ConnectedDevices });\n }\n}\n\nexport default DeviceManager.shared;\n","import { createConsole } from \"./utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"./utils/EventDispatcher.ts\";\nimport BaseConnectionManager, {\n TxMessage,\n TxRxMessageType,\n ConnectionStatus,\n ConnectionMessageType,\n MetaConnectionMessageTypes,\n BatteryLevelMessageTypes,\n ConnectionEventTypes,\n ConnectionStatusEventMessages,\n} from \"./connection/BaseConnectionManager.ts\";\nimport { isInBrowser, isInNode } from \"./utils/environment.ts\";\nimport WebBluetoothConnectionManager from \"./connection/bluetooth/WebBluetoothConnectionManager.ts\";\nimport SensorConfigurationManager, {\n SendSensorConfigurationMessageCallback,\n SensorConfiguration,\n SensorConfigurationEventDispatcher,\n SensorConfigurationEventMessages,\n SensorConfigurationEventTypes,\n SensorConfigurationMessageType,\n SensorConfigurationMessageTypes,\n} from \"./sensor/SensorConfigurationManager.ts\";\nimport SensorDataManager, {\n SensorDataEventMessages,\n SensorDataEventTypes,\n SensorDataMessageType,\n SensorDataMessageTypes,\n SensorType,\n ContinuousSensorTypes,\n SensorDataEventDispatcher,\n} from \"./sensor/SensorDataManager.ts\";\nimport VibrationManager, {\n SendVibrationMessageCallback,\n VibrationConfiguration,\n} from \"./vibration/VibrationManager.ts\";\nimport FileTransferManager, {\n FileTransferEventTypes,\n FileTransferEventMessages,\n FileTransferEventDispatcher,\n SendFileTransferMessageCallback,\n FileTransferMessageTypes,\n FileTransferMessageType,\n FileType,\n} from \"./FileTransferManager.ts\";\nimport TfliteManager, {\n TfliteEventTypes,\n TfliteEventMessages,\n TfliteEventDispatcher,\n SendTfliteMessageCallback,\n TfliteMessageTypes,\n TfliteMessageType,\n TfliteSensorTypes,\n} from \"./TfliteManager.ts\";\nimport FirmwareManager, {\n FirmwareEventDispatcher,\n FirmwareEventMessages,\n FirmwareEventTypes,\n FirmwareMessageType,\n FirmwareMessageTypes,\n} from \"./FirmwareManager.ts\";\nimport DeviceInformationManager, {\n DeviceInformationEventDispatcher,\n DeviceInformationEventTypes,\n DeviceInformationMessageType,\n DeviceInformationMessageTypes,\n DeviceInformationEventMessages,\n} from \"./DeviceInformationManager.ts\";\nimport InformationManager, {\n DeviceType,\n InformationEventDispatcher,\n InformationEventTypes,\n InformationMessageType,\n InformationMessageTypes,\n InformationEventMessages,\n SendInformationMessageCallback,\n} from \"./InformationManager.ts\";\nimport { FileLike } from \"./utils/ArrayBufferUtils.ts\";\nimport DeviceManager from \"./DeviceManager.ts\";\n\nconst _console = createConsole(\"Device\", { log: true });\n\nexport const DeviceEventTypes = [\n \"connectionMessage\",\n ...ConnectionEventTypes,\n ...MetaConnectionMessageTypes,\n ...BatteryLevelMessageTypes,\n ...InformationEventTypes,\n ...DeviceInformationEventTypes,\n ...SensorConfigurationEventTypes,\n ...SensorDataEventTypes,\n ...FileTransferEventTypes,\n ...TfliteEventTypes,\n ...FirmwareEventTypes,\n] as const;\nexport type DeviceEventType = (typeof DeviceEventTypes)[number];\n\nexport interface DeviceEventMessages\n extends ConnectionStatusEventMessages,\n DeviceInformationEventMessages,\n InformationEventMessages,\n SensorDataEventMessages,\n SensorConfigurationEventMessages,\n TfliteEventMessages,\n FileTransferEventMessages,\n FirmwareEventMessages {\n batteryLevel: { batteryLevel: number };\n connectionMessage: { messageType: ConnectionMessageType; dataView: DataView };\n}\n\nexport type SendMessageCallback<MessageType extends string> = (\n messages?: { type: MessageType; data?: ArrayBuffer }[],\n sendImmediately?: boolean\n) => Promise<void>;\n\nexport type SendSmpMessageCallback = (data: ArrayBuffer) => Promise<void>;\n\nexport type DeviceEventDispatcher = EventDispatcher<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEvent = Event<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventMap = EventMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type DeviceEventListenerMap = EventListenerMap<Device, DeviceEventType, DeviceEventMessages>;\nexport type BoundDeviceEventListeners = BoundEventListeners<Device, DeviceEventType, DeviceEventMessages>;\n\nexport const RequiredInformationConnectionMessages: TxRxMessageType[] = [\n \"isCharging\",\n \"getBatteryCurrent\",\n \"getId\",\n \"getMtu\",\n\n \"getName\",\n \"getType\",\n \"getCurrentTime\",\n \"getSensorConfiguration\",\n \"getSensorScalars\",\n \"getPressurePositions\",\n\n \"maxFileLength\",\n \"getFileLength\",\n \"getFileChecksum\",\n \"getFileType\",\n \"fileTransferStatus\",\n\n \"getTfliteName\",\n \"getTfliteTask\",\n \"getTfliteSampleRate\",\n \"getTfliteSensorTypes\",\n \"tfliteIsReady\",\n \"getTfliteCaptureDelay\",\n \"getTfliteThreshold\",\n \"getTfliteInferencingEnabled\",\n];\n\nclass Device {\n get bluetoothId() {\n return this.#connectionManager?.bluetoothId;\n }\n\n constructor() {\n this.#deviceInformationManager.eventDispatcher = this.#eventDispatcher as DeviceInformationEventDispatcher;\n\n this._informationManager.sendMessage = this.sendTxMessages as SendInformationMessageCallback;\n this._informationManager.eventDispatcher = this.#eventDispatcher as InformationEventDispatcher;\n\n this.#sensorConfigurationManager.sendMessage = this.sendTxMessages as SendSensorConfigurationMessageCallback;\n this.#sensorConfigurationManager.eventDispatcher = this.#eventDispatcher as SensorConfigurationEventDispatcher;\n\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as SensorDataEventDispatcher;\n\n this.#vibrationManager.sendMessage = this.sendTxMessages as SendVibrationMessageCallback;\n\n this.#tfliteManager.sendMessage = this.sendTxMessages as SendTfliteMessageCallback;\n this.#tfliteManager.eventDispatcher = this.#eventDispatcher as TfliteEventDispatcher;\n\n this.#fileTransferManager.sendMessage = this.sendTxMessages as SendFileTransferMessageCallback;\n this.#fileTransferManager.eventDispatcher = this.#eventDispatcher as FileTransferEventDispatcher;\n\n this.#firmwareManager.sendMessage = this.sendSmpMessage as SendSmpMessageCallback;\n this.#firmwareManager.eventDispatcher = this.#eventDispatcher as FirmwareEventDispatcher;\n\n this.addEventListener(\"getMtu\", () => {\n this.#firmwareManager.mtu = this.mtu;\n this.#fileTransferManager.mtu = this.mtu;\n this.connectionManager!.mtu = this.mtu;\n });\n DeviceManager.onDevice(this);\n if (isInBrowser) {\n window.addEventListener(\"beforeunload\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n if (isInNode) {\n /** can add more node leave handlers https://gist.github.com/hyrious/30a878f6e6a057f09db87638567cb11a */\n process.on(\"exit\", () => {\n if (this.isConnected && this.clearSensorConfigurationOnLeave) {\n this.clearSensorConfiguration();\n }\n });\n }\n }\n\n static #DefaultConnectionManager(): BaseConnectionManager {\n return new WebBluetoothConnectionManager();\n }\n\n #eventDispatcher: DeviceEventDispatcher = new EventDispatcher(this as Device, DeviceEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // CONNECTION MANAGER\n\n #connectionManager?: BaseConnectionManager;\n get connectionManager() {\n return this.#connectionManager;\n }\n set connectionManager(newConnectionManager) {\n if (this.connectionManager == newConnectionManager) {\n _console.log(\"same connectionManager is already assigned\");\n return;\n }\n\n if (this.connectionManager) {\n this.connectionManager.onStatusUpdated = undefined;\n this.connectionManager.onMessageReceived = undefined;\n this.connectionManager.onMessagesReceived = undefined;\n }\n if (newConnectionManager) {\n newConnectionManager.onStatusUpdated = this.#onConnectionStatusUpdated.bind(this);\n newConnectionManager.onMessageReceived = this.#onConnectionMessageReceived.bind(this);\n newConnectionManager.onMessagesReceived = this.#onConnectionMessagesReceived.bind(this);\n }\n\n this.#connectionManager = newConnectionManager;\n _console.log(\"assigned new connectionManager\", this.#connectionManager);\n }\n async #sendTxMessages(messages?: TxMessage[], sendImmediately?: boolean) {\n await this.#connectionManager?.sendTxMessages(messages, sendImmediately);\n }\n private sendTxMessages = this.#sendTxMessages.bind(this);\n\n async connect() {\n if (!this.connectionManager) {\n this.connectionManager = Device.#DefaultConnectionManager();\n }\n this.#clear();\n return this.connectionManager.connect();\n }\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n /** @throws {Error} if not connected */\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n get #hasRequiredInformation() {\n return RequiredInformationConnectionMessages.every((messageType) => {\n return this.latestConnectionMessage.has(messageType);\n });\n }\n #requestRequiredInformation() {\n const messages: TxMessage[] = RequiredInformationConnectionMessages.map((messageType) => ({\n type: messageType,\n }));\n this.#sendTxMessages(messages);\n }\n\n get canReconnect() {\n return this.connectionManager?.canReconnect;\n }\n #assertCanReconnect() {\n _console.assertWithError(this.canReconnect, \"cannot reconnect to device\");\n }\n async reconnect() {\n this.#assertCanReconnect();\n this.#clear();\n return this.connectionManager?.reconnect();\n }\n\n static async Connect() {\n const device = new Device();\n await device.connect();\n return device;\n }\n\n static #ReconnectOnDisconnection = false;\n static get ReconnectOnDisconnection() {\n return this.#ReconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#ReconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n #reconnectOnDisconnection = Device.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this.#reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this.#reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n #reconnectIntervalId?: NodeJS.Timeout | number;\n\n get connectionType() {\n return this.connectionManager?.type;\n }\n async disconnect() {\n this.#assertIsConnected();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.addEventListener(\n \"isConnected\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n\n return this.connectionManager!.disconnect();\n }\n\n toggleConnection() {\n if (this.isConnected) {\n this.disconnect();\n } else if (this.canReconnect) {\n this.reconnect();\n } else {\n this.connect();\n }\n }\n\n get connectionStatus(): ConnectionStatus {\n switch (this.#connectionManager?.status) {\n case \"connected\":\n return this.isConnected ? \"connected\" : \"connecting\";\n case \"notConnected\":\n case \"connecting\":\n case \"disconnecting\":\n return this.#connectionManager.status;\n default:\n return \"notConnected\";\n }\n }\n get isConnectionBusy() {\n return this.connectionStatus == \"connecting\" || this.connectionStatus == \"disconnecting\";\n }\n\n #onConnectionStatusUpdated(connectionStatus: ConnectionStatus) {\n _console.log({ connectionStatus });\n\n if (connectionStatus == \"notConnected\") {\n //this.#clear();\n\n if (this.canReconnect && this.reconnectOnDisconnection) {\n _console.log(\"starting reconnect interval...\");\n this.#reconnectIntervalId = setInterval(() => {\n _console.log(\"attempting reconnect...\");\n this.reconnect();\n }, 1000);\n }\n } else {\n if (this.#reconnectIntervalId != undefined) {\n _console.log(\"clearing reconnect interval\");\n clearInterval(this.#reconnectIntervalId);\n this.#reconnectIntervalId = undefined;\n }\n }\n\n this.#checkConnection();\n\n if (connectionStatus == \"connected\" && !this.#isConnected) {\n this.#requestRequiredInformation();\n }\n\n DeviceManager.OnDeviceConnectionStatusUpdated(this, connectionStatus);\n }\n\n #dispatchConnectionEvents(includeIsConnected: boolean = false) {\n this.#dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.#dispatchEvent(this.connectionStatus, {});\n if (includeIsConnected) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n }\n #checkConnection() {\n this.#isConnected =\n Boolean(this.connectionManager?.isConnected) &&\n this.#hasRequiredInformation &&\n this._informationManager.isCurrentTimeSet;\n\n switch (this.connectionStatus) {\n case \"connected\":\n if (this.#isConnected) {\n this.#dispatchConnectionEvents(true);\n }\n break;\n case \"notConnected\":\n this.#dispatchConnectionEvents(true);\n break;\n default:\n this.#dispatchConnectionEvents(false);\n break;\n }\n }\n\n #clear() {\n this.connectionManager?.clear();\n this.latestConnectionMessage.clear();\n this._informationManager.clear();\n this.#deviceInformationManager.clear();\n }\n\n #onConnectionMessageReceived(messageType: ConnectionMessageType, dataView: DataView) {\n _console.log({ messageType, dataView });\n switch (messageType) {\n case \"batteryLevel\":\n const batteryLevel = dataView.getUint8(0);\n _console.log(\"received battery level\", { batteryLevel });\n this.#updateBatteryLevel(batteryLevel);\n break;\n\n default:\n if (FileTransferMessageTypes.includes(messageType as FileTransferMessageType)) {\n this.#fileTransferManager.parseMessage(messageType as FileTransferMessageType, dataView);\n } else if (TfliteMessageTypes.includes(messageType as TfliteMessageType)) {\n this.#tfliteManager.parseMessage(messageType as TfliteMessageType, dataView);\n } else if (SensorDataMessageTypes.includes(messageType as SensorDataMessageType)) {\n this.#sensorDataManager.parseMessage(messageType as SensorDataMessageType, dataView);\n } else if (FirmwareMessageTypes.includes(messageType as FirmwareMessageType)) {\n this.#firmwareManager.parseMessage(messageType as FirmwareMessageType, dataView);\n } else if (DeviceInformationMessageTypes.includes(messageType as DeviceInformationMessageType)) {\n this.#deviceInformationManager.parseMessage(messageType as DeviceInformationMessageType, dataView);\n } else if (InformationMessageTypes.includes(messageType as InformationMessageType)) {\n this._informationManager.parseMessage(messageType as InformationMessageType, dataView);\n } else if (SensorConfigurationMessageTypes.includes(messageType as SensorConfigurationMessageType)) {\n this.#sensorConfigurationManager.parseMessage(messageType as SensorConfigurationMessageType, dataView);\n } else {\n throw Error(`uncaught messageType ${messageType}`);\n }\n }\n\n this.latestConnectionMessage.set(messageType, dataView);\n this.#dispatchEvent(\"connectionMessage\", { messageType, dataView });\n }\n #onConnectionMessagesReceived() {\n if (!this.isConnected && this.#hasRequiredInformation) {\n this.#checkConnection();\n }\n if (this.connectionStatus == \"notConnected\") {\n return;\n }\n this.#sendTxMessages();\n }\n\n latestConnectionMessage: Map<ConnectionMessageType, DataView> = new Map();\n\n // DEVICE INFORMATION\n #deviceInformationManager = new DeviceInformationManager();\n get deviceInformation() {\n return this.#deviceInformationManager.information;\n }\n\n // BATTERY LEVEL\n #batteryLevel = 0;\n get batteryLevel() {\n return this.#batteryLevel;\n }\n #updateBatteryLevel(updatedBatteryLevel: number) {\n _console.assertTypeWithError(updatedBatteryLevel, \"number\");\n if (this.#batteryLevel == updatedBatteryLevel) {\n _console.log(`duplicate batteryLevel assignment ${updatedBatteryLevel}`);\n return;\n }\n this.#batteryLevel = updatedBatteryLevel;\n _console.log({ updatedBatteryLevel: this.#batteryLevel });\n this.#dispatchEvent(\"batteryLevel\", { batteryLevel: this.#batteryLevel });\n }\n\n // INFORMATION\n /** @private */\n _informationManager = new InformationManager();\n\n get id() {\n return this._informationManager.id;\n }\n\n get isCharging() {\n return this._informationManager.isCharging;\n }\n get batteryCurrent() {\n return this._informationManager.batteryCurrent;\n }\n get getBatteryCurrent() {\n return this._informationManager.getBatteryCurrent;\n }\n\n get name() {\n return this._informationManager.name;\n }\n get setName() {\n return this._informationManager.setName;\n }\n\n get type() {\n return this._informationManager.type;\n }\n get setType() {\n return this._informationManager.setType;\n }\n\n get isInsole() {\n return this._informationManager.isInsole;\n }\n get insoleSide() {\n return this._informationManager.insoleSide;\n }\n\n get mtu() {\n return this._informationManager.mtu;\n }\n\n // SENSOR TYPES\n get sensorTypes() {\n return Object.keys(this.sensorConfiguration) as SensorType[];\n }\n get continuousSensorTypes() {\n return ContinuousSensorTypes.filter((sensorType) => this.sensorTypes.includes(sensorType));\n }\n\n // SENSOR CONFIGURATION\n\n #sensorConfigurationManager = new SensorConfigurationManager();\n\n get sensorConfiguration() {\n return this.#sensorConfigurationManager.configuration;\n }\n\n async setSensorConfiguration(newSensorConfiguration: SensorConfiguration, clearRest?: boolean) {\n await this.#sensorConfigurationManager.setConfiguration(newSensorConfiguration, clearRest);\n }\n\n async clearSensorConfiguration() {\n return this.#sensorConfigurationManager.clearSensorConfiguration();\n }\n\n static #ClearSensorConfigurationOnLeave = true;\n static get ClearSensorConfigurationOnLeave() {\n return this.#ClearSensorConfigurationOnLeave;\n }\n static set ClearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#ClearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n #clearSensorConfigurationOnLeave = Device.ClearSensorConfigurationOnLeave;\n get clearSensorConfigurationOnLeave() {\n return this.#clearSensorConfigurationOnLeave;\n }\n set clearSensorConfigurationOnLeave(newClearSensorConfigurationOnLeave) {\n _console.assertTypeWithError(newClearSensorConfigurationOnLeave, \"boolean\");\n this.#clearSensorConfigurationOnLeave = newClearSensorConfigurationOnLeave;\n }\n\n // PRESSURE\n get numberOfPressureSensors() {\n return this.#sensorDataManager.pressureSensorDataManager.numberOfSensors;\n }\n\n // SENSOR DATA\n #sensorDataManager = new SensorDataManager();\n resetPressureRange() {\n this.#sensorDataManager.pressureSensorDataManager.resetRange();\n }\n\n // VIBRATION\n #vibrationManager = new VibrationManager();\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n this.#vibrationManager.triggerVibration(vibrationConfigurations, sendImmediately);\n }\n\n // FILE TRANSFER\n #fileTransferManager = new FileTransferManager();\n\n get maxFileLength() {\n return this.#fileTransferManager.maxLength;\n }\n\n async sendFile(fileType: FileType, file: FileLike) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.send(fileType, file);\n await promise;\n }\n async receiveFile(fileType: FileType) {\n const promise = this.waitForEvent(\"fileTransferComplete\");\n this.#fileTransferManager.receive(fileType);\n await promise;\n }\n\n get fileTransferStatus() {\n return this.#fileTransferManager.status;\n }\n\n cancelFileTransfer() {\n this.#fileTransferManager.cancel();\n }\n\n // TFLITE\n #tfliteManager = new TfliteManager();\n\n get tfliteName() {\n return this.#tfliteManager.name;\n }\n get setTfliteName() {\n return this.#tfliteManager.setName;\n }\n\n // TFLITE MODEL CONFIG\n get tfliteTask() {\n return this.#tfliteManager.task;\n }\n get setTfliteTask() {\n return this.#tfliteManager.setTask;\n }\n get tfliteSampleRate() {\n return this.#tfliteManager.sampleRate;\n }\n get setTfliteSampleRate() {\n return this.#tfliteManager.setSampleRate;\n }\n get tfliteSensorTypes() {\n return this.#tfliteManager.sensorTypes;\n }\n get allowedTfliteSensorTypes() {\n return this.sensorTypes.filter((sensorType) => TfliteSensorTypes.includes(sensorType));\n }\n get setTfliteSensorTypes() {\n return this.#tfliteManager.setSensorTypes;\n }\n get tfliteIsReady() {\n return this.#tfliteManager.isReady;\n }\n\n // TFLITE INFERENCING\n\n get tfliteInferencingEnabled() {\n return this.#tfliteManager.inferencingEnabled;\n }\n get setTfliteInferencingEnabled() {\n return this.#tfliteManager.setInferencingEnabled;\n }\n async enableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(true);\n }\n async disableTfliteInferencing() {\n return this.setTfliteInferencingEnabled(false);\n }\n get toggleTfliteInferencing() {\n return this.#tfliteManager.toggleInferencingEnabled;\n }\n\n // TFLITE INFERENCE CONFIG\n\n get tfliteCaptureDelay() {\n return this.#tfliteManager.captureDelay;\n }\n get setTfliteCaptureDelay() {\n return this.#tfliteManager.setCaptureDelay;\n }\n get tfliteThreshold() {\n return this.#tfliteManager.threshold;\n }\n get setTfliteThreshold() {\n return this.#tfliteManager.setThreshold;\n }\n\n // FIRMWARE MANAGER\n\n #firmwareManager = new FirmwareManager();\n\n #sendSmpMessage(data: ArrayBuffer) {\n return this.#connectionManager!.sendSmpMessage(data);\n }\n private sendSmpMessage = this.#sendSmpMessage.bind(this);\n\n get uploadFirmware() {\n return this.#firmwareManager.uploadFirmware;\n }\n async reset() {\n await this.#firmwareManager.reset();\n return this.#connectionManager!.disconnect();\n }\n get firmwareStatus() {\n return this.#firmwareManager.status;\n }\n get getFirmwareImages() {\n return this.#firmwareManager.getImages;\n }\n get firmwareImages() {\n return this.#firmwareManager.images;\n }\n get eraseFirmwareImage() {\n return this.#firmwareManager.eraseImage;\n }\n get confirmFirmwareImage() {\n return this.#firmwareManager.confirmImage;\n }\n get testFirmwareImage() {\n return this.#firmwareManager.testImage;\n }\n\n // SERVER SIDE\n #isServerSide = false;\n get isServerSide() {\n return this.#isServerSide;\n }\n set isServerSide(newIsServerSide) {\n if (this.#isServerSide == newIsServerSide) {\n _console.log(\"redundant isServerSide assignment\");\n return;\n }\n _console.log({ newIsServerSide });\n this.#isServerSide = newIsServerSide;\n\n this.#fileTransferManager.isServerSide = this.isServerSide;\n }\n}\n\nexport default Device;\n","import { createConsole } from \"../utils/Console.ts\";\nimport CenterOfPressureHelper from \"../utils/CenterOfPressureHelper.ts\";\nimport { PressureData } from \"../sensor/PressureSensorDataManager.ts\";\nimport { CenterOfPressure } from \"../utils/CenterOfPressureHelper.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { DeviceEventMap } from \"../Device.ts\";\n\nconst _console = createConsole(\"DevicePairPressureSensorDataManager\", { log: true });\n\nexport type DevicePairRawPressureData = { [insoleSide in InsoleSide]: PressureData };\n\nexport interface DevicePairPressureData {\n rawSum: number;\n normalizedSum: number;\n center?: CenterOfPressure;\n normalizedCenter?: CenterOfPressure;\n}\n\nexport interface DevicePairPressureDataEventMessage {\n pressure: DevicePairPressureData;\n}\n\nexport interface DevicePairPressureDataEventMessages {\n pressure: DevicePairPressureDataEventMessage;\n}\n\nclass DevicePairPressureSensorDataManager {\n #rawPressure: Partial<DevicePairRawPressureData> = {};\n\n #centerOfPressureHelper = new CenterOfPressureHelper();\n\n resetPressureRange() {\n this.#centerOfPressureHelper.reset();\n }\n\n onDevicePressureData(event: DeviceEventMap[\"pressure\"]) {\n const { pressure } = event.message;\n const insoleSide = event.target.insoleSide;\n _console.log({ pressure, insoleSide });\n this.#rawPressure[insoleSide] = pressure;\n if (this.#hasAllPressureData) {\n return this.#updatePressureData();\n } else {\n _console.log(\"doesn't have all pressure data yet...\");\n }\n }\n\n get #hasAllPressureData() {\n return InsoleSides.every((side) => side in this.#rawPressure);\n }\n\n #updatePressureData() {\n const pressure: DevicePairPressureData = { rawSum: 0, normalizedSum: 0 };\n\n InsoleSides.forEach((side) => {\n pressure.rawSum += this.#rawPressure[side]!.scaledSum;\n pressure.normalizedSum += this.#rawPressure[side]!.normalizedSum;\n });\n\n if (pressure.normalizedSum > 0.001) {\n pressure.center = { x: 0, y: 0 };\n InsoleSides.forEach((side) => {\n const sidePressure = this.#rawPressure[side]!;\n const normalizedPressureSumWeight = sidePressure.normalizedSum / pressure.normalizedSum;\n if (normalizedPressureSumWeight > 0) {\n if (sidePressure.normalizedCenter?.y != undefined) {\n pressure.center!.y += sidePressure.normalizedCenter.y * normalizedPressureSumWeight;\n }\n if (side == \"right\") {\n pressure.center!.x = normalizedPressureSumWeight;\n }\n }\n });\n\n pressure.normalizedCenter = this.#centerOfPressureHelper.updateAndGetNormalization(pressure.center);\n }\n\n _console.log({ devicePairPressure: pressure });\n\n return pressure;\n }\n}\n\nexport default DevicePairPressureSensorDataManager;\n","import DevicePairPressureSensorDataManager, {\n DevicePairPressureDataEventMessages,\n} from \"./DevicePairPressureSensorDataManager.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { InsoleSide } from \"../InformationManager.ts\";\nimport { SensorType } from \"../sensor/SensorDataManager.ts\";\nimport { DeviceEventMap, SpecificDeviceEvent } from \"../Device.ts\";\nimport EventDispatcher from \"../utils/EventDispatcher.ts\";\nimport DevicePair from \"./DevicePair.ts\";\nimport { AddKeysAsPropertyToInterface, ExtendInterfaceValues, ValueOf } from \"../utils/TypeScriptUtils.ts\";\n\nconst _console = createConsole(\"DevicePairSensorDataManager\", { log: true });\n\nexport const DevicePairSensorTypes = [\"pressure\", \"sensorData\"] as const;\nexport type DevicePairSensorType = (typeof DevicePairSensorTypes)[number];\n\nexport const DevicePairSensorDataEventTypes = DevicePairSensorTypes;\nexport type DevicePairSensorDataEventType = (typeof DevicePairSensorDataEventTypes)[number];\n\nexport type DevicePairSensorDataTimestamps = { [insoleSide in InsoleSide]: number };\n\ninterface BaseDevicePairSensorDataEventMessage {\n timestamps: DevicePairSensorDataTimestamps;\n}\n\ntype BaseDevicePairSensorDataEventMessages = DevicePairPressureDataEventMessages;\ntype _DevicePairSensorDataEventMessages = ExtendInterfaceValues<\n AddKeysAsPropertyToInterface<BaseDevicePairSensorDataEventMessages, \"sensorType\">,\n BaseDevicePairSensorDataEventMessage\n>;\n\nexport type DevicePairSensorDataEventMessage = ValueOf<_DevicePairSensorDataEventMessages>;\ninterface AnyDevicePairSensorDataEventMessages {\n sensorData: DevicePairSensorDataEventMessage;\n}\nexport type DevicePairSensorDataEventMessages = _DevicePairSensorDataEventMessages &\n AnyDevicePairSensorDataEventMessages;\n\nexport type DevicePairSensorDataEventDispatcher = EventDispatcher<\n DevicePair,\n DevicePairSensorDataEventType,\n DevicePairSensorDataEventMessages\n>;\n\nclass DevicePairSensorDataManager {\n eventDispatcher!: DevicePairSensorDataEventDispatcher;\n get dispatchEvent() {\n return this.eventDispatcher.dispatchEvent;\n }\n\n #timestamps: { [sensorType in SensorType]?: Partial<DevicePairSensorDataTimestamps> } = {};\n\n pressureSensorDataManager = new DevicePairPressureSensorDataManager();\n resetPressureRange() {\n this.pressureSensorDataManager.resetPressureRange();\n }\n\n onDeviceSensorData(event: DeviceEventMap[\"sensorData\"]) {\n const { timestamp, sensorType } = event.message;\n\n _console.log({ sensorType, timestamp, event });\n\n if (!this.#timestamps[sensorType]) {\n this.#timestamps[sensorType] = {};\n }\n this.#timestamps[sensorType]![event.target.insoleSide] = timestamp;\n\n let value;\n switch (sensorType) {\n case \"pressure\":\n value = this.pressureSensorDataManager.onDevicePressureData(event as unknown as DeviceEventMap[\"pressure\"]);\n break;\n default:\n _console.log(`uncaught sensorType \"${sensorType}\"`);\n break;\n }\n\n if (value) {\n const timestamps = Object.assign({}, this.#timestamps[sensorType]) as DevicePairSensorDataTimestamps;\n // @ts-expect-error\n this.dispatchEvent(sensorType as DevicePairSensorDataEventType, { sensorType, timestamps, [sensorType]: value });\n // @ts-expect-error\n this.dispatchEvent(\"sensorData\", { sensorType, timestamps, [sensorType]: value });\n } else {\n _console.log(\"no value received\");\n }\n }\n}\n\nexport default DevicePairSensorDataManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport EventDispatcher, { BoundEventListeners, Event, EventListenerMap, EventMap } from \"../utils/EventDispatcher.ts\";\nimport { addEventListeners, removeEventListeners } from \"../utils/EventUtils.ts\";\nimport Device, {\n DeviceEvent,\n DeviceEventType,\n DeviceEventMessages,\n DeviceEventTypes,\n BoundDeviceEventListeners,\n DeviceEventMap,\n} from \"../Device.ts\";\nimport DevicePairSensorDataManager, { DevicePairSensorDataEventDispatcher } from \"./DevicePairSensorDataManager.ts\";\nimport { capitalizeFirstCharacter } from \"../utils/stringUtils.ts\";\nimport { InsoleSide, InsoleSides } from \"../InformationManager.ts\";\nimport { VibrationConfiguration } from \"../vibration/VibrationManager.ts\";\nimport { SensorConfiguration } from \"../sensor/SensorConfigurationManager.ts\";\nimport { DevicePairSensorDataEventMessages, DevicePairSensorDataEventTypes } from \"./DevicePairSensorDataManager.ts\";\nimport { AddPrefixToInterfaceKeys, ExtendInterfaceValues, KeyOf } from \"../utils/TypeScriptUtils.ts\";\nimport DeviceManager from \"../DeviceManager.ts\";\n\nconst _console = createConsole(\"DevicePair\", { log: true });\n\ninterface BaseDevicePairDeviceEventMessage {\n device: Device;\n side: InsoleSide;\n}\ntype DevicePairDeviceEventMessages = ExtendInterfaceValues<\n AddPrefixToInterfaceKeys<DeviceEventMessages, \"device\">,\n BaseDevicePairDeviceEventMessage\n>;\ntype DevicePairDeviceEventType = KeyOf<DevicePairDeviceEventMessages>;\nfunction getDevicePairDeviceEventType(deviceEventType: DeviceEventType) {\n return `device${capitalizeFirstCharacter(deviceEventType)}` as DevicePairDeviceEventType;\n}\nconst DevicePairDeviceEventTypes = DeviceEventTypes.map((eventType) =>\n getDevicePairDeviceEventType(eventType)\n) as DevicePairDeviceEventType[];\n\nexport const DevicePairConnectionEventTypes = [\"isConnected\"] as const;\nexport type DevicePairConnectionEventType = (typeof DevicePairConnectionEventTypes)[number];\n\nexport interface DevicePairConnectionEventMessages {\n isConnected: { isConnected: boolean };\n}\n\nexport const DevicePairEventTypes = [\n ...DevicePairConnectionEventTypes,\n ...DevicePairSensorDataEventTypes,\n ...DevicePairDeviceEventTypes,\n] as const;\nexport type DevicePairEventType = (typeof DevicePairEventTypes)[number];\n\nexport type DevicePairEventMessages = DevicePairConnectionEventMessages &\n DevicePairSensorDataEventMessages &\n DevicePairDeviceEventMessages;\n\nexport type DevicePairEventDispatcher = EventDispatcher<DevicePair, DevicePairEventType, DevicePairEventMessages>;\nexport type DevicePairEventMap = EventMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEventListenerMap = EventListenerMap<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type DevicePairEvent = Event<DevicePair, DeviceEventType, DevicePairEventMessages>;\nexport type BoundDevicePairEventListeners = BoundEventListeners<DevicePair, DeviceEventType, DevicePairEventMessages>;\n\nclass DevicePair {\n constructor() {\n this.#sensorDataManager.eventDispatcher = this.#eventDispatcher as DevicePairSensorDataEventDispatcher;\n }\n\n get sides() {\n return InsoleSides;\n }\n\n #eventDispatcher: DevicePairEventDispatcher = new EventDispatcher(this as DevicePair, DevicePairEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n get #dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n get removeEventListeners() {\n return this.#eventDispatcher.removeEventListeners;\n }\n get removeAllEventListeners() {\n return this.#eventDispatcher.removeAllEventListeners;\n }\n\n // SIDES\n #left?: Device;\n get left() {\n return this.#left;\n }\n\n #right?: Device;\n get right() {\n return this.#right;\n }\n\n get isConnected() {\n return InsoleSides.every((side) => this[side]?.isConnected);\n }\n get isPartiallyConnected() {\n return InsoleSides.some((side) => this[side]?.isConnected);\n }\n get isHalfConnected() {\n return this.isPartiallyConnected && !this.isConnected;\n }\n #assertIsConnected() {\n _console.assertWithError(this.isConnected, \"devicePair must be connected\");\n }\n\n assignInsole(device: Device) {\n if (!device.isInsole) {\n _console.warn(\"device is not an insole\");\n return;\n }\n const side = device.insoleSide;\n\n const currentDevice = this[side];\n\n if (device == currentDevice) {\n _console.log(\"device already assigned\");\n return;\n }\n\n if (currentDevice) {\n this.#removeDeviceEventListeners(currentDevice);\n }\n this.#addDeviceEventListeners(device);\n\n switch (side) {\n case \"left\":\n this.#left = device;\n break;\n case \"right\":\n this.#right = device;\n break;\n }\n\n _console.log(`assigned ${side} insole`, device);\n\n this.resetPressureRange();\n\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n this.#dispatchEvent(\"deviceIsConnected\", { device, isConnected: device.isConnected, side });\n\n return currentDevice;\n }\n\n #addDeviceEventListeners(device: Device) {\n addEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.addEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n #removeDeviceEventListeners(device: Device) {\n removeEventListeners(device, this.#boundDeviceEventListeners);\n DeviceEventTypes.forEach((deviceEventType) => {\n // @ts-expect-error\n device.removeEventListener(deviceEventType, this.#redispatchDeviceEvent.bind(this));\n });\n }\n\n #removeInsole(device: Device) {\n const foundDevice = InsoleSides.some((side) => {\n if (this[side] != device) {\n return false;\n }\n\n _console.log(`removing ${side} insole`, device);\n removeEventListeners(device, this.#boundDeviceEventListeners);\n delete this[side];\n\n return true;\n });\n if (foundDevice) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n return foundDevice;\n }\n\n #boundDeviceEventListeners: BoundDeviceEventListeners = {\n isConnected: this.#onDeviceIsConnected.bind(this),\n sensorData: this.#onDeviceSensorData.bind(this),\n getType: this.#onDeviceType.bind(this),\n };\n\n #redispatchDeviceEvent(deviceEvent: DeviceEvent) {\n const { type, target: device, message } = deviceEvent;\n this.#dispatchEvent(getDevicePairDeviceEventType(type), {\n ...message,\n device,\n side: device.insoleSide,\n });\n }\n\n #onDeviceIsConnected(deviceEvent: DeviceEventMap[\"isConnected\"]) {\n this.#dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n }\n\n #onDeviceType(deviceEvent: DeviceEventMap[\"getType\"]) {\n const { target: device } = deviceEvent;\n if (this[device.insoleSide] == device) {\n return;\n }\n const foundDevice = this.#removeInsole(device);\n if (!foundDevice) {\n return;\n }\n this.assignInsole(device);\n }\n\n // SENSOR CONFIGURATION\n async setSensorConfiguration(sensorConfiguration: SensorConfiguration) {\n for (let i = 0; i < InsoleSides.length; i++) {\n const side = InsoleSides[i];\n if (this[side]?.isConnected) {\n await this[side].setSensorConfiguration(sensorConfiguration);\n }\n }\n }\n\n // SENSOR DATA\n #sensorDataManager = new DevicePairSensorDataManager();\n #onDeviceSensorData(deviceEvent: DeviceEventMap[\"sensorData\"]) {\n if (this.isConnected) {\n this.#sensorDataManager.onDeviceSensorData(deviceEvent);\n }\n }\n resetPressureRange() {\n this.#sensorDataManager.resetPressureRange();\n }\n\n // VIBRATION\n async triggerVibration(vibrationConfigurations: VibrationConfiguration[], sendImmediately?: boolean) {\n const promises = InsoleSides.map((side) => {\n return this[side]?.triggerVibration(vibrationConfigurations, sendImmediately);\n }).filter(Boolean);\n return Promise.allSettled(promises);\n }\n\n // SHARED INSTANCE\n static #shared = new DevicePair();\n static get shared() {\n return this.#shared;\n }\n static {\n DeviceManager.AddEventListener(\"deviceConnected\", (event) => {\n const { device } = event.message;\n if (device.isInsole) {\n this.#shared.assignInsole(device);\n }\n });\n }\n}\n\nexport default DevicePair;\n","import { DeviceEventTypes } from \"../Device.ts\";\nimport { ConnectionMessageType, ConnectionMessageTypes } from \"../connection/BaseConnectionManager.ts\";\nimport { concatenateArrayBuffers } from \"../utils/ArrayBufferUtils.ts\";\nimport { createConsole } from \"../utils/Console.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\n\nconst _console = createConsole(\"ServerUtils\", { log: false });\n\nexport const ServerMessageTypes = [\n \"isScanningAvailable\",\n \"isScanning\",\n \"startScan\",\n \"stopScan\",\n \"discoveredDevice\",\n \"discoveredDevices\",\n \"expiredDiscoveredDevice\",\n \"connectToDevice\",\n \"disconnectFromDevice\",\n \"connectedDevices\",\n \"deviceMessage\",\n] as const;\nexport type ServerMessageType = (typeof ServerMessageTypes)[number];\n\nexport const DeviceMessageTypes = [\"connectionStatus\", \"batteryLevel\", \"deviceInformation\", \"rx\", \"smp\"] as const;\nexport type DeviceMessageType = (typeof DeviceMessageTypes)[number];\n\n// MESSAGING\n\nexport type MessageLike = number | number[] | ArrayBufferLike | DataView | boolean | string | any;\n\nexport interface Message<MessageType extends string> {\n type: MessageType;\n data?: MessageLike | MessageLike[];\n}\n\nexport function createMessage<MessageType extends string>(\n enumeration: readonly MessageType[],\n ...messages: (Message<MessageType> | MessageType)[]\n) {\n _console.log(\"createMessage\", ...messages);\n\n const messageBuffers = messages.map((message) => {\n if (typeof message == \"string\") {\n message = { type: message };\n }\n\n if (message.data != undefined) {\n if (!Array.isArray(message.data)) {\n message.data = [message.data];\n }\n } else {\n message.data = [];\n }\n\n const messageDataArrayBuffer = concatenateArrayBuffers(...message.data);\n const messageDataArrayBufferByteLength = messageDataArrayBuffer.byteLength;\n\n _console.assertEnumWithError(message.type, enumeration);\n const messageTypeEnum = enumeration.indexOf(message.type);\n\n const messageDataLengthDataView = new DataView(new ArrayBuffer(2));\n messageDataLengthDataView.setUint16(0, messageDataArrayBufferByteLength, true);\n\n return concatenateArrayBuffers(messageTypeEnum, messageDataLengthDataView, messageDataArrayBuffer);\n });\n _console.log(\"messageBuffers\", ...messageBuffers);\n return concatenateArrayBuffers(...messageBuffers);\n}\n\nexport type ServerMessage = ServerMessageType | Message<ServerMessageType>;\nexport function createServerMessage(...messages: ServerMessage[]) {\n _console.log(\"createServerMessage\", ...messages);\n return createMessage(ServerMessageTypes, ...messages);\n}\n\nexport type DeviceMessage = DeviceEventType | Message<DeviceEventType>;\nexport function createDeviceMessage(...messages: DeviceMessage[]) {\n _console.log(\"createDeviceMessage\", ...messages);\n return createMessage(DeviceEventTypes, ...messages);\n}\n\nexport type ClientDeviceMessage = ConnectionMessageType | Message<ConnectionMessageType>;\nexport function createClientDeviceMessage(...messages: ClientDeviceMessage[]) {\n _console.log(\"createClientDeviceMessage\", ...messages);\n return createMessage(ConnectionMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const isScanningAvailableRequestMessage = createServerMessage(\"isScanningAvailable\");\nexport const isScanningRequestMessage = createServerMessage(\"isScanning\");\nexport const startScanRequestMessage = createServerMessage(\"startScan\");\nexport const stopScanRequestMessage = createServerMessage(\"stopScan\");\nexport const discoveredDevicesMessage = createServerMessage(\"discoveredDevices\");\n","import { createConsole } from \"../utils/Console.ts\";\nimport { isInBrowser } from \"../utils/environment.ts\";\nimport BaseConnectionManager, { ConnectionType, ConnectionMessageType } from \"./BaseConnectionManager.ts\";\nimport { DeviceEventTypes } from \"../Device.ts\";\nimport { parseMessage } from \"../utils/ParseUtils.ts\";\nimport { DeviceInformationMessageTypes } from \"../DeviceInformationManager.ts\";\nimport { DeviceEventType } from \"../Device.ts\";\nimport { ClientDeviceMessage } from \"../server/ServerUtils.ts\";\n\nconst _console = createConsole(\"ClientConnectionManager\", { log: true });\n\nexport type SendClientMessageCallback = (...messages: ClientDeviceMessage[]) => void;\n\nconst ClientDeviceInformationMessageTypes: ConnectionMessageType[] = [...DeviceInformationMessageTypes, \"batteryLevel\"];\n\nclass ClientConnectionManager extends BaseConnectionManager {\n static get isSupported() {\n return isInBrowser;\n }\n static get type(): ConnectionType {\n return \"client\";\n }\n\n #bluetoothId!: string;\n get bluetoothId() {\n return this.#bluetoothId!;\n }\n set bluetoothId(newBluetoothId) {\n _console.assertTypeWithError(newBluetoothId, \"string\");\n if (this.#bluetoothId == newBluetoothId) {\n _console.log(\"redundant bluetoothId assignment\");\n return;\n }\n this.#bluetoothId = newBluetoothId;\n }\n\n #isConnected = false;\n get isConnected() {\n return this.#isConnected;\n }\n set isConnected(newIsConnected) {\n _console.assertTypeWithError(newIsConnected, \"boolean\");\n if (this.#isConnected == newIsConnected) {\n _console.log(\"redundant newIsConnected assignment\", newIsConnected);\n return;\n }\n this.#isConnected = newIsConnected;\n\n this.status = this.#isConnected ? \"connected\" : \"notConnected\";\n\n if (this.isConnected) {\n this.#requestDeviceInformation();\n }\n }\n\n async connect() {\n await super.connect();\n this.sendClientConnectMessage();\n }\n async disconnect() {\n await super.disconnect();\n this.sendClientDisconnectMessage();\n }\n\n get canReconnect() {\n return true;\n }\n async reconnect() {\n await super.reconnect();\n _console.log(\"attempting to reconnect...\");\n this.connect();\n }\n\n sendClientMessage!: SendClientMessageCallback;\n sendClientConnectMessage!: Function;\n sendClientDisconnectMessage!: Function;\n\n async sendSmpMessage(data: ArrayBuffer) {\n super.sendSmpMessage(data);\n this.sendClientMessage({ type: \"smp\", data });\n }\n\n async sendTxData(data: ArrayBuffer) {\n super.sendTxData(data);\n if (data.byteLength == 0) {\n return;\n }\n this.sendClientMessage({ type: \"tx\", data });\n }\n\n #requestDeviceInformation() {\n this.sendClientMessage(...ClientDeviceInformationMessageTypes);\n }\n\n onClientMessage(dataView: DataView) {\n _console.log({ dataView });\n parseMessage(dataView, DeviceEventTypes, this.#onClientMessageCallback.bind(this), null, true);\n this.onMessagesReceived!();\n }\n\n #onClientMessageCallback(messageType: DeviceEventType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isConnected\":\n const isConnected = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isConnected });\n this.isConnected = isConnected;\n break;\n\n case \"rx\":\n this.parseRxMessage(dataView);\n break;\n\n default:\n this.onMessageReceived!(messageType as ConnectionMessageType, dataView);\n break;\n }\n }\n}\n\nexport default ClientConnectionManager;\n","import { createConsole } from \"../utils/Console.ts\";\nimport {\n ServerMessageTypes,\n discoveredDevicesMessage,\n ServerMessage,\n MessageLike,\n ClientDeviceMessage,\n createClientDeviceMessage,\n ServerMessageType,\n} from \"./ServerUtils.ts\";\nimport { parseMessage, parseStringFromDataView } from \"../utils/ParseUtils.ts\";\nimport EventDispatcher, { BoundEventListeners, Event } from \"../utils/EventDispatcher.ts\";\nimport Device from \"../Device.ts\";\nimport { sliceDataView } from \"../utils/ArrayBufferUtils.ts\";\nimport { DiscoveredDevice, DiscoveredDevicesMap, ScannerEventMessages } from \"../scanner/BaseScanner.ts\";\nimport ClientConnectionManager from \"../connection/ClientConnectionManager.ts\";\n\nconst _console = createConsole(\"BaseClient\", { log: true });\n\nexport const ClientConnectionStatuses = [\"notConnected\", \"connecting\", \"connected\", \"disconnecting\"] as const;\nexport type ClientConnectionStatus = (typeof ClientConnectionStatuses)[number];\n\nexport const ClientEventTypes = [\n ...ClientConnectionStatuses,\n \"connectionStatus\",\n \"isConnected\",\n \"isScanningAvailable\",\n \"isScanning\",\n \"discoveredDevice\",\n \"expiredDiscoveredDevice\",\n] as const;\nexport type ClientEventType = (typeof ClientEventTypes)[number];\n\ninterface ClientConnectionEventMessages {\n connectionStatus: { connectionStatus: ClientConnectionStatus };\n isConnected: { isConnected: boolean };\n}\n\nexport type ClientEventMessages = ClientConnectionEventMessages & ScannerEventMessages;\n\nexport type ClientEventDispatcher = EventDispatcher<BaseClient, ClientEventType, ClientEventMessages>;\nexport type ClientEvent = Event<BaseClient, ClientEventType, ClientEventMessages>;\nexport type BoundClientEventListeners = BoundEventListeners<BaseClient, ClientEventType, ClientEventMessages>;\n\nexport type ServerURL = string | URL;\n\ntype DevicesMap = { [deviceId: string]: Device };\n\nabstract class BaseClient {\n protected get baseConstructor() {\n return this.constructor as typeof BaseClient;\n }\n\n #reset() {\n this.#isScanningAvailable = false;\n this.#isScanning = false;\n for (const id in this.#devices) {\n const device = this.#devices[id];\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n //device.removeAllEventListeners();\n }\n //this.#devices = {};\n }\n\n // DEVICES\n #devices: DevicesMap = {};\n get devices(): Readonly<DevicesMap> {\n return this.#devices;\n }\n\n #eventDispatcher: ClientEventDispatcher = new EventDispatcher(this as BaseClient, ClientEventTypes);\n get addEventListener() {\n return this.#eventDispatcher.addEventListener;\n }\n protected get dispatchEvent() {\n return this.#eventDispatcher.dispatchEvent;\n }\n get removeEventListener() {\n return this.#eventDispatcher.removeEventListener;\n }\n get waitForEvent() {\n return this.#eventDispatcher.waitForEvent;\n }\n\n abstract isConnected: boolean;\n protected assertConnection() {\n _console.assertWithError(this.isConnected, \"notConnected\");\n }\n\n abstract isDisconnected: boolean;\n protected assertDisconnection() {\n _console.assertWithError(this.isDisconnected, \"not disconnected\");\n }\n\n abstract connect(): void;\n abstract disconnect(): void;\n abstract reconnect(): void;\n abstract toggleConnection(url?: ServerURL): void;\n\n static _reconnectOnDisconnection = true;\n static get ReconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n static set ReconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n protected _reconnectOnDisconnection = this.baseConstructor.ReconnectOnDisconnection;\n get reconnectOnDisconnection() {\n return this._reconnectOnDisconnection;\n }\n set reconnectOnDisconnection(newReconnectOnDisconnection) {\n _console.assertTypeWithError(newReconnectOnDisconnection, \"boolean\");\n this._reconnectOnDisconnection = newReconnectOnDisconnection;\n }\n\n abstract sendServerMessage(...messages: ServerMessage[]): void;\n\n // CONNECTION STATUS\n #_connectionStatus: ClientConnectionStatus = \"notConnected\";\n protected get _connectionStatus() {\n return this.#_connectionStatus;\n }\n protected set _connectionStatus(newConnectionStatus) {\n _console.assertTypeWithError(newConnectionStatus, \"string\");\n _console.log({ newConnectionStatus });\n this.#_connectionStatus = newConnectionStatus;\n\n this.dispatchEvent(\"connectionStatus\", { connectionStatus: this.connectionStatus });\n this.dispatchEvent(this.connectionStatus, {});\n\n switch (newConnectionStatus) {\n case \"connected\":\n case \"notConnected\":\n this.dispatchEvent(\"isConnected\", { isConnected: this.isConnected });\n if (this.isConnected) {\n this.sendServerMessage(\"isScanningAvailable\", \"discoveredDevices\", \"connectedDevices\");\n } else {\n this.#reset();\n }\n break;\n }\n }\n get connectionStatus() {\n return this._connectionStatus;\n }\n\n protected parseMessage(dataView: DataView) {\n _console.log(\"parseMessage\", { dataView });\n parseMessage(dataView, ServerMessageTypes, this.#parseMessageCallback.bind(this), null, true);\n }\n\n #parseMessageCallback(messageType: ServerMessageType, dataView: DataView) {\n let byteOffset = 0;\n\n _console.log({ messageType }, dataView);\n\n switch (messageType) {\n case \"isScanningAvailable\":\n {\n const isScanningAvailable = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanningAvailable });\n this.#isScanningAvailable = isScanningAvailable;\n }\n break;\n case \"isScanning\":\n {\n const isScanning = Boolean(dataView.getUint8(byteOffset++));\n _console.log({ isScanning });\n this.#isScanning = isScanning;\n }\n break;\n case \"discoveredDevice\":\n {\n const { string: discoveredDeviceString } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ discoveredDeviceString });\n\n const discoveredDevice: DiscoveredDevice = JSON.parse(discoveredDeviceString);\n _console.log({ discoveredDevice });\n\n this.onDiscoveredDevice(discoveredDevice);\n }\n break;\n case \"expiredDiscoveredDevice\":\n {\n const { string: bluetoothId } = parseStringFromDataView(dataView, byteOffset);\n this.#onExpiredDiscoveredDevice(bluetoothId);\n }\n break;\n case \"connectedDevices\":\n {\n if (dataView.byteLength == 0) {\n break;\n }\n const { string: connectedBluetoothDeviceIdStrings } = parseStringFromDataView(dataView, byteOffset);\n _console.log({ connectedBluetoothDeviceIdStrings });\n const connectedBluetoothDeviceIds = JSON.parse(connectedBluetoothDeviceIdStrings).connectedDevices;\n _console.log({ connectedBluetoothDeviceIds });\n this.onConnectedBluetoothDeviceIds(connectedBluetoothDeviceIds);\n }\n break;\n case \"deviceMessage\":\n {\n const { string: bluetoothId, byteOffset: _byteOffset } = parseStringFromDataView(dataView, byteOffset);\n byteOffset = _byteOffset;\n const device = this.#devices[bluetoothId];\n _console.assertWithError(device, `no device found for id ${bluetoothId}`);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n const _dataView = sliceDataView(dataView, byteOffset);\n connectionManager.onClientMessage(_dataView);\n }\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // SCANNING\n #_isScanningAvailable = false;\n get #isScanningAvailable() {\n return this.#_isScanningAvailable;\n }\n set #isScanningAvailable(newIsAvailable) {\n _console.assertTypeWithError(newIsAvailable, \"boolean\");\n this.#_isScanningAvailable = newIsAvailable;\n this.dispatchEvent(\"isScanningAvailable\", { isScanningAvailable: this.isScanningAvailable });\n if (this.isScanningAvailable) {\n this.#requestIsScanning();\n }\n }\n get isScanningAvailable() {\n return this.#isScanningAvailable;\n }\n #assertIsScanningAvailable() {\n this.assertConnection();\n _console.assertWithError(this.isScanningAvailable, \"scanning is not available\");\n }\n protected requestIsScanningAvailable() {\n this.sendServerMessage(\"isScanningAvailable\");\n }\n\n #_isScanning = false;\n get #isScanning() {\n return this.#_isScanning;\n }\n set #isScanning(newIsScanning) {\n _console.assertTypeWithError(newIsScanning, \"boolean\");\n this.#_isScanning = newIsScanning;\n this.dispatchEvent(\"isScanning\", { isScanning: this.isScanning });\n }\n get isScanning() {\n return this.#isScanning;\n }\n #requestIsScanning() {\n this.sendServerMessage(\"isScanning\");\n }\n\n #assertIsScanning() {\n _console.assertWithError(this.isScanning, \"is not scanning\");\n }\n #assertIsNotScanning() {\n _console.assertWithError(!this.isScanning, \"is already scanning\");\n }\n\n startScan() {\n this.#assertIsNotScanning();\n this.sendServerMessage(\"startScan\");\n }\n stopScan() {\n this.#assertIsScanning();\n this.sendServerMessage(\"stopScan\");\n }\n toggleScan() {\n this.#assertIsScanningAvailable();\n\n if (this.isScanning) {\n this.stopScan();\n } else {\n this.startScan();\n }\n }\n\n // PERIPHERALS\n #discoveredDevices: DiscoveredDevicesMap = {};\n get discoveredDevices(): Readonly<DiscoveredDevicesMap> {\n return this.#discoveredDevices;\n }\n\n protected onDiscoveredDevice(discoveredDevice: DiscoveredDevice) {\n _console.log({ discoveredDevice });\n this.#discoveredDevices[discoveredDevice.bluetoothId] = discoveredDevice;\n this.dispatchEvent(\"discoveredDevice\", { discoveredDevice });\n }\n requestDiscoveredDevices() {\n this.sendServerMessage({ type: \"discoveredDevices\" });\n }\n #onExpiredDiscoveredDevice(bluetoothId: string) {\n _console.log({ expiredBluetoothDeviceId: bluetoothId });\n const discoveredDevice = this.#discoveredDevices[bluetoothId];\n if (!discoveredDevice) {\n _console.warn(`no discoveredDevice found with id \"${bluetoothId}\"`);\n return;\n }\n _console.log({ expiredDiscoveredDevice: discoveredDevice });\n delete this.#discoveredDevices[bluetoothId];\n this.dispatchEvent(\"expiredDiscoveredDevice\", { discoveredDevice });\n }\n\n // DEVICE CONNECTION\n connectToDevice(bluetoothId: string) {\n return this.requestConnectionToDevice(bluetoothId);\n }\n protected requestConnectionToDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.#getOrCreateDevice(bluetoothId);\n device.connect();\n return device;\n }\n protected sendConnectToDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"connectToDevice\", data: bluetoothId });\n }\n\n // DEVICE CONNECTION\n createDevice(bluetoothId: string) {\n const device = new Device();\n const clientConnectionManager = new ClientConnectionManager();\n clientConnectionManager.bluetoothId = bluetoothId;\n clientConnectionManager.sendClientMessage = this.sendDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientConnectMessage = this.sendConnectToDeviceMessage.bind(this, bluetoothId);\n clientConnectionManager.sendClientDisconnectMessage = this.sendDisconnectFromDeviceMessage.bind(this, bluetoothId);\n device.connectionManager = clientConnectionManager;\n return device;\n }\n\n #getOrCreateDevice(bluetoothId: string) {\n let device = this.#devices[bluetoothId];\n if (!device) {\n device = this.createDevice(bluetoothId);\n this.#devices[bluetoothId] = device;\n }\n return device;\n }\n protected onConnectedBluetoothDeviceIds(bluetoothIds: string[]) {\n _console.log({ bluetoothIds });\n bluetoothIds.forEach((bluetoothId) => {\n const device = this.#getOrCreateDevice(bluetoothId);\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = true;\n });\n }\n\n disconnectFromDevice(bluetoothId: string) {\n this.requestDisconnectionFromDevice(bluetoothId);\n }\n protected requestDisconnectionFromDevice(bluetoothId: string) {\n this.assertConnection();\n _console.assertTypeWithError(bluetoothId, \"string\");\n const device = this.devices[bluetoothId];\n _console.assertWithError(device, `no device found with id ${bluetoothId}`);\n device.disconnect();\n return device;\n }\n protected sendDisconnectFromDeviceMessage(bluetoothId: string) {\n this.sendServerMessage({ type: \"disconnectFromDevice\", data: bluetoothId });\n }\n\n protected sendDeviceMessage(bluetoothId: string, ...messages: ClientDeviceMessage[]) {\n this.sendServerMessage({\n type: \"deviceMessage\",\n data: [bluetoothId, createClientDeviceMessage(...messages)],\n });\n }\n}\n\nexport default BaseClient;\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createMessage, Message } from \"../ServerUtils.ts\";\n\nconst _console = createConsole(\"WebSocketUtils\", { log: false });\n\nexport const webSocketPingTimeout = 30_000_000;\nexport const webSocketReconnectTimeout = 3_000;\n\nexport const WebSocketMessageTypes = [\"ping\", \"pong\", \"serverMessage\"] as const;\nexport type WebSocketMessageType = (typeof WebSocketMessageTypes)[number];\n\nexport type WebSocketMessage = WebSocketMessageType | Message<WebSocketMessageType>;\nexport function createWebSocketMessage(...messages: WebSocketMessage[]) {\n _console.log(\"createWebSocketMessage\", ...messages);\n return createMessage(WebSocketMessageTypes, ...messages);\n}\n\n// STATIC MESSAGES\nexport const webSocketPingMessage = createWebSocketMessage(\"ping\");\nexport const webSocketPongMessage = createWebSocketMessage(\"pong\");\n","import { createConsole } from \"../../utils/Console.ts\";\nimport { createServerMessage, MessageLike, ServerMessage } from \"../ServerUtils.ts\";\nimport { addEventListeners, removeEventListeners } from \"../../utils/EventUtils.ts\";\nimport ClientConnectionManager from \"../../connection/ClientConnectionManager.ts\";\nimport BaseClient, { ServerURL } from \"../BaseClient.ts\";\nimport type * as ws from \"ws\";\nimport Timer from \"../../utils/Timer.ts\";\nimport {\n createWebSocketMessage,\n WebSocketMessageType,\n WebSocketMessageTypes,\n webSocketPingTimeout,\n webSocketReconnectTimeout,\n WebSocketMessage,\n} from \"./WebSocketUtils.ts\";\nimport { parseMessage } from \"../../utils/ParseUtils.ts\";\n\nconst _console = createConsole(\"WebSocketClient\", { log: true });\n\nclass WebSocketClient extends BaseClient {\n // WEBSOCKET\n #webSocket?: WebSocket;\n get webSocket() {\n return this.#webSocket;\n }\n set webSocket(newWebSocket) {\n if (this.#webSocket == newWebSocket) {\n _console.log(\"redundant webSocket assignment\");\n return;\n }\n\n _console.log(\"assigning webSocket\", newWebSocket);\n\n if (this.#webSocket) {\n removeEventListeners(this.#webSocket, this.#boundWebSocketEventListeners);\n }\n\n addEventListeners(newWebSocket, this.#boundWebSocketEventListeners);\n this.#webSocket = newWebSocket;\n\n _console.log(\"assigned webSocket\");\n }\n get readyState() {\n return this.webSocket?.readyState;\n }\n get isConnected() {\n return this.readyState == WebSocket.OPEN;\n }\n get isDisconnected() {\n return this.readyState == WebSocket.CLOSED;\n }\n\n connect(url: string | URL = `wss://${location.host}`) {\n if (this.webSocket) {\n this.assertDisconnection();\n }\n this._connectionStatus = \"connecting\";\n this.webSocket = new WebSocket(url);\n }\n\n disconnect() {\n this.assertConnection();\n if (this.reconnectOnDisconnection) {\n this.reconnectOnDisconnection = false;\n this.webSocket!.addEventListener(\n \"close\",\n () => {\n this.reconnectOnDisconnection = true;\n },\n { once: true }\n );\n }\n this._connectionStatus = \"disconnecting\";\n this.webSocket!.close();\n }\n\n reconnect() {\n this.assertDisconnection();\n this.connect(this.webSocket!.url);\n }\n\n toggleConnection(url?: ServerURL) {\n if (this.isConnected) {\n this.disconnect();\n } else if (url && this.webSocket?.url == url) {\n this.reconnect();\n } else {\n this.connect(url);\n }\n }\n\n // WEBSOCKET MESSAGING\n sendMessage(message: MessageLike) {\n this.assertConnection();\n this.#webSocket!.send(message);\n }\n\n sendServerMessage(...messages: ServerMessage[]) {\n this.sendMessage(createWebSocketMessage({ type: \"serverMessage\", data: createServerMessage(...messages) }));\n }\n\n #sendWebSocketMessage(...messages: WebSocketMessage[]) {\n this.sendMessage(createWebSocketMessage(...messages));\n }\n\n // WEBSOCKET EVENTS\n #boundWebSocketEventListeners: { [eventType: string]: Function } = {\n open: this.#onWebSocketOpen.bind(this),\n message: this.#onWebSocketMessage.bind(this),\n close: this.#onWebSocketClose.bind(this),\n error: this.#onWebSocketError.bind(this),\n };\n\n #onWebSocketOpen(event: ws.Event) {\n _console.log(\"webSocket.open\", event);\n this.#pingTimer.start();\n this._connectionStatus = \"connected\";\n }\n async #onWebSocketMessage(event: ws.MessageEvent) {\n _console.log(\"webSocket.message\", event);\n this.#pingTimer.restart();\n //@ts-expect-error\n const arrayBuffer = await event.data.arrayBuffer();\n const dataView = new DataView(arrayBuffer);\n this.#parseWebSocketMessage(dataView);\n }\n #onWebSocketClose(event: ws.CloseEvent) {\n _console.log(\"webSocket.close\", event);\n\n this._connectionStatus = \"notConnected\";\n\n Object.entries(this.devices).forEach(([id, device]) => {\n const connectionManager = device.connectionManager! as ClientConnectionManager;\n connectionManager.isConnected = false;\n });\n\n this.#pingTimer.stop();\n if (this.reconnectOnDisconnection) {\n setTimeout(() => {\n this.reconnect();\n }, webSocketReconnectTimeout);\n }\n }\n #onWebSocketError(event: ws.ErrorEvent) {\n _console.error(\"webSocket.error\", event.message);\n }\n\n // PARSING\n #parseWebSocketMessage(dataView: DataView) {\n parseMessage(dataView, WebSocketMessageTypes, this.#onServerMessage.bind(this), null, true);\n }\n\n #onServerMessage(messageType: WebSocketMessageType, dataView: DataView) {\n switch (messageType) {\n case \"ping\":\n this.#pong();\n break;\n case \"pong\":\n break;\n case \"serverMessage\":\n this.parseMessage(dataView);\n break;\n default:\n _console.error(`uncaught messageType \"${messageType}\"`);\n break;\n }\n }\n\n // PING\n #pingTimer = new Timer(this.#ping.bind(this), webSocketPingTimeout);\n #ping() {\n this.#sendWebSocketMessage(\"ping\");\n }\n #pong() {\n this.#sendWebSocketMessage(\"pong\");\n }\n}\n\nexport default WebSocketClient;\n"],"names":[],"mappings":"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;;;AAIJ;AACE;;AAEF;;AAEA;AACF;AAEE;AACA;;AAEE;;AAEF;AACA;AACA;AACF;AAGE;AACA;AACF;;;AAKE;;;AAGA;AACA;AACA;AACF;;;ACnQF;AAKO;AAGA;AAiBA;AACL;AACA;AACA;AACA;AACA;AACA;;AAIK;AAGA;AAGA;AAGA;AACL;AACA;AACA;AACA;AACA;;AAQF;AAYE;;;AAGA;AACE;;AAEF;AACE;;AAIF;AACE;;AAQF;;AAIA;AA4EA;AACA;AA0EA;AA1JE;;AAIF;;;;AAIE;AACA;AACE;;;AAGF;AACA;AACA;AAEA;AACE;;;AAEA;;AAGF;AACE;;;AAIJ;AACE;;AAyBF;AACE;AACA;AACA;;AAEF;AACE;;AAEF;AACE;AACA;;;AAGF;AACE;AACA;AACA;AACA;;;AAIA;AACA;;AAKF;AACE;;;;;;AAQE;;;AAIF;AACE;;;AAGF;AAEA;;;;;AAME;;AAEF;AACA;AAEA;AACE;;;AAGE;AACE;AACE;;AAEF;AACA;AACF;;;AAIA;AACA;AACA;;;;AAGF;AACA;AACA;;AAGF;;;AAMA;;AAGF;AACE;;;;AAmBA;AACA;;AAEH;;AAtMG;AACF;AA0BE;AACF;;AAsCA;;AAIA;;AAIA;;AAIA;AAGE;AACA;AACF;;AAmGE;AACF;AAKE;AACE;AACA;;AAEJ;;AChQI;AACJ;AACF;;ACPA;AAqBgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;AAEgB;AACd;AACA;AACA;AACA;AACE;AACF;AACF;;ACrCA;AAOA;AACE;AACF;AAGA;AACE;;;AAGF;AAEA;AACE;AACF;AAEA;AACE;AACF;AAgBA;AACE;AACE;AACE;AACA;AACE;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACF;AACF;AACD;AACE;AACA;AACE;AACE;AACD;AACF;AACF;AACD;AACE;AACA;;;AAGC;AACF;AACD;AACE;AACA;AACE;AACD;AACF;AACF;AACF;AAEM;AACA;AACL;AACA;AACA;;AAE6B;AAEzB;;;AAGJ;;;AAGE;;;;;;;AAOF;AACF;AAEO;AACA;AAKP;AACE;;;;AAIA;;;AAGI;AACA;;AAEF;AAEF;AACF;AAIM;;AAKJ;AACA;;;;;AAKI;;;;;;;AAOF;AACA;AACF;AACA;AACF;AAEM;AAGJ;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAKA;AACA;AACA;AACE;;;;AAMF;AACA;AACA;AACE;;;;AAMF;AACE;;;;AAMF;AACE;;;AAIJ;AACF;;ACpNA;AAIA;AAAA;;;;;AAII;AACE;;;;;;AAMM;;;;AAKR;;;;AAKA;AACA;;;;;AAKH;;;ACpBD;AAWA;AASA;AACE;AACF;AAGA;AAAA;;;;;;;;;;;;;AACE;AACE;;AAUF;AACE;;AAEF;AACE;;AAIF;;;;AAIE;AACE;;;AAGF;;;;AAIE;;AAEF;;AAGF;AACE;;AAEF;AACE;;AAMF;AACE;AAEA;AACE;AACE;;AAED;AAED;AACA;AAEA;;;AAIA;AAEA;AAEA;;;AAEA;AACA;AACA;AACA;;;AAmEJ;AACE;AACA;AACA;AACA;;AA+BF;AACE;;;;;AAMA;AACE;AACA;;;AAEA;AACA;;AAEF;;AAGE;AACA;AACA;AACE;;;;AAUN;AACE;;AAEF;AACE;AACA;AACA;AACA;AACE;;;AAEA;AACA;;AAGF;AACE;AACA;AACA;;;AAEA;AACA;;;AAGL;;AAvJG;AAEA;;;AAKA;AACA;AACE;AACA;;;AAGA;AACA;;AAEA;AACA;AACA;AACA;AACE;AACA;;AAEA;;AAKA;;AAEA;;AAEA;AACE;AACA;;AAEF;AACE;AACA;AACA;AACE;;;;;AAKV;AAEE;;;AAIA;;AAEE;;AAEA;AACE;AACA;;AAEJ;AAEA;AACF;AASE;AAEA;AACA;AACF;AAGE;AAEA;AACA;AAKA;AACA;;;AAIA;AACE;;;AAEA;;AAEJ;AA4BE;AACA;AACF;;ACzNF;AACA;AACA;AAEO;AACP;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEO;AACP;AACA;AACA;;ACzYA;AAEO;AAEP;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;;;ACnbA;AAEO;AAGA;AACL;;;;;;AAQK;AAyBP;AAGE;;AAkDA;;;;AAjDE;;;AAKF;AACE;;AAKF;AACE;;AAEF;AACE;;;AAIA;;AAGE;AACE;;;AAGF;AACE;;;;AAKJ;;AAIA;AAEA;;AAEA;;AAIA;AAEA;;AAIF;;;AAkBA;;;AAUA;;AAGE;AACA;AAEA;;AAGF;AA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","x_google_ignoreList":[0,8]}