@robotical/webapp-types 3.25.10 → 3.25.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-types/package.json +1 -1
- package/dist-types/src/application/RAFTs/Marty/PublishedDataAnalyser.d.ts +11 -3
- package/dist-types/src/application/RAFTs/Marty/PublishedDataAnalyser.js +76 -7
- package/dist-types/src/utils/colour-classifier.d.ts +1 -1
- package/dist-types/src/utils/colour-classifier.js +1 -1
- package/dist-types/src/utils/colour-event-filter.d.ts +24 -0
- package/dist-types/src/utils/colour-event-filter.js +88 -0
- package/package.json +1 -1
package/dist-types/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import EventEmitter from "events";
|
|
3
|
-
import { ColourSignalProfile } from "../../../utils/colour-classifier";
|
|
3
|
+
import { classifyColour, ColourSignalProfile } from "../../../utils/colour-classifier";
|
|
4
4
|
import RAFT from "../RAFT";
|
|
5
5
|
import type { RICStateInfo } from "@robotical/roboticaljs";
|
|
6
6
|
import { type ColourSensorValues } from "./published-data-helpers";
|
|
7
7
|
interface MartyState {
|
|
8
8
|
colourSensed: "blue" | "green" | "red" | "yellow" | "purple" | "unclear" | "air";
|
|
9
|
-
/** Rule strength (0–100)
|
|
9
|
+
/** Rule strength of the confirmed/held colour (0–100); null without a reading. */
|
|
10
10
|
colourConfidence: number | null;
|
|
11
11
|
lightSense: "none" | "high" | "mid" | "low";
|
|
12
12
|
objectSense: "none" | "near";
|
|
@@ -16,6 +16,11 @@ declare class PublishedDataAnalyser extends EventEmitter {
|
|
|
16
16
|
private marty;
|
|
17
17
|
martyState: MartyState;
|
|
18
18
|
private pubSub;
|
|
19
|
+
lastColourReading: ReturnType<typeof classifyColour> | null;
|
|
20
|
+
private colourFilter;
|
|
21
|
+
private colourContext;
|
|
22
|
+
private colourExpiry;
|
|
23
|
+
private connectionObserver;
|
|
19
24
|
lightSenseDetection: LightSenseDetection;
|
|
20
25
|
colourSensedDetection: ColourSensedDetection;
|
|
21
26
|
objectSenseDetection: ObjectSenseDetection;
|
|
@@ -37,12 +42,15 @@ declare class PublishedDataAnalyser extends EventEmitter {
|
|
|
37
42
|
unsubscribeFromPublishedData(): void;
|
|
38
43
|
analyse(data: RICStateInfo): void;
|
|
39
44
|
setColourSensed(colour: MartyState["colourSensed"], confidence?: number | null): void;
|
|
45
|
+
private resetColourDetection;
|
|
46
|
+
recordColourReading(reading: ReturnType<typeof classifyColour>, receivedAtMs: number): void;
|
|
47
|
+
private scheduleColourExpiry;
|
|
40
48
|
setLightSense(lightSense: MartyState["lightSense"]): void;
|
|
41
49
|
setObjectSense(objectSense: MartyState["objectSense"]): void;
|
|
42
50
|
setNoiseSense(noiseSense: MartyState["noiseSense"]): void;
|
|
43
51
|
}
|
|
44
52
|
declare class ColourSensedDetection {
|
|
45
|
-
detectColourSensed({ r, g, b, clear, air }: ColourSensorValues, analyser: PublishedDataAnalyser, profile?: ColourSignalProfile): void;
|
|
53
|
+
detectColourSensed({ r, g, b, clear, air }: ColourSensorValues, analyser: PublishedDataAnalyser, profile?: ColourSignalProfile, receivedAtMs?: number): void;
|
|
46
54
|
}
|
|
47
55
|
declare class LightSenseDetection {
|
|
48
56
|
private lowLightThreshold;
|
|
@@ -14,15 +14,29 @@ var __extends = (this && this.__extends) || (function () {
|
|
|
14
14
|
};
|
|
15
15
|
})();
|
|
16
16
|
import EventEmitter from "events";
|
|
17
|
-
import {
|
|
17
|
+
import { RaftConnEvent } from "@robdobsn/raftjs";
|
|
18
|
+
import { classifyColour, colourChannelsFromValues } from "../../../utils/colour-classifier";
|
|
19
|
+
import { ColourEventFilter } from "../../../utils/colour-event-filter";
|
|
18
20
|
import { getAddonColourSensorProfile } from "../../../utils/colour-sensor-profile";
|
|
19
21
|
import { createRaftPubSubscriptionHelper } from "../raft-subscription-helpers";
|
|
20
|
-
import { getColourSensor, getLightSensor, getObjectSensor, getNoiseSensor,
|
|
22
|
+
import { getColourSensor, getLightSensor, getObjectSensor, getNoiseSensor, getLightSenseValues, getObjectIsTouchingFromVals, getNoiseSenseValue, } from "./published-data-helpers";
|
|
21
23
|
var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
22
24
|
__extends(PublishedDataAnalyser, _super);
|
|
23
25
|
function PublishedDataAnalyser(marty) {
|
|
24
26
|
var _this = _super.call(this) || this;
|
|
25
27
|
_this.marty = marty;
|
|
28
|
+
_this.lastColourReading = null;
|
|
29
|
+
_this.colourFilter = new ColourEventFilter();
|
|
30
|
+
_this.colourContext = "";
|
|
31
|
+
_this.colourExpiry = null;
|
|
32
|
+
_this.connectionObserver = {
|
|
33
|
+
notify: function (eventType, eventEnum) {
|
|
34
|
+
if (eventType === "conn" && [RaftConnEvent.CONN_DISCONNECTED, RaftConnEvent.CONN_REJECTED,
|
|
35
|
+
RaftConnEvent.CONN_VERIFYING_CORRECT, RaftConnEvent.CONN_VERIFIED_CORRECT].includes(eventEnum)) {
|
|
36
|
+
_this.resetColourDetection();
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
26
40
|
_this.lightSenseDetection = new LightSenseDetection();
|
|
27
41
|
_this.colourSensedDetection = new ColourSensedDetection();
|
|
28
42
|
_this.objectSenseDetection = new ObjectSenseDetection();
|
|
@@ -72,6 +86,9 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
|
72
86
|
}
|
|
73
87
|
PublishedDataAnalyser.prototype.subscribeToPublishedData = function () {
|
|
74
88
|
var _this = this;
|
|
89
|
+
var _a;
|
|
90
|
+
(_a = this.pubSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
91
|
+
this.marty.subscribe(this.connectionObserver, ["conn"]);
|
|
75
92
|
this.pubSub = createRaftPubSubscriptionHelper(this.marty, this.marty.id);
|
|
76
93
|
this.pubSub.subscribe(function (data) {
|
|
77
94
|
_this.analyse(data.stateInfo);
|
|
@@ -80,6 +97,9 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
|
80
97
|
PublishedDataAnalyser.prototype.unsubscribeFromPublishedData = function () {
|
|
81
98
|
var _a;
|
|
82
99
|
(_a = this.pubSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
100
|
+
this.pubSub = null;
|
|
101
|
+
this.marty.unsubscribe(this.connectionObserver);
|
|
102
|
+
this.resetColourDetection();
|
|
83
103
|
};
|
|
84
104
|
PublishedDataAnalyser.prototype.analyse = function (data) {
|
|
85
105
|
var _a;
|
|
@@ -89,8 +109,23 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
|
89
109
|
var lightSensor = getLightSensor(data);
|
|
90
110
|
var objectSensor = getObjectSensor(data);
|
|
91
111
|
var noiseSensor = getNoiseSensor(data);
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
var powerAge = Date.now() - data.powerValidMs;
|
|
113
|
+
if (!colourSensor || this.marty.isVerified === false || document.hidden ||
|
|
114
|
+
(colourSensor.status !== undefined && !(colourSensor.status & 0x80)) ||
|
|
115
|
+
(data.powerValidMs > 0 && powerAge >= 0 && powerAge < 3000 && ((_a = data.power) === null || _a === void 0 ? void 0 : _a.powerStatus.power5VIsOn) === false)) {
|
|
116
|
+
this.resetColourDetection();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
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]);
|
|
121
|
+
if (context !== this.colourContext) {
|
|
122
|
+
this.resetColourDetection();
|
|
123
|
+
this.colourContext = context;
|
|
124
|
+
}
|
|
125
|
+
var values = colourChannelsFromValues(colourSensor.vals);
|
|
126
|
+
this.colourSensedDetection.detectColourSensed({ r: values.red, g: values.green, b: values.blue,
|
|
127
|
+
clear: values.clear, air: values.air }, this, profile === null || profile === void 0 ? void 0 : profile.signal, data.addOnInfoValidMs);
|
|
128
|
+
}
|
|
94
129
|
(objectSensor || colourSensor) &&
|
|
95
130
|
this.objectSenseDetection.detectObjectSense(getObjectIsTouchingFromVals(objectSensor, colourSensor), this);
|
|
96
131
|
lightSensor &&
|
|
@@ -104,9 +139,42 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
|
104
139
|
};
|
|
105
140
|
PublishedDataAnalyser.prototype.setColourSensed = function (colour, confidence) {
|
|
106
141
|
if (confidence === void 0) { confidence = null; }
|
|
142
|
+
var changed = colour !== this.martyState.colourSensed;
|
|
107
143
|
this.martyState.colourSensed = colour;
|
|
108
144
|
this.martyState.colourConfidence = confidence;
|
|
109
|
-
|
|
145
|
+
if (changed)
|
|
146
|
+
this.emit(this.eventsMap.colourSensed[colour]);
|
|
147
|
+
};
|
|
148
|
+
PublishedDataAnalyser.prototype.resetColourDetection = function () {
|
|
149
|
+
if (this.colourExpiry !== null)
|
|
150
|
+
clearTimeout(this.colourExpiry);
|
|
151
|
+
this.colourExpiry = null;
|
|
152
|
+
this.colourContext = "";
|
|
153
|
+
this.colourFilter.reset();
|
|
154
|
+
this.lastColourReading = null;
|
|
155
|
+
this.setColourSensed("unclear", null);
|
|
156
|
+
};
|
|
157
|
+
PublishedDataAnalyser.prototype.recordColourReading = function (reading, receivedAtMs) {
|
|
158
|
+
this.lastColourReading = reading;
|
|
159
|
+
var value = this.colourFilter.update(reading, receivedAtMs, Date.now());
|
|
160
|
+
this.setColourSensed(value.colour, value.confidence);
|
|
161
|
+
this.scheduleColourExpiry();
|
|
162
|
+
};
|
|
163
|
+
PublishedDataAnalyser.prototype.scheduleColourExpiry = function () {
|
|
164
|
+
var _this = this;
|
|
165
|
+
if (this.colourExpiry !== null)
|
|
166
|
+
clearTimeout(this.colourExpiry);
|
|
167
|
+
var expiresAt = this.colourFilter.nextExpiryAtMs;
|
|
168
|
+
this.colourExpiry = expiresAt === null ? null : setTimeout(function () {
|
|
169
|
+
_this.colourExpiry = null;
|
|
170
|
+
if (_this.marty.isVerified === false || document.hidden) {
|
|
171
|
+
_this.resetColourDetection();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
var value = _this.colourFilter.current(Date.now());
|
|
175
|
+
_this.setColourSensed(value.colour, value.confidence);
|
|
176
|
+
_this.scheduleColourExpiry();
|
|
177
|
+
}, Math.max(0, expiresAt - Date.now()));
|
|
110
178
|
};
|
|
111
179
|
PublishedDataAnalyser.prototype.setLightSense = function (lightSense) {
|
|
112
180
|
this.martyState.lightSense = lightSense;
|
|
@@ -125,10 +193,11 @@ var PublishedDataAnalyser = /** @class */ (function (_super) {
|
|
|
125
193
|
var ColourSensedDetection = /** @class */ (function () {
|
|
126
194
|
function ColourSensedDetection() {
|
|
127
195
|
}
|
|
128
|
-
ColourSensedDetection.prototype.detectColourSensed = function (_a, analyser, profile) {
|
|
196
|
+
ColourSensedDetection.prototype.detectColourSensed = function (_a, analyser, profile, receivedAtMs) {
|
|
129
197
|
var r = _a.r, g = _a.g, b = _a.b, clear = _a.clear, air = _a.air;
|
|
198
|
+
if (receivedAtMs === void 0) { receivedAtMs = Date.now(); }
|
|
130
199
|
var reading = classifyColour({ red: r, green: g, blue: b, clear: clear, air: air }, profile);
|
|
131
|
-
return analyser.
|
|
200
|
+
return analyser.recordColourReading(reading, receivedAtMs);
|
|
132
201
|
};
|
|
133
202
|
return ColourSensedDetection;
|
|
134
203
|
}());
|
|
@@ -33,7 +33,7 @@ export declare const classifyColour: (values: RGB & {
|
|
|
33
33
|
signalSource: string;
|
|
34
34
|
classificationReason: string;
|
|
35
35
|
};
|
|
36
|
-
export declare const colourChannelsFromValues: (vals: Record<string,
|
|
36
|
+
export declare const colourChannelsFromValues: (vals: Record<string, unknown>) => {
|
|
37
37
|
red: number;
|
|
38
38
|
green: number;
|
|
39
39
|
blue: number;
|
|
@@ -86,5 +86,5 @@ export var colourChannelsFromValues = function (vals) {
|
|
|
86
86
|
values[channel.toLowerCase()] = vals[key];
|
|
87
87
|
}
|
|
88
88
|
return { red: values.red, green: values.green, blue: values.blue,
|
|
89
|
-
clear: values.clear, air: [true, false, 0, 1].
|
|
89
|
+
clear: values.clear, air: [true, false, 0, 1].some(function (value) { return value === values.air; }) ? Boolean(values.air) : undefined };
|
|
90
90
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { classifyColour, ColourName } from "./colour-classifier";
|
|
2
|
+
export declare const COLOUR_EVENT_TIMING: {
|
|
3
|
+
readonly minimumReadings: 3;
|
|
4
|
+
readonly confirmationMs: 200;
|
|
5
|
+
readonly signalGapMs: 250;
|
|
6
|
+
readonly staleMs: 500;
|
|
7
|
+
};
|
|
8
|
+
type Reading = Pick<ReturnType<typeof classifyColour>, "classifiedColour" | "confidence" | "classificationReason">;
|
|
9
|
+
type Value = {
|
|
10
|
+
colour: ColourName;
|
|
11
|
+
confidence: number | null;
|
|
12
|
+
};
|
|
13
|
+
/** Per-sensor event confirmation. Instantaneous classification remains independent. */
|
|
14
|
+
export declare class ColourEventFilter {
|
|
15
|
+
private lastPacketAtMs;
|
|
16
|
+
private latest;
|
|
17
|
+
private confirmed;
|
|
18
|
+
private candidate;
|
|
19
|
+
reset(): void;
|
|
20
|
+
current(now: number): Value;
|
|
21
|
+
update(reading: Reading, receivedAtMs: number, now: number): Value;
|
|
22
|
+
get nextExpiryAtMs(): number | null;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export var COLOUR_EVENT_TIMING = {
|
|
2
|
+
minimumReadings: 3,
|
|
3
|
+
confirmationMs: 200,
|
|
4
|
+
signalGapMs: 250,
|
|
5
|
+
staleMs: 500,
|
|
6
|
+
};
|
|
7
|
+
var isCard = function (colour) { return colour !== "air" && colour !== "unclear"; };
|
|
8
|
+
/** Per-sensor event confirmation. Instantaneous classification remains independent. */
|
|
9
|
+
var ColourEventFilter = /** @class */ (function () {
|
|
10
|
+
function ColourEventFilter() {
|
|
11
|
+
this.lastPacketAtMs = null;
|
|
12
|
+
this.latest = null;
|
|
13
|
+
this.confirmed = null;
|
|
14
|
+
this.candidate = null;
|
|
15
|
+
}
|
|
16
|
+
ColourEventFilter.prototype.reset = function () {
|
|
17
|
+
this.lastPacketAtMs = null;
|
|
18
|
+
this.latest = this.confirmed = this.candidate = null;
|
|
19
|
+
};
|
|
20
|
+
ColourEventFilter.prototype.current = function (now) {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
if (this.lastPacketAtMs !== null && now - this.lastPacketAtMs >= COLOUR_EVENT_TIMING.staleMs) {
|
|
23
|
+
// Retain the packet watermark: repeated cached packets are still duplicates.
|
|
24
|
+
this.latest = this.confirmed = this.candidate = null;
|
|
25
|
+
}
|
|
26
|
+
if (this.confirmed && now - this.confirmed.lastAtMs >= COLOUR_EVENT_TIMING.signalGapMs)
|
|
27
|
+
this.confirmed = null;
|
|
28
|
+
if (this.candidate && now - this.candidate.lastAtMs >= COLOUR_EVENT_TIMING.signalGapMs)
|
|
29
|
+
this.candidate = null;
|
|
30
|
+
return ((_a = this.confirmed) === null || _a === void 0 ? void 0 : _a.value) || (((_b = this.latest) === null || _b === void 0 ? void 0 : _b.colour) === "air" ? this.latest :
|
|
31
|
+
{ colour: "unclear", confidence: this.latest ? 0 : null });
|
|
32
|
+
};
|
|
33
|
+
ColourEventFilter.prototype.update = function (reading, receivedAtMs, now) {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
this.current(now);
|
|
36
|
+
if (!Number.isFinite(receivedAtMs) || receivedAtMs <= 0 || receivedAtMs > now ||
|
|
37
|
+
now - receivedAtMs >= COLOUR_EVENT_TIMING.staleMs) {
|
|
38
|
+
this.reset();
|
|
39
|
+
return this.current(now);
|
|
40
|
+
}
|
|
41
|
+
// A STATE_INFO event can reuse the same add-on packet when other telemetry changes.
|
|
42
|
+
if (this.lastPacketAtMs !== null && receivedAtMs <= this.lastPacketAtMs)
|
|
43
|
+
return this.current(now);
|
|
44
|
+
this.lastPacketAtMs = receivedAtMs;
|
|
45
|
+
var value = { colour: reading.classifiedColour, confidence: reading.confidence };
|
|
46
|
+
this.latest = value;
|
|
47
|
+
if (reading.classificationReason === "invalid") {
|
|
48
|
+
this.confirmed = this.candidate = null;
|
|
49
|
+
}
|
|
50
|
+
else if (isCard(value.colour)) {
|
|
51
|
+
if (((_a = this.confirmed) === null || _a === void 0 ? void 0 : _a.value.colour) === value.colour) {
|
|
52
|
+
this.confirmed.value = value;
|
|
53
|
+
this.confirmed.lastAtMs = receivedAtMs;
|
|
54
|
+
this.candidate = null;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
if (((_b = this.candidate) === null || _b === void 0 ? void 0 : _b.value.colour) === value.colour) {
|
|
58
|
+
this.candidate.count++;
|
|
59
|
+
this.candidate.lastAtMs = receivedAtMs;
|
|
60
|
+
this.candidate.value = value;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.candidate = { value: value, firstAtMs: receivedAtMs, lastAtMs: receivedAtMs, count: 1 };
|
|
64
|
+
}
|
|
65
|
+
if (this.candidate.count >= COLOUR_EVENT_TIMING.minimumReadings &&
|
|
66
|
+
receivedAtMs - this.candidate.firstAtMs >= COLOUR_EVENT_TIMING.confirmationMs) {
|
|
67
|
+
this.confirmed = this.candidate;
|
|
68
|
+
this.candidate = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return this.current(now);
|
|
73
|
+
};
|
|
74
|
+
Object.defineProperty(ColourEventFilter.prototype, "nextExpiryAtMs", {
|
|
75
|
+
get: function () {
|
|
76
|
+
var deadlines = [
|
|
77
|
+
this.confirmed && this.confirmed.lastAtMs + COLOUR_EVENT_TIMING.signalGapMs,
|
|
78
|
+
this.candidate && this.candidate.lastAtMs + COLOUR_EVENT_TIMING.signalGapMs,
|
|
79
|
+
this.latest && this.lastPacketAtMs + COLOUR_EVENT_TIMING.staleMs,
|
|
80
|
+
].filter(function (value) { return value !== null; });
|
|
81
|
+
return deadlines.length ? Math.min.apply(Math, deadlines) : null;
|
|
82
|
+
},
|
|
83
|
+
enumerable: false,
|
|
84
|
+
configurable: true
|
|
85
|
+
});
|
|
86
|
+
return ColourEventFilter;
|
|
87
|
+
}());
|
|
88
|
+
export { ColourEventFilter };
|