@robotical/webapp-types 3.25.11 → 3.25.13

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,6 +1,6 @@
1
1
  {
2
2
  "name": "marty-web-app",
3
- "version": "3.25.11",
3
+ "version": "3.25.13",
4
4
  "private": true,
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -18,7 +18,7 @@
18
18
  "@robotical/martyblocks": "^4.0.77",
19
19
  "@robotical/martyblocksjr": "^4.5.6",
20
20
  "@robotical/maze": "^0.1.22",
21
- "@robotical/rc-sequencer": "^1.1.15",
21
+ "@robotical/rc-sequencer": "^1.1.16",
22
22
  "@robotical/ricjs-robotical-addons": "^1.2.8",
23
23
  "@robotical/robofly-game": "^0.1.25",
24
24
  "@robotical/roboticaljs": "^1.1.28",
@@ -117,7 +117,8 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
117
117
  }
118
118
  else {
119
119
  var profile = getAddonColourSensorProfile(this.marty, colourSensor);
120
- var context = JSON.stringify([colourSensor.id, colourSensor.name, profile === null || profile === void 0 ? void 0 : profile.serialNumber, profile === null || profile === void 0 ? void 0 : profile.offsetsHex, profile === null || profile === void 0 ? void 0 : profile.whiteCalibrationHex, profile === null || profile === void 0 ? void 0 : profile.capturedAtMs]);
120
+ var profileSignal = profile === null || profile === void 0 ? void 0 : profile.signal;
121
+ var context = JSON.stringify([colourSensor.id, colourSensor.name, profile === null || profile === void 0 ? void 0 : profile.serialNumber, profile === null || profile === void 0 ? void 0 : profile.offsetsHex, profile === null || profile === void 0 ? void 0 : profile.whiteCalibrationHex, profile === null || profile === void 0 ? void 0 : profile.capturedAtMs, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.airMax.red, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.airMax.green, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.airMax.blue, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.margin.red, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.margin.green, profileSignal === null || profileSignal === void 0 ? void 0 : profileSignal.margin.blue]);
121
122
  if (context !== this.colourContext) {
122
123
  this.resetColourDetection();
123
124
  this.colourContext = context;
@@ -22,6 +22,19 @@ declare const colourDiagnosticsEnglish: {
22
22
  confidenceHelp: string;
23
23
  withoutAir: string;
24
24
  firmwareAir: string;
25
+ profileSource: string;
26
+ eepromStatus: string;
27
+ eeprom: string;
28
+ local: string;
29
+ firebase: string;
30
+ defaultRules: string;
31
+ verified: string;
32
+ empty: string;
33
+ invalid: string;
34
+ readFailed: string;
35
+ writeFailed: string;
36
+ firmwareNotAudited: string;
37
+ notChecked: string;
25
38
  waiting: string;
26
39
  capture: string;
27
40
  captureAir: string;
@@ -17,6 +17,12 @@ var colourDiagnosticsEnglish = {
17
17
  confidenceHelp: "Rule strength, not probability. Higher scores mean more room before a colour or air cutoff. Air needs a saved calibration to score.",
18
18
  withoutAir: "Firmware reports air; the RGB signal matches {{result}}.",
19
19
  firmwareAir: "Firmware Air",
20
+ profileSource: "Profile: {{source}}",
21
+ eepromStatus: "EEPROM: {{status}}",
22
+ eeprom: "sensor memory", local: "local backup", firebase: "database backup", defaultRules: "default rules",
23
+ verified: "verified", empty: "no profile saved", invalid: "invalid or outdated profile",
24
+ readFailed: "read failed", writeFailed: "save failed", firmwareNotAudited: "not enabled for this firmware",
25
+ notChecked: "not checked yet",
20
26
  waiting: "Select a sensor and wait for fresh readings.",
21
27
  capture: "Take {{colour}} snapshot",
22
28
  captureAir: "Take air snapshot",
@@ -4,7 +4,7 @@ export type RGB = {
4
4
  blue: number;
5
5
  };
6
6
  export type ColourSignalProfile = {
7
- airMin: RGB;
7
+ airMin?: RGB;
8
8
  airMax: RGB;
9
9
  margin: RGB;
10
10
  };
@@ -0,0 +1,36 @@
1
+ import type { ColourSignalProfile } from "./colour-classifier";
2
+ export declare const COLOUR_PROFILE_EEPROM_ADDRESS: "401c";
3
+ export declare const COLOUR_PROFILE_EEPROM_FORMAT = 1;
4
+ export type ColourProfileIdentity = {
5
+ serialNumber: string;
6
+ typeCode: string;
7
+ firmwareVersion: string;
8
+ offsetsHex: string;
9
+ whiteCalibrationHex: string;
10
+ };
11
+ export type ColourProfileStorageEvidence = {
12
+ source: "eeprom" | "local" | "firebase" | "defaultRules";
13
+ eeprom: {
14
+ status: "verified" | "empty" | "invalid" | "readFailed" | "writeFailed" | "firmwareNotAudited" | "notChecked";
15
+ address: typeof COLOUR_PROFILE_EEPROM_ADDRESS;
16
+ formatVersion: number;
17
+ hex?: string;
18
+ reason?: string;
19
+ checkedAtMs?: number;
20
+ writeAttempted?: boolean;
21
+ readbackMatches?: boolean;
22
+ };
23
+ };
24
+ export declare const supportsColourProfileEEPROM: (hardware: {
25
+ typeCode: string | null;
26
+ firmwareVersion: string | null;
27
+ }) => boolean;
28
+ /** Format 1: version(2), R/G/B air excursions(6 each), coordinate checksum(12). Big endian. */
29
+ export declare const encodeColourProfileEEPROM: (identity: ColourProfileIdentity, signal: ColourSignalProfile) => string;
30
+ export declare const decodeColourProfileEEPROM: (hex: string, identity: ColourProfileIdentity) => {
31
+ status: "verified";
32
+ signal: ColourSignalProfile;
33
+ } | {
34
+ status: "empty" | "invalid";
35
+ reason: string;
36
+ };
@@ -0,0 +1,88 @@
1
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
2
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
3
+ if (ar || !(i in from)) {
4
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
5
+ ar[i] = from[i];
6
+ }
7
+ }
8
+ return to.concat(ar || Array.prototype.slice.call(from));
9
+ };
10
+ // The documented four-byte gap between air offsets and the white reference.
11
+ export var COLOUR_PROFILE_EEPROM_ADDRESS = "401c";
12
+ export var COLOUR_PROFILE_EEPROM_FORMAT = 1;
13
+ var RAW_NOISE_MARGIN = 3;
14
+ var CHANNELS = ["red", "green", "blue"];
15
+ // 1.4.1 retains its existing calibration path, but its source has not been audited for this new allocation.
16
+ export var supportsColourProfileEEPROM = function (hardware) {
17
+ return (hardware.typeCode === "97" && hardware.firmwareVersion === "1.3.1") ||
18
+ (hardware.typeCode === "98" && hardware.firmwareVersion === "1.4.0");
19
+ };
20
+ var coordinates = function (identity) {
21
+ if (!supportsColourProfileEEPROM(identity) || !/^[a-zA-Z0-9_-]{1,128}$/.test(identity.serialNumber) ||
22
+ !/^[a-f0-9]{8}$/i.test(identity.offsetsHex) || !/^[a-f0-9]{16}$/i.test(identity.whiteCalibrationHex)) {
23
+ throw new Error("unsupportedProfileCoordinates");
24
+ }
25
+ var offsets = CHANNELS.map(function (_, i) { return parseInt(identity.offsetsHex.slice((i + 1) * 2, (i + 2) * 2), 16); });
26
+ var white = CHANNELS.map(function (_, i) { return parseInt(identity.whiteCalibrationHex.slice((i + 1) * 4, (i + 2) * 4), 16); });
27
+ if (white.some(function (value) { return value < 10 || value > 255; }))
28
+ throw new Error("invalidWhiteReference");
29
+ return { offsets: offsets, white: white };
30
+ };
31
+ // CRC-12 polynomial 0x80f, MSB-first, initial value zero, no reflection or final XOR.
32
+ var checksum = function (payload, identity) {
33
+ var bytes = __spreadArray(__spreadArray(__spreadArray(__spreadArray([payload >>> 16, (payload >>> 8) & 255, payload & 255], Array.from((identity.offsetsHex + identity.whiteCalibrationHex).match(/../g), function (byte) { return parseInt(byte, 16); }), true), [Number(identity.typeCode)], false), identity.firmwareVersion.split(".").map(Number), true), Array.from(identity.serialNumber, function (character) { return character.charCodeAt(0); }), true);
34
+ var crc = 0;
35
+ for (var _i = 0, bytes_1 = bytes; _i < bytes_1.length; _i++) {
36
+ var byte = bytes_1[_i];
37
+ for (var bit = 7; bit >= 0; bit--) {
38
+ var feedback = ((crc >>> 11) ^ (byte >>> bit)) & 1;
39
+ crc = ((crc << 1) & 0xfff) ^ (feedback ? 0x80f : 0);
40
+ }
41
+ }
42
+ return crc;
43
+ };
44
+ /** Format 1: version(2), R/G/B air excursions(6 each), coordinate checksum(12). Big endian. */
45
+ export var encodeColourProfileEEPROM = function (identity, signal) {
46
+ var _a = coordinates(identity), offsets = _a.offsets, white = _a.white;
47
+ var excursions = CHANNELS.map(function (channel, i) {
48
+ var rawMax = signal.airMax[channel] * white[i] / 255;
49
+ var margin = signal.margin[channel] * white[i] / 255;
50
+ var excursion = Math.round(rawMax) - (identity.typeCode === "97" ? offsets[i] : 0);
51
+ // Calibration's steadiness check guarantees this range; never silently quantise arbitrary legacy profiles.
52
+ if (!Number.isFinite(rawMax) || Math.abs(rawMax - Math.round(rawMax)) > 1e-7 ||
53
+ !Number.isFinite(margin) || Math.abs(margin - RAW_NOISE_MARGIN) > 1e-7 ||
54
+ excursion < 0 || excursion > 63 || excursion + offsets[i] >= 255 || excursion > Math.max(4, offsets[i] * 0.15)) {
55
+ throw new Error("profileNotRepresentable");
56
+ }
57
+ return excursion;
58
+ });
59
+ var payload = COLOUR_PROFILE_EEPROM_FORMAT * Math.pow(2, 18) + excursions[0] * Math.pow(2, 12) + excursions[1] * Math.pow(2, 6) + excursions[2];
60
+ return (payload * Math.pow(2, 12) + checksum(payload, identity)).toString(16).padStart(8, "0");
61
+ };
62
+ export var decodeColourProfileEEPROM = function (hex, identity) {
63
+ if (!/^[a-f0-9]{8}$/i.test(hex))
64
+ return { status: "invalid", reason: "invalidLength" };
65
+ if (["00000000", "ffffffff"].includes(hex.toLowerCase()))
66
+ return { status: "empty", reason: "noProfile" };
67
+ try {
68
+ var _a = coordinates(identity), offsets_1 = _a.offsets, white_1 = _a.white;
69
+ var word = parseInt(hex, 16);
70
+ var payload = Math.floor(word / Math.pow(2, 12));
71
+ if (Math.floor(payload / Math.pow(2, 18)) !== COLOUR_PROFILE_EEPROM_FORMAT)
72
+ return { status: "invalid", reason: "unknownFormat" };
73
+ if (word % Math.pow(2, 12) !== checksum(payload, identity))
74
+ return { status: "invalid", reason: "checksumOrCalibrationMismatch" };
75
+ var excursions_1 = [Math.floor(payload / Math.pow(2, 12)) % 64, Math.floor(payload / Math.pow(2, 6)) % 64, payload % 64];
76
+ if (excursions_1.some(function (value, i) { return value + offsets_1[i] >= 255 || value > Math.max(4, offsets_1[i] * 0.15); }))
77
+ return { status: "invalid", reason: "invalidAirExcursion" };
78
+ var airMax = Object.fromEntries(CHANNELS.map(function (channel, i) {
79
+ return [channel, (excursions_1[i] + (identity.typeCode === "97" ? offsets_1[i] : 0)) * 255 / white_1[i]];
80
+ }));
81
+ var margin = Object.fromEntries(CHANNELS.map(function (channel, i) { return [channel, RAW_NOISE_MARGIN * 255 / white_1[i]]; }));
82
+ // Measured air minima and capture history remain in diagnostics; classification needs only max + margin.
83
+ return { status: "verified", signal: { airMax: airMax, margin: margin } };
84
+ }
85
+ catch (_b) {
86
+ return { status: "invalid", reason: "invalidCoordinates" };
87
+ }
88
+ };
@@ -1,6 +1,7 @@
1
1
  import colourPolicy from "./marty-colour-policy.json";
2
2
  import { ColourConfidenceFactors, ColourSignalProfile } from "./colour-classifier";
3
- import { ColourSensorProfile } from "./colour-sensor-profile";
3
+ import { ActiveColourSensorProfile } from "./colour-sensor-profile";
4
+ import { ColourProfileStorageEvidence } from "./colour-profile-eeprom";
4
5
  import type { RaftObserver } from "../application/RAFTs/RaftObserver";
5
6
  import type { RestMessageOptions } from "../types/communication-between-apps/wrapper-communication";
6
7
  import { SensorRead, RawColourFrame, ColourSensorSetup } from "./colour-sensor-registers";
@@ -94,7 +95,8 @@ export type ColourSnapshot = {
94
95
  airFlagTakesPrecedence: boolean;
95
96
  boundaries: typeof BLOCKS_COLOUR_BOUNDARIES;
96
97
  signal?: typeof colourPolicy.signal;
97
- profile?: ColourSensorProfile;
98
+ profile?: ActiveColourSensorProfile;
99
+ profileStorage?: ColourProfileStorageEvidence;
98
100
  clearBoundsUsedForClassification?: boolean;
99
101
  confidence?: typeof colourPolicy.confidence;
100
102
  };
@@ -72,7 +72,8 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
72
72
  import packageInfo from "../../package.json";
73
73
  import colourPolicy from "./marty-colour-policy.json";
74
74
  import { classifyColour, colourChannelsFromValues } from "./colour-classifier";
75
- import { getActiveColourSensorProfile } from "./colour-sensor-profile";
75
+ import { getActiveColourSensorProfile, hydrateColourSensorProfile, clearActiveColourSensorProfile } from "./colour-sensor-profile";
76
+ import { COLOUR_PROFILE_EEPROM_ADDRESS, COLOUR_PROFILE_EEPROM_FORMAT } from "./colour-profile-eeprom";
76
77
  import { lockColourSensorOperation, waitForColourProfileRead } from "./colour-sensor-operation";
77
78
  import { getPendingColourCalibration } from "./colour-calibration-reconnect";
78
79
  import { collectColourSensorSetup, readRawColourSensor } from "./colour-sensor-registers";
@@ -165,7 +166,7 @@ var delay = function (ms, signal) { return new Promise(function (resolve, reject
165
166
  abort();
166
167
  }); };
167
168
  var captureUnlocked = function (sensor, expectedColour, session, signal) { return __awaiter(void 0, void 0, void 0, function () {
168
- var startedAtMs, checkActive, sensorSetup, sampleWindowStartedAtMs, samples, rawSamples, rawController, abortRaw, rawFailure, collectRaw, rawCapture, index, reading;
169
+ var startedAtMs, checkActive, sensorSetup, profileStorage, hardware, sampleWindowStartedAtMs, samples, rawSamples, rawController, abortRaw, rawFailure, collectRaw, rawCapture, index, reading;
169
170
  var _a, _b, _c, _d, _e, _f;
170
171
  return __generator(this, function (_g) {
171
172
  switch (_g.label) {
@@ -187,6 +188,21 @@ var captureUnlocked = function (sensor, expectedColour, session, signal) { retur
187
188
  throw new ColourDiagnosticError("sensorPowerFailed");
188
189
  if (sensorSetup.hardware.status === "ok" && sensorSetup.hardware.value.commsOk === 0)
189
190
  throw new ColourDiagnosticError("sensorOffline");
191
+ profileStorage = { source: "defaultRules", eeprom: {
192
+ status: "notChecked", address: COLOUR_PROFILE_EEPROM_ADDRESS, formatVersion: COLOUR_PROFILE_EEPROM_FORMAT,
193
+ reason: "sensorDetailsUnavailable",
194
+ } };
195
+ clearActiveColourSensorProfile(sensor);
196
+ if (!(sensorSetup.hardware.status === "ok" && sensorSetup.offsets.status === "ok" && sensorSetup.whiteCalibration.status === "ok")) return [3 /*break*/, 3];
197
+ hardware = sensorSetup.hardware.value;
198
+ if (!(hardware.serialNumber && hardware.typeCode && hardware.firmwareVersion)) return [3 /*break*/, 3];
199
+ return [4 /*yield*/, hydrateColourSensorProfile(sensor, { serialNumber: hardware.serialNumber,
200
+ typeCode: hardware.typeCode, firmwareVersion: hardware.firmwareVersion,
201
+ offsetsHex: sensorSetup.offsets.value.hexRd, whiteCalibrationHex: sensorSetup.whiteCalibration.value.hexRd }, signal, checkActive)];
202
+ case 2:
203
+ profileStorage = _g.sent();
204
+ _g.label = 3;
205
+ case 3:
190
206
  readColourSensor(sensor);
191
207
  sampleWindowStartedAtMs = Date.now();
192
208
  samples = [];
@@ -228,42 +244,42 @@ var captureUnlocked = function (sensor, expectedColour, session, signal) { retur
228
244
  });
229
245
  }); };
230
246
  rawCapture = collectRaw();
231
- _g.label = 2;
232
- case 2:
233
- _g.trys.push([2, , 7, 9]);
247
+ _g.label = 4;
248
+ case 4:
249
+ _g.trys.push([4, , 9, 11]);
234
250
  index = 0;
235
- _g.label = 3;
236
- case 3:
237
- if (!(index <= CAPTURE_DURATION_MS / CAPTURE_INTERVAL_MS)) return [3 /*break*/, 6];
251
+ _g.label = 5;
252
+ case 5:
253
+ if (!(index <= CAPTURE_DURATION_MS / CAPTURE_INTERVAL_MS)) return [3 /*break*/, 8];
238
254
  checkActive();
239
255
  if (rawFailure)
240
256
  throw rawFailure;
241
257
  reading = readColourSensor(sensor);
242
258
  if (!samples.length || samples[samples.length - 1].receivedAtMs !== reading.receivedAtMs)
243
259
  samples.push(reading);
244
- if (!(index < CAPTURE_DURATION_MS / CAPTURE_INTERVAL_MS)) return [3 /*break*/, 5];
260
+ if (!(index < CAPTURE_DURATION_MS / CAPTURE_INTERVAL_MS)) return [3 /*break*/, 7];
245
261
  return [4 /*yield*/, delay(CAPTURE_INTERVAL_MS, signal)];
246
- case 4:
262
+ case 6:
247
263
  _g.sent();
248
- _g.label = 5;
249
- case 5:
250
- index++;
251
- return [3 /*break*/, 3];
252
- case 6: return [3 /*break*/, 9];
264
+ _g.label = 7;
253
265
  case 7:
266
+ index++;
267
+ return [3 /*break*/, 5];
268
+ case 8: return [3 /*break*/, 11];
269
+ case 9:
254
270
  rawController.abort();
255
271
  signal.removeEventListener("abort", abortRaw);
256
272
  return [4 /*yield*/, rawCapture];
257
- case 8:
273
+ case 10:
258
274
  _g.sent();
259
275
  return [7 /*endfinally*/];
260
- case 9:
276
+ case 11:
261
277
  if (rawFailure)
262
278
  throw rawFailure;
263
279
  if (samples.length < 2)
264
280
  throw new ColourDiagnosticError("staleReading");
265
281
  return [2 /*return*/, __assign(__assign({ schemaVersion: 2, sessionId: session.sessionId, sessionStartedAtMs: session.startedAtMs, captureId: createId(), expectedColour: expectedColour, startedAtMs: startedAtMs, finishedAtMs: Date.now(), appVersion: packageInfo.version, classifier: { source: colourPolicy.source, airFlagTakesPrecedence: false, boundaries: BLOCKS_COLOUR_BOUNDARIES,
266
- signal: colourPolicy.signal, profile: getActiveColourSensorProfile(sensor), clearBoundsUsedForClassification: false,
282
+ signal: colourPolicy.signal, profile: getActiveColourSensorProfile(sensor), profileStorage: profileStorage, clearBoundsUsedForClassification: false,
267
283
  confidence: colourPolicy.confidence }, device: {
268
284
  id: sensor.raftId, type: sensor.raft.type, name: ((_c = (_b = sensor.raft).getFriendlyName) === null || _c === void 0 ? void 0 : _c.call(_b)) || sensor.raftId,
269
285
  firmwareVersion: ((_d = sensor.raft.systemInfo) === null || _d === void 0 ? void 0 : _d.SystemVersion) || null, connectionMethod: sensor.raft.connectionMethod || null,
@@ -1,5 +1,6 @@
1
1
  import type { ColourSensor } from "./colour-sensor-diagnostics";
2
2
  import { ColourSignalProfile } from "./colour-classifier";
3
+ import { ColourProfileIdentity, ColourProfileStorageEvidence } from "./colour-profile-eeprom";
3
4
  export type ColourSensorProfile = {
4
5
  schemaVersion: 1;
5
6
  serialNumber: string;
@@ -12,21 +13,26 @@ export type ColourSensorProfile = {
12
13
  purpleIncluded: boolean;
13
14
  groundDetection: "clearThreshold" | "rgbFallback" | "purpleSkipped";
14
15
  };
16
+ export type ActiveColourSensorProfile = Omit<ColourSensorProfile, "capturedAtMs" | "purpleIncluded" | "groundDetection"> & Partial<Pick<ColourSensorProfile, "capturedAtMs" | "purpleIncluded" | "groundDetection">>;
15
17
  export declare const validColourSensorProfile: (value: unknown) => value is ColourSensorProfile;
16
18
  export declare const cacheColourSensorProfile: (profile: ColourSensorProfile) => void;
17
19
  export declare const colourSensorProfilePath: (profile: ColourSensorProfile) => string;
18
- export declare const bindColourSensorProfile: (sensor: ColourSensor, profile: ColourSensorProfile) => void;
19
- export declare const getActiveColourSensorProfile: (sensor: Pick<ColourSensor, "raft" | "key">) => ColourSensorProfile | undefined;
20
+ export declare const bindColourSensorProfile: (sensor: ColourSensor, profile: ActiveColourSensorProfile) => void;
21
+ export declare const getActiveColourSensorProfile: (sensor: Pick<ColourSensor, "raft" | "key">) => ActiveColourSensorProfile | undefined;
20
22
  export declare const getAddonColourSensorProfile: (raft: object, addon: {
21
23
  id: number;
22
24
  name: string;
23
- }) => ColourSensorProfile | undefined;
24
- export declare const clearActiveColourSensorProfile: (sensor: Pick<ColourSensor, "raft" | "key">) => boolean | undefined;
25
+ }) => ActiveColourSensorProfile | undefined;
26
+ export declare const getColourProfileStorage: (sensor: Pick<ColourSensor, "raft" | "key">) => ColourProfileStorageEvidence | undefined;
27
+ export declare const setColourProfileStorage: (sensor: Pick<ColourSensor, "raft" | "key">, evidence: ColourProfileStorageEvidence) => void;
28
+ export declare const clearActiveColourSensorProfile: (sensor: Pick<ColourSensor, "raft" | "key">) => void;
25
29
  export declare const classifyColourAddon: (addon: {
26
30
  id: number;
27
31
  name: string;
28
32
  vals: Record<string, number | boolean>;
29
33
  }) => import("./colour-classifier").ColourName;
34
+ /** Caller owns the raw-command lock and has freshly read hardware, offsets and white references. */
35
+ export declare const hydrateColourSensorProfile: (sensor: ColourSensor, identity: ColourProfileIdentity, signal: AbortSignal, check: () => void) => Promise<ColourProfileStorageEvidence>;
30
36
  export declare const loadColourSensorProfile: (sensor: ColourSensor, signal: AbortSignal) => Promise<void>;
31
37
  /** Read-only browser/WebView hydration. Never wakes sleeping sensors or modifies their registers. */
32
38
  export declare const startColourSensorProfileSync: () => () => void;
@@ -1,3 +1,14 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
1
12
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
13
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
14
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -39,9 +50,11 @@ import { classifyColour, colourChannelsFromValues } from "./colour-classifier";
39
50
  import { lockColourSensorOperation } from "./colour-sensor-operation";
40
51
  import { readColourSensorHardware, readColourSensorReadiness, readColourSensorRegister } from "./colour-sensor-registers";
41
52
  import { getPendingColourCalibration } from "./colour-calibration-reconnect";
53
+ import { COLOUR_PROFILE_EEPROM_ADDRESS, COLOUR_PROFILE_EEPROM_FORMAT, decodeColourProfileEEPROM, supportsColourProfileEEPROM } from "./colour-profile-eeprom";
42
54
  var PREFIX = "marty-colour-signal-profile:";
43
55
  var memory = new Map();
44
56
  var active = new WeakMap();
57
+ var storage = new WeakMap();
45
58
  var retries = new WeakMap();
46
59
  var profileKey = function (profile) {
47
60
  return "".concat(profile.serialNumber, "/").concat(profile.offsetsHex).concat(profile.whiteCalibrationHex);
@@ -57,7 +70,7 @@ export var validColourSensorProfile = function (value) {
57
70
  var _a, _b, _c, _d, _e, _f;
58
71
  var c = channel;
59
72
  return [(_b = (_a = profile.signal) === null || _a === void 0 ? void 0 : _a.airMin) === null || _b === void 0 ? void 0 : _b[c], (_d = (_c = profile.signal) === null || _c === void 0 ? void 0 : _c.airMax) === null || _d === void 0 ? void 0 : _d[c], (_f = (_e = profile.signal) === null || _e === void 0 ? void 0 : _e.margin) === null || _f === void 0 ? void 0 : _f[c]]
60
- .every(function (v) { return Number.isFinite(v) && v >= 0; }) && profile.signal.airMin[c] <= profile.signal.airMax[c] && profile.signal.margin[c] > 0;
73
+ .every(function (v) { return typeof v === "number" && Number.isFinite(v) && v >= 0; }) && profile.signal.airMin[c] <= profile.signal.airMax[c] && profile.signal.margin[c] > 0;
61
74
  });
62
75
  };
63
76
  export var cacheColourSensorProfile = function (profile) {
@@ -82,7 +95,20 @@ export var bindColourSensorProfile = function (sensor, profile) {
82
95
  };
83
96
  export var getActiveColourSensorProfile = function (sensor) { var _a; return (_a = active.get(sensor.raft)) === null || _a === void 0 ? void 0 : _a.get(sensor.key); };
84
97
  export var getAddonColourSensorProfile = function (raft, addon) { var _a; return (_a = active.get(raft)) === null || _a === void 0 ? void 0 : _a.get(JSON.stringify([addon.id, addon.name])); };
85
- export var clearActiveColourSensorProfile = function (sensor) { var _a; return (_a = active.get(sensor.raft)) === null || _a === void 0 ? void 0 : _a.delete(sensor.key); };
98
+ export var getColourProfileStorage = function (sensor) { var _a; return (_a = storage.get(sensor.raft)) === null || _a === void 0 ? void 0 : _a.get(sensor.key); };
99
+ export var setColourProfileStorage = function (sensor, evidence) {
100
+ var entries = storage.get(sensor.raft);
101
+ if (!entries) {
102
+ entries = new Map();
103
+ storage.set(sensor.raft, entries);
104
+ }
105
+ entries.set(sensor.key, evidence);
106
+ };
107
+ export var clearActiveColourSensorProfile = function (sensor) {
108
+ var _a, _b;
109
+ (_a = active.get(sensor.raft)) === null || _a === void 0 ? void 0 : _a.delete(sensor.key);
110
+ (_b = storage.get(sensor.raft)) === null || _b === void 0 ? void 0 : _b.delete(sensor.key);
111
+ };
86
112
  export var classifyColourAddon = function (addon) {
87
113
  var _a;
88
114
  var raft = Object.values(window.applicationManager.connectedRafts).find(function (candidate) {
@@ -91,96 +117,156 @@ export var classifyColourAddon = function (addon) {
91
117
  });
92
118
  return classifyColour(colourChannelsFromValues(addon.vals), raft ? (_a = getAddonColourSensorProfile(raft, addon)) === null || _a === void 0 ? void 0 : _a.signal : undefined).classifiedColour;
93
119
  };
94
- // Check physical identity and the complete saved coordinate system before using a persisted profile.
95
- export var loadColourSensorProfile = function (sensor, signal) { return __awaiter(void 0, void 0, void 0, function () {
96
- var release, check, readiness, hardware, white, offsets, identity, key_1, profile, stored, controller_1, abort, timeout, response, records, _a, state, current;
120
+ /** Caller owns the raw-command lock and has freshly read hardware, offsets and white references. */
121
+ export var hydrateColourSensorProfile = function (sensor, identity, signal, check) { return __awaiter(void 0, void 0, void 0, function () {
122
+ var evidence, profile, result, decoded, key_1, cached, stored, controller_1, abort, timeout, response, records, _a, hardware, unavailable, state, current;
97
123
  var _b;
98
124
  return __generator(this, function (_c) {
99
125
  switch (_c.label) {
100
126
  case 0:
101
- release = lockColourSensorOperation(sensor.raft, true);
102
- if (!release)
103
- return [2 /*return*/];
104
- check = function () { return assertColourSensorActive(sensor, signal); };
105
- _c.label = 1;
106
- case 1:
107
- _c.trys.push([1, , 13, 14]);
108
127
  check();
109
128
  clearActiveColourSensorProfile(sensor);
110
- return [4 /*yield*/, readColourSensorReadiness(sensor, signal, check)];
129
+ evidence = { source: "defaultRules", eeprom: {
130
+ status: "firmwareNotAudited", address: COLOUR_PROFILE_EEPROM_ADDRESS, formatVersion: COLOUR_PROFILE_EEPROM_FORMAT,
131
+ } };
132
+ if (!supportsColourProfileEEPROM(identity)) return [3 /*break*/, 2];
133
+ return [4 /*yield*/, readColourSensorRegister(sensor, COLOUR_PROFILE_EEPROM_ADDRESS, signal, check)];
134
+ case 1:
135
+ result = _c.sent();
136
+ evidence.eeprom.checkedAtMs = result.receivedAtMs;
137
+ if (result.status === "ok") {
138
+ decoded = decodeColourProfileEEPROM(result.value, identity);
139
+ evidence.eeprom = __assign(__assign(__assign({}, evidence.eeprom), { status: decoded.status, hex: result.value }), (decoded.status === "verified" ? { readbackMatches: true } : { reason: decoded.reason }));
140
+ if (decoded.status === "verified") {
141
+ profile = __assign(__assign({ schemaVersion: 1 }, identity), { signal: decoded.signal });
142
+ evidence.source = "eeprom";
143
+ }
144
+ }
145
+ else {
146
+ evidence.eeprom.status = "readFailed";
147
+ evidence.eeprom.reason = result.reason;
148
+ }
149
+ _c.label = 2;
111
150
  case 2:
112
- readiness = _c.sent();
113
- if (readiness.status !== "ok" || !readiness.value)
114
- return [2 /*return*/];
115
- return [4 /*yield*/, readColourSensorHardware(sensor, signal, check)];
116
- case 3:
117
- hardware = _c.sent();
118
- if (hardware.status !== "ok" || hardware.value.commsOk === 0 || !hardware.value.serialNumber ||
119
- !/^[a-zA-Z0-9_-]{1,128}$/.test(hardware.value.serialNumber))
120
- return [2 /*return*/];
121
- return [4 /*yield*/, readColourSensorRegister(sensor, "4020", signal, check)];
122
- case 4:
123
- white = _c.sent();
124
- if (white.status !== "ok")
125
- return [2 /*return*/];
126
- return [4 /*yield*/, readColourSensorRegister(sensor, "4018", signal, check)];
127
- case 5:
128
- offsets = _c.sent();
129
- if (offsets.status !== "ok")
130
- return [2 /*return*/];
131
- identity = { serialNumber: hardware.value.serialNumber, offsetsHex: offsets.value, whiteCalibrationHex: white.value };
151
+ if (!!profile) return [3 /*break*/, 10];
132
152
  key_1 = profileKey(identity);
133
- profile = memory.get(key_1);
153
+ cached = memory.get(key_1);
134
154
  try {
135
155
  stored = JSON.parse(localStorage.getItem(PREFIX + key_1) || "null");
136
- if (validColourSensorProfile(stored))
137
- profile = stored;
156
+ if (validColourSensorProfile(stored) && profileKey(stored) === key_1)
157
+ cached = stored;
138
158
  }
139
159
  catch ( /* Native WebViews may have temporary or unavailable storage. */_d) { /* Native WebViews may have temporary or unavailable storage. */ }
140
- if (!!profile) return [3 /*break*/, 12];
160
+ if (!(cached && profileKey(cached) === key_1 && cached.typeCode === identity.typeCode && cached.firmwareVersion === identity.firmwareVersion)) return [3 /*break*/, 3];
161
+ profile = cached;
162
+ evidence.source = "local";
163
+ return [3 /*break*/, 10];
164
+ case 3:
141
165
  controller_1 = new AbortController();
142
166
  abort = function () { return controller_1.abort(); };
143
167
  signal.addEventListener("abort", abort, { once: true });
168
+ if (signal.aborted)
169
+ abort();
144
170
  timeout = setTimeout(abort, 5000);
145
- _c.label = 6;
146
- case 6:
147
- _c.trys.push([6, 10, 11, 12]);
171
+ _c.label = 4;
172
+ case 4:
173
+ _c.trys.push([4, 8, 9, 10]);
148
174
  return [4 /*yield*/, fetch("".concat(COLOUR_DATABASE_URL, "/colourSensorProfiles/").concat(key_1, ".json"), { signal: controller_1.signal })];
149
- case 7:
175
+ case 5:
150
176
  response = _c.sent();
151
- if (!response.ok) return [3 /*break*/, 9];
177
+ if (!response.ok) return [3 /*break*/, 7];
152
178
  return [4 /*yield*/, response.json()];
153
- case 8:
179
+ case 6:
154
180
  records = _c.sent();
155
181
  if (records && typeof records === "object")
156
182
  profile = Object.values(records).filter(validColourSensorProfile)
157
- .filter(function (value) { return profileKey(value) === key_1; }).sort(function (a, b) { return b.capturedAtMs - a.capturedAtMs; })[0];
158
- _c.label = 9;
159
- case 9: return [3 /*break*/, 12];
160
- case 10:
183
+ .filter(function (value) { return profileKey(value) === key_1 && value.typeCode === identity.typeCode && value.firmwareVersion === identity.firmwareVersion; })
184
+ .sort(function (a, b) { return b.capturedAtMs - a.capturedAtMs; })[0];
185
+ if (profile) {
186
+ evidence.source = "firebase";
187
+ cacheColourSensorProfile(profile);
188
+ }
189
+ _c.label = 7;
190
+ case 7: return [3 /*break*/, 10];
191
+ case 8:
161
192
  _a = _c.sent();
162
- return [3 /*break*/, 12];
163
- case 11:
193
+ return [3 /*break*/, 10];
194
+ case 9:
164
195
  clearTimeout(timeout);
165
196
  signal.removeEventListener("abort", abort);
166
197
  return [7 /*endfinally*/];
167
- case 12:
198
+ case 10:
168
199
  check();
200
+ return [4 /*yield*/, readColourSensorHardware(sensor, signal, check)];
201
+ case 11:
202
+ hardware = _c.sent();
203
+ if (hardware.status !== "ok" || hardware.value.commsOk === 0 || hardware.value.serialNumber !== identity.serialNumber ||
204
+ hardware.value.typeCode !== identity.typeCode || hardware.value.firmwareVersion !== identity.firmwareVersion) {
205
+ unavailable = __assign(__assign({}, evidence), { source: "defaultRules", eeprom: __assign(__assign({}, evidence.eeprom), { status: "notChecked", reason: "sensorIdentityChangedOrUnavailable", readbackMatches: false }) });
206
+ setColourProfileStorage(sensor, unavailable);
207
+ return [2 /*return*/, unavailable];
208
+ }
169
209
  state = sensor.raft.raftStateInfo;
170
210
  current = state.addOnInfo.addons.find(function (addon) { return addon.id === sensor.addon.id && addon.name === sensor.addon.name; });
171
211
  if (!current || (current.status !== undefined && !(current.status & 128)) ||
172
- Date.now() - state.addOnInfoValidMs > 1500 || state.addOnInfoValidMs > Date.now() || ((_b = state.power) === null || _b === void 0 ? void 0 : _b.powerStatus.power5VIsOn) === false)
173
- return [2 /*return*/];
174
- if (profile && profileKey(profile) === key_1 && profile.typeCode === hardware.value.typeCode &&
175
- profile.firmwareVersion === hardware.value.firmwareVersion) {
176
- cacheColourSensorProfile(profile);
177
- bindColourSensorProfile(sensor, profile);
212
+ Date.now() - state.addOnInfoValidMs > 1500 || state.addOnInfoValidMs > Date.now() || ((_b = state.power) === null || _b === void 0 ? void 0 : _b.powerStatus.power5VIsOn) === false) {
213
+ return [2 /*return*/, __assign(__assign({}, evidence), { source: "defaultRules", eeprom: __assign(__assign({}, evidence.eeprom), { status: "notChecked", reason: "sensorNotFresh" }) })];
178
214
  }
179
- return [3 /*break*/, 14];
180
- case 13:
215
+ if (profile)
216
+ bindColourSensorProfile(sensor, profile);
217
+ setColourProfileStorage(sensor, evidence);
218
+ return [2 /*return*/, evidence];
219
+ }
220
+ });
221
+ }); };
222
+ // Check physical identity and the complete saved coordinate system before using a persisted profile.
223
+ export var loadColourSensorProfile = function (sensor, signal) { return __awaiter(void 0, void 0, void 0, function () {
224
+ var release, check, readiness, hardware, white, offsets;
225
+ return __generator(this, function (_a) {
226
+ switch (_a.label) {
227
+ case 0:
228
+ release = lockColourSensorOperation(sensor.raft, true);
229
+ if (!release)
230
+ return [2 /*return*/];
231
+ check = function () { return assertColourSensorActive(sensor, signal); };
232
+ _a.label = 1;
233
+ case 1:
234
+ _a.trys.push([1, , 7, 8]);
235
+ check();
236
+ clearActiveColourSensorProfile(sensor);
237
+ return [4 /*yield*/, readColourSensorReadiness(sensor, signal, check)];
238
+ case 2:
239
+ readiness = _a.sent();
240
+ if (readiness.status !== "ok" || !readiness.value)
241
+ return [2 /*return*/];
242
+ return [4 /*yield*/, readColourSensorHardware(sensor, signal, check)];
243
+ case 3:
244
+ hardware = _a.sent();
245
+ if (hardware.status !== "ok" || hardware.value.commsOk === 0 || !hardware.value.serialNumber ||
246
+ !/^[a-zA-Z0-9_-]{1,128}$/.test(hardware.value.serialNumber))
247
+ return [2 /*return*/];
248
+ return [4 /*yield*/, readColourSensorRegister(sensor, "4020", signal, check)];
249
+ case 4:
250
+ white = _a.sent();
251
+ if (white.status !== "ok")
252
+ return [2 /*return*/];
253
+ return [4 /*yield*/, readColourSensorRegister(sensor, "4018", signal, check)];
254
+ case 5:
255
+ offsets = _a.sent();
256
+ if (offsets.status !== "ok")
257
+ return [2 /*return*/];
258
+ if (!hardware.value.typeCode || !hardware.value.firmwareVersion)
259
+ return [2 /*return*/];
260
+ return [4 /*yield*/, hydrateColourSensorProfile(sensor, { serialNumber: hardware.value.serialNumber,
261
+ typeCode: hardware.value.typeCode, firmwareVersion: hardware.value.firmwareVersion,
262
+ offsetsHex: offsets.value, whiteCalibrationHex: white.value }, signal, check)];
263
+ case 6:
264
+ _a.sent();
265
+ return [3 /*break*/, 8];
266
+ case 7:
181
267
  release();
182
268
  return [7 /*endfinally*/];
183
- case 14: return [2 /*return*/];
269
+ case 8: return [2 /*return*/];
184
270
  }
185
271
  });
186
272
  }); };
@@ -43,7 +43,7 @@ export type ColourSensorSetup = {
43
43
  offsets: SensorRead<Channels>;
44
44
  whiteCalibration: SensorRead<Channels>;
45
45
  };
46
- export type ColourSensorRegister = "4014" | "4018" | "4020";
46
+ export type ColourSensorRegister = "4014" | "4018" | "401c" | "4020";
47
47
  export declare const readColourSensorRegister: (sensor: ColourSensor, address: ColourSensorRegister, signal: AbortSignal, checkActive: () => void) => Promise<SensorRead<string>>;
48
48
  export declare const readColourSensorHardware: (sensor: ColourSensor, signal: AbortSignal, checkActive: () => void) => Promise<SensorRead<SensorHardware>>;
49
49
  export declare const wakeColourSensor: (sensor: ColourSensor, signal: AbortSignal, checkActive: () => void) => Promise<SensorRead<boolean>>;
@@ -138,7 +138,7 @@ var readHex = function (sensor, hexWr, bytes, signal, checkActive) {
138
138
  });
139
139
  };
140
140
  export var readColourSensorRegister = function (sensor, address, signal, checkActive) {
141
- return readHex(sensor, "10".concat(address), { "4014": 2, "4018": 4, "4020": 8 }[address], signal, checkActive);
141
+ return readHex(sensor, "10".concat(address), { "4014": 2, "4018": 4, "401c": 4, "4020": 8 }[address], signal, checkActive);
142
142
  };
143
143
  var mapRead = function (result, convert) {
144
144
  return result.status === "ok" ? __assign(__assign({}, result), { value: convert(result.value) }) : result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robotical/webapp-types",
3
- "version": "3.25.11",
3
+ "version": "3.25.13",
4
4
  "description": "Type definitions for the Application Manager",
5
5
  "main": "dist/application-manager.d.ts",
6
6
  "types": "dist/application-manager.d.ts",