@ziztechnology/dial-library 0.0.10 → 0.0.12
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/README.md +38 -23
- package/dist/index.d.mts +142 -28
- package/dist/index.mjs +841 -660
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -45,40 +45,59 @@ const dialSdkDebugWarn = (moduleName, scope, phase, data) => {
|
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region src/sensor/check_status.ts
|
|
47
47
|
const DEBUG_MODULE$5 = "check_status.ts";
|
|
48
|
+
const DEVICE_RIGHT = {
|
|
49
|
+
x: 1,
|
|
50
|
+
y: 0,
|
|
51
|
+
z: 0
|
|
52
|
+
};
|
|
53
|
+
const ZERO_VECTOR$1 = {
|
|
54
|
+
x: 0,
|
|
55
|
+
y: 0,
|
|
56
|
+
z: 0
|
|
57
|
+
};
|
|
48
58
|
const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
49
|
-
version: "driving-status-
|
|
59
|
+
version: "driving-status-calibrated-2026-07",
|
|
50
60
|
maxSampleAgeMs: 1e3,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
turnLateralThreshold: 2.5,
|
|
61
|
+
gravityFilterTimeConstantMs: 1e3,
|
|
62
|
+
biasFilterTimeConstantMs: 1e4,
|
|
63
|
+
vehicleRightMinimumNorm: .2,
|
|
55
64
|
motionWindowMs: 1e3,
|
|
56
65
|
motionMinimumSamples: 5,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
stoppedGyroThreshold: .
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
stoppedMotionThreshold: .12,
|
|
67
|
+
drivingMotionThreshold: .18,
|
|
68
|
+
stoppedGyroThreshold: .025,
|
|
69
|
+
stoppedRawGyroThreshold: .065,
|
|
70
|
+
drivingGyroThreshold: .04,
|
|
71
|
+
drivingRawGyroThreshold: .08,
|
|
72
|
+
stoppedConfirmationMs: 5e3,
|
|
73
|
+
drivingConfirmationMs: 600,
|
|
74
|
+
longitudinalWindowMs: 800,
|
|
75
|
+
longitudinalMinimumSamples: 5,
|
|
76
|
+
longitudinalThreshold: .7,
|
|
77
|
+
longitudinalExitThreshold: .35,
|
|
78
|
+
longitudinalSignCoherence: .8,
|
|
79
|
+
longitudinalReleaseMs: 500,
|
|
80
|
+
rapidLongitudinalWindowMs: 600,
|
|
81
|
+
rapidLongitudinalMinimumSamples: 4,
|
|
82
|
+
rapidLongitudinalPeakThreshold: 2.2,
|
|
83
|
+
rapidLongitudinalMeanThreshold: 1.4,
|
|
84
|
+
rapidLongitudinalSignCoherence: .75,
|
|
85
|
+
rapidMinimumDisplayMs: 500,
|
|
86
|
+
verticalBumpRatio: 1.25,
|
|
87
|
+
longitudinalReversalWindowMs: 400,
|
|
88
|
+
turnWindowMs: 1500,
|
|
89
|
+
turnMinimumSamples: 4,
|
|
90
|
+
turnYawThreshold: .12,
|
|
91
|
+
turnConfirmationMs: 600,
|
|
92
|
+
turnIntegratedAngleThreshold: .12,
|
|
93
|
+
turnSignCoherence: .7,
|
|
94
|
+
turnYawDominanceThreshold: .6,
|
|
95
|
+
turnLateralEvidenceThreshold: .25,
|
|
96
|
+
turnVerticalNoiseFloor: .1,
|
|
97
|
+
turnVerticalBumpRatio: 1.25,
|
|
98
|
+
turnExitYawThreshold: .04,
|
|
99
|
+
turnExitConfirmationMs: 700,
|
|
100
|
+
turnClosureWindowMs: 1500
|
|
82
101
|
});
|
|
83
102
|
const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
84
103
|
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "called", {
|
|
@@ -87,52 +106,95 @@ const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
|
87
106
|
});
|
|
88
107
|
const tuning = resolveDrivingStatusTuning(tuningOverrides);
|
|
89
108
|
const metrics = extractDrivingSnapshotMetrics(info, tuning);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
if (metrics === null) {
|
|
110
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result: null });
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const { candidateStatus, rejectionReason } = classifyInstantaneousEvidence(metrics, tuning);
|
|
114
|
+
const result = {
|
|
115
|
+
capturedAtMs: info.capturedAtMs,
|
|
93
116
|
metrics,
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
candidateStatus,
|
|
118
|
+
rejectionReason,
|
|
119
|
+
quality: {
|
|
120
|
+
frameValid: true,
|
|
121
|
+
maximumSampleAgeMs: Math.max(info.capturedAtMs - metrics.linearAccelerationSampledAtMs, info.capturedAtMs - metrics.gyroscopeSampledAtMs, info.capturedAtMs - metrics.gravitySampledAtMs)
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result });
|
|
96
125
|
return result;
|
|
97
126
|
};
|
|
98
|
-
const checkRunningStatus = classifyDrivingSnapshot;
|
|
99
127
|
const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
100
128
|
dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "called", { overrides });
|
|
101
129
|
const tuning = {
|
|
102
130
|
...DEFAULT_DRIVING_STATUS_TUNING,
|
|
103
|
-
...overrides
|
|
104
|
-
confirmationSamples: {
|
|
105
|
-
...DEFAULT_DRIVING_STATUS_TUNING.confirmationSamples,
|
|
106
|
-
...overrides.confirmationSamples
|
|
107
|
-
},
|
|
108
|
-
minimumDisplayMs: {
|
|
109
|
-
...DEFAULT_DRIVING_STATUS_TUNING.minimumDisplayMs,
|
|
110
|
-
...overrides.minimumDisplayMs
|
|
111
|
-
}
|
|
131
|
+
...overrides
|
|
112
132
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
for (const field of [
|
|
134
|
+
"maxSampleAgeMs",
|
|
135
|
+
"gravityFilterTimeConstantMs",
|
|
136
|
+
"biasFilterTimeConstantMs",
|
|
137
|
+
"vehicleRightMinimumNorm",
|
|
138
|
+
"motionWindowMs",
|
|
139
|
+
"stoppedMotionThreshold",
|
|
140
|
+
"drivingMotionThreshold",
|
|
141
|
+
"stoppedGyroThreshold",
|
|
142
|
+
"stoppedRawGyroThreshold",
|
|
143
|
+
"drivingGyroThreshold",
|
|
144
|
+
"drivingRawGyroThreshold",
|
|
145
|
+
"stoppedConfirmationMs",
|
|
146
|
+
"drivingConfirmationMs",
|
|
147
|
+
"longitudinalWindowMs",
|
|
148
|
+
"longitudinalThreshold",
|
|
149
|
+
"longitudinalExitThreshold",
|
|
150
|
+
"longitudinalReleaseMs",
|
|
151
|
+
"rapidLongitudinalWindowMs",
|
|
152
|
+
"rapidLongitudinalPeakThreshold",
|
|
153
|
+
"rapidLongitudinalMeanThreshold",
|
|
154
|
+
"rapidMinimumDisplayMs",
|
|
155
|
+
"verticalBumpRatio",
|
|
156
|
+
"longitudinalReversalWindowMs",
|
|
157
|
+
"turnWindowMs",
|
|
158
|
+
"turnYawThreshold",
|
|
159
|
+
"turnConfirmationMs",
|
|
160
|
+
"turnIntegratedAngleThreshold",
|
|
161
|
+
"turnLateralEvidenceThreshold",
|
|
162
|
+
"turnVerticalNoiseFloor",
|
|
163
|
+
"turnVerticalBumpRatio",
|
|
164
|
+
"turnExitYawThreshold",
|
|
165
|
+
"turnExitConfirmationMs",
|
|
166
|
+
"turnClosureWindowMs"
|
|
167
|
+
]) assertPositiveFinite(field, tuning[field]);
|
|
168
|
+
for (const field of [
|
|
169
|
+
"motionMinimumSamples",
|
|
170
|
+
"longitudinalMinimumSamples",
|
|
171
|
+
"rapidLongitudinalMinimumSamples",
|
|
172
|
+
"turnMinimumSamples"
|
|
173
|
+
]) if (!Number.isInteger(tuning[field]) || tuning[field] < 1) throwLoggedRangeError("resolveDrivingStatusTuning", `${field} must be a positive integer`);
|
|
174
|
+
for (const field of [
|
|
175
|
+
"longitudinalSignCoherence",
|
|
176
|
+
"rapidLongitudinalSignCoherence",
|
|
177
|
+
"turnSignCoherence",
|
|
178
|
+
"turnYawDominanceThreshold"
|
|
179
|
+
]) {
|
|
180
|
+
const value = tuning[field];
|
|
181
|
+
if (!Number.isFinite(value) || value <= .5 || value > 1) throwLoggedRangeError("resolveDrivingStatusTuning", `${field} must be greater than 0.5 and less than or equal to 1`);
|
|
182
|
+
}
|
|
183
|
+
if (tuning.drivingMotionThreshold <= tuning.stoppedMotionThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingMotionThreshold must be greater than stoppedMotionThreshold");
|
|
184
|
+
if (tuning.drivingGyroThreshold <= tuning.stoppedGyroThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingGyroThreshold must be greater than stoppedGyroThreshold");
|
|
185
|
+
if (tuning.drivingRawGyroThreshold <= tuning.stoppedRawGyroThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingRawGyroThreshold must be greater than stoppedRawGyroThreshold");
|
|
186
|
+
if (tuning.longitudinalThreshold <= tuning.longitudinalExitThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "longitudinalThreshold must be greater than longitudinalExitThreshold");
|
|
187
|
+
if (tuning.rapidLongitudinalMeanThreshold <= tuning.longitudinalThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalMeanThreshold must be greater than longitudinalThreshold");
|
|
188
|
+
if (tuning.rapidLongitudinalPeakThreshold < tuning.rapidLongitudinalMeanThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalPeakThreshold must be greater than or equal to rapidLongitudinalMeanThreshold");
|
|
189
|
+
if (tuning.turnYawThreshold <= tuning.turnExitYawThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "turnYawThreshold must be greater than turnExitYawThreshold");
|
|
129
190
|
dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "returned", { tuning });
|
|
130
191
|
return tuning;
|
|
131
192
|
};
|
|
132
|
-
const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING) => {
|
|
193
|
+
const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING, correction = {}) => {
|
|
133
194
|
dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "called", {
|
|
134
195
|
info,
|
|
135
|
-
tuning
|
|
196
|
+
tuning,
|
|
197
|
+
correction
|
|
136
198
|
});
|
|
137
199
|
if (!info || !Number.isFinite(info.capturedAtMs)) {
|
|
138
200
|
dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
|
|
@@ -140,149 +202,164 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
140
202
|
}
|
|
141
203
|
const linearAcceleration = availableFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
|
|
142
204
|
const gyroscope = availableFreshVector(info.gyroscope, info.capturedAtMs, tuning.maxSampleAgeMs);
|
|
143
|
-
|
|
205
|
+
const gravityMetric = availableFreshVector(info.gravity, info.capturedAtMs, tuning.maxSampleAgeMs);
|
|
206
|
+
const gravity = availableCorrectedGravity(correction, info.capturedAtMs, tuning.maxSampleAgeMs) ?? (correction.filteredGravity !== void 0 && gravityMetric !== null ? {
|
|
207
|
+
value: correction.filteredGravity,
|
|
208
|
+
sampledAtMs: gravityMetric.sampledAtMs
|
|
209
|
+
} : gravityMetric);
|
|
210
|
+
if (linearAcceleration === null || gyroscope === null || gravity === null) {
|
|
144
211
|
dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
|
|
145
212
|
linearAcceleration,
|
|
146
|
-
gyroscope
|
|
213
|
+
gyroscope,
|
|
214
|
+
gravity
|
|
147
215
|
});
|
|
148
216
|
return null;
|
|
149
217
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
218
|
+
const frame = buildVehicleFrame(gravity.value, tuning.vehicleRightMinimumNorm);
|
|
219
|
+
if (frame === null) {
|
|
220
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
|
|
221
|
+
gravity: gravity.value,
|
|
222
|
+
vehicleRightMinimumNorm: tuning.vehicleRightMinimumNorm
|
|
223
|
+
});
|
|
156
224
|
return null;
|
|
157
225
|
}
|
|
226
|
+
const correctedLinear = subtract(linearAcceleration.value, correction.linearAccelerationBias ?? ZERO_VECTOR$1);
|
|
227
|
+
const correctedGyroscope = subtract(gyroscope.value, correction.gyroscopeBias ?? ZERO_VECTOR$1);
|
|
158
228
|
const result = {
|
|
159
229
|
capturedAtMs: info.capturedAtMs,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
230
|
+
linearAccelerationSampledAtMs: linearAcceleration.sampledAtMs,
|
|
231
|
+
gyroscopeSampledAtMs: gyroscope.sampledAtMs,
|
|
232
|
+
gravitySampledAtMs: gravity.sampledAtMs,
|
|
233
|
+
longitudinal: dot(correctedLinear, frame.forward),
|
|
234
|
+
lateral: dot(correctedLinear, frame.right),
|
|
235
|
+
vertical: dot(correctedLinear, frame.up),
|
|
236
|
+
yaw: dot(correctedGyroscope, frame.up),
|
|
237
|
+
gyroMagnitude: magnitude(correctedGyroscope),
|
|
238
|
+
rawGyroMagnitude: magnitude(gyroscope.value),
|
|
239
|
+
rawLinearAcceleration: linearAcceleration.value,
|
|
240
|
+
rawGyroscope: gyroscope.value,
|
|
241
|
+
frame
|
|
165
242
|
};
|
|
166
243
|
dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned", { result });
|
|
167
244
|
return result;
|
|
168
245
|
};
|
|
169
|
-
const
|
|
170
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "
|
|
171
|
-
|
|
172
|
-
|
|
246
|
+
const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TUNING.vehicleRightMinimumNorm) => {
|
|
247
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "called", {
|
|
248
|
+
gravity,
|
|
249
|
+
rightMinimumNorm
|
|
173
250
|
});
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
if (longitudinal >= tuning.rapidLongitudinalThreshold) {
|
|
180
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "RAPID_ACCELERATION" });
|
|
181
|
-
return "RAPID_ACCELERATION";
|
|
182
|
-
}
|
|
183
|
-
if (Math.abs(yaw) >= tuning.turnYawThreshold) {
|
|
184
|
-
const result = yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
|
|
185
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", {
|
|
186
|
-
result,
|
|
187
|
-
basis: "yaw"
|
|
188
|
-
});
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
if (Math.abs(lateral) >= tuning.turnLateralThreshold) {
|
|
192
|
-
const result = lateral < 0 ? "LEFT_TURN" : "RIGHT_TURN";
|
|
193
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", {
|
|
194
|
-
result,
|
|
195
|
-
basis: "lateral"
|
|
196
|
-
});
|
|
197
|
-
return result;
|
|
251
|
+
const gravityDirection = normalize(gravity);
|
|
252
|
+
if (gravityDirection === null) {
|
|
253
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid gravity", { result: null });
|
|
254
|
+
return null;
|
|
198
255
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
256
|
+
const up = gravityDirection;
|
|
257
|
+
const projectedRight = subtract(DEVICE_RIGHT, scale(up, dot(DEVICE_RIGHT, up)));
|
|
258
|
+
if (magnitude(projectedRight) < rightMinimumNorm) {
|
|
259
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned degenerate right axis", { result: null });
|
|
260
|
+
return null;
|
|
202
261
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
262
|
+
const right = normalize(projectedRight);
|
|
263
|
+
if (right === null) {
|
|
264
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid right axis", { result: null });
|
|
265
|
+
return null;
|
|
206
266
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
267
|
+
const forward = normalize(cross(right, up));
|
|
268
|
+
const result = forward === null ? null : {
|
|
269
|
+
up,
|
|
270
|
+
right,
|
|
271
|
+
forward
|
|
272
|
+
};
|
|
273
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned", { result });
|
|
274
|
+
return result;
|
|
275
|
+
};
|
|
276
|
+
const classifyInstantaneousEvidence = (metrics, tuning) => {
|
|
277
|
+
const longitudinalMagnitude = Math.abs(metrics.longitudinal);
|
|
278
|
+
if (longitudinalMagnitude >= tuning.rapidLongitudinalPeakThreshold) {
|
|
279
|
+
if (Math.abs(metrics.vertical) > longitudinalMagnitude * tuning.verticalBumpRatio) return {
|
|
280
|
+
candidateStatus: null,
|
|
281
|
+
rejectionReason: "vertical_bump"
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
candidateStatus: metrics.longitudinal > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING",
|
|
285
|
+
rejectionReason: null
|
|
286
|
+
};
|
|
210
287
|
}
|
|
211
|
-
|
|
212
|
-
|
|
288
|
+
if (Math.abs(metrics.yaw) >= tuning.turnYawThreshold) return {
|
|
289
|
+
candidateStatus: metrics.yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN",
|
|
290
|
+
rejectionReason: null
|
|
291
|
+
};
|
|
292
|
+
if (longitudinalMagnitude >= tuning.longitudinalThreshold) return {
|
|
293
|
+
candidateStatus: metrics.longitudinal > 0 ? "ACCELERATION" : "BRAKING",
|
|
294
|
+
rejectionReason: null
|
|
295
|
+
};
|
|
296
|
+
return {
|
|
297
|
+
candidateStatus: null,
|
|
298
|
+
rejectionReason: null
|
|
299
|
+
};
|
|
213
300
|
};
|
|
214
301
|
const availableFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
|
|
215
|
-
|
|
216
|
-
metric,
|
|
217
|
-
capturedAtMs,
|
|
218
|
-
maxSampleAgeMs
|
|
219
|
-
});
|
|
220
|
-
if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) {
|
|
221
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshVector", "returned", { result: null });
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
302
|
+
if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) return null;
|
|
224
303
|
const { x, y, z } = metric.value;
|
|
225
|
-
|
|
304
|
+
return [
|
|
226
305
|
x,
|
|
227
306
|
y,
|
|
228
307
|
z
|
|
229
|
-
].every(Number.isFinite) ?
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
308
|
+
].every(Number.isFinite) ? {
|
|
309
|
+
value: metric.value,
|
|
310
|
+
sampledAtMs: metric.sampledAtMs
|
|
311
|
+
} : null;
|
|
312
|
+
};
|
|
313
|
+
const availableCorrectedGravity = (correction, capturedAtMs, maxSampleAgeMs) => {
|
|
314
|
+
const { filteredGravity, filteredGravitySampledAtMs } = correction;
|
|
315
|
+
if (filteredGravity === void 0 || filteredGravitySampledAtMs === void 0 || !isFresh(filteredGravitySampledAtMs, capturedAtMs, maxSampleAgeMs)) return null;
|
|
316
|
+
const { x, y, z } = filteredGravity;
|
|
317
|
+
return [
|
|
318
|
+
x,
|
|
319
|
+
y,
|
|
320
|
+
z
|
|
321
|
+
].every(Number.isFinite) ? {
|
|
322
|
+
value: filteredGravity,
|
|
323
|
+
sampledAtMs: filteredGravitySampledAtMs
|
|
324
|
+
} : null;
|
|
246
325
|
};
|
|
247
326
|
const isFresh = (sampledAtMs, capturedAtMs, maxSampleAgeMs) => {
|
|
248
|
-
|
|
249
|
-
sampledAtMs,
|
|
250
|
-
capturedAtMs,
|
|
251
|
-
maxSampleAgeMs
|
|
252
|
-
});
|
|
253
|
-
if (sampledAtMs === null || !Number.isFinite(sampledAtMs)) {
|
|
254
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "isFresh", "returned", { result: false });
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
327
|
+
if (sampledAtMs === null || !Number.isFinite(sampledAtMs)) return false;
|
|
257
328
|
const age = capturedAtMs - sampledAtMs;
|
|
258
|
-
|
|
259
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "isFresh", "returned", {
|
|
260
|
-
age,
|
|
261
|
-
result
|
|
262
|
-
});
|
|
263
|
-
return result;
|
|
329
|
+
return age >= 0 && age <= maxSampleAgeMs;
|
|
264
330
|
};
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
331
|
+
const dot = (left, right) => left.x * right.x + left.y * right.y + left.z * right.z;
|
|
332
|
+
const cross = (left, right) => ({
|
|
333
|
+
x: left.y * right.z - left.z * right.y,
|
|
334
|
+
y: left.z * right.x - left.x * right.z,
|
|
335
|
+
z: left.x * right.y - left.y * right.x
|
|
336
|
+
});
|
|
337
|
+
const subtract = (left, right) => ({
|
|
338
|
+
x: left.x - right.x,
|
|
339
|
+
y: left.y - right.y,
|
|
340
|
+
z: left.z - right.z
|
|
341
|
+
});
|
|
342
|
+
const scale = (value, factor) => ({
|
|
343
|
+
x: value.x * factor,
|
|
344
|
+
y: value.y * factor,
|
|
345
|
+
z: value.z * factor
|
|
346
|
+
});
|
|
347
|
+
const magnitude = (value) => Math.hypot(value.x, value.y, value.z);
|
|
348
|
+
const normalize = (value) => {
|
|
349
|
+
const length = magnitude(value);
|
|
350
|
+
return Number.isFinite(length) && length > Number.EPSILON ? scale(value, 1 / length) : null;
|
|
274
351
|
};
|
|
275
352
|
const assertPositiveFinite = (name, value) => {
|
|
276
|
-
dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "called", {
|
|
277
|
-
name,
|
|
278
|
-
value
|
|
279
|
-
});
|
|
280
353
|
if (!Number.isFinite(value) || value <= 0) {
|
|
281
354
|
const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
|
|
282
355
|
dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "threw", error);
|
|
283
356
|
throw error;
|
|
284
357
|
}
|
|
285
|
-
|
|
358
|
+
};
|
|
359
|
+
const throwLoggedRangeError = (scope, message) => {
|
|
360
|
+
const error = new RangeError(message);
|
|
361
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, scope, "threw", error);
|
|
362
|
+
throw error;
|
|
286
363
|
};
|
|
287
364
|
//#endregion
|
|
288
365
|
//#region src/sensor/unified_info.ts
|
|
@@ -1272,6 +1349,11 @@ const safeInvoke = (callback) => {
|
|
|
1272
1349
|
//#endregion
|
|
1273
1350
|
//#region src/sensor/driving_status_controller.ts
|
|
1274
1351
|
const DEBUG_MODULE$1 = "driving_status_controller.ts";
|
|
1352
|
+
const ZERO_VECTOR = {
|
|
1353
|
+
x: 0,
|
|
1354
|
+
y: 0,
|
|
1355
|
+
z: 0
|
|
1356
|
+
};
|
|
1275
1357
|
const createDrivingStatusController = (options = {}) => {
|
|
1276
1358
|
dialSdkDebugWarn(DEBUG_MODULE$1, "createDrivingStatusController", "called", { options });
|
|
1277
1359
|
const result = new DefaultDrivingStatusController(options);
|
|
@@ -1281,14 +1363,42 @@ const createDrivingStatusController = (options = {}) => {
|
|
|
1281
1363
|
var DefaultDrivingStatusController = class {
|
|
1282
1364
|
constructor(options) {
|
|
1283
1365
|
this.listeners = /* @__PURE__ */ new Set();
|
|
1284
|
-
this.
|
|
1366
|
+
this.longitudinalWindow = [];
|
|
1367
|
+
this.yawWindow = [];
|
|
1285
1368
|
this.motionWindow = [];
|
|
1286
1369
|
this.candidate = null;
|
|
1287
|
-
this.
|
|
1370
|
+
this.candidateSinceMs = null;
|
|
1288
1371
|
this.lastAvailableAtMs = null;
|
|
1289
1372
|
this.unavailableSinceMs = null;
|
|
1290
1373
|
this.fallbackApplied = false;
|
|
1291
|
-
this.
|
|
1374
|
+
this.filteredGravity = null;
|
|
1375
|
+
this.lastGravitySampledAtMs = null;
|
|
1376
|
+
this.linearAccelerationBias = { ...ZERO_VECTOR };
|
|
1377
|
+
this.gyroscopeBias = { ...ZERO_VECTOR };
|
|
1378
|
+
this.biasCalibrated = false;
|
|
1379
|
+
this.lastBiasSampledAtMs = null;
|
|
1380
|
+
this.stoppedConditionSinceMs = null;
|
|
1381
|
+
this.drivingConditionSinceMs = null;
|
|
1382
|
+
this.stationaryConfirmed = false;
|
|
1383
|
+
this.urgentStatus = null;
|
|
1384
|
+
this.urgentStartedAtMs = null;
|
|
1385
|
+
this.normalLongitudinalStatus = null;
|
|
1386
|
+
this.longitudinalClearSinceMs = null;
|
|
1387
|
+
this.turnStatus = null;
|
|
1388
|
+
this.turnQuietSinceMs = null;
|
|
1389
|
+
this.oppositeTurnSinceMs = null;
|
|
1390
|
+
this.detectorCandidates = {
|
|
1391
|
+
base: null,
|
|
1392
|
+
rapid_longitudinal: null,
|
|
1393
|
+
longitudinal: null,
|
|
1394
|
+
turn: null
|
|
1395
|
+
};
|
|
1396
|
+
this.detectorRejections = {
|
|
1397
|
+
base: null,
|
|
1398
|
+
rapid_longitudinal: null,
|
|
1399
|
+
longitudinal: null,
|
|
1400
|
+
turn: null
|
|
1401
|
+
};
|
|
1292
1402
|
this.requestedRunning = false;
|
|
1293
1403
|
this.polling = false;
|
|
1294
1404
|
this.lifecycleSuspended = false;
|
|
@@ -1297,12 +1407,12 @@ var DefaultDrivingStatusController = class {
|
|
|
1297
1407
|
this.pendingRead = null;
|
|
1298
1408
|
this.unsubscribePageLifecycle = null;
|
|
1299
1409
|
this.handleLifecyclePause = () => {
|
|
1300
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "called"
|
|
1301
|
-
requestedRunning: this.requestedRunning,
|
|
1302
|
-
lifecycleSuspended: this.lifecycleSuspended
|
|
1303
|
-
});
|
|
1410
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "called");
|
|
1304
1411
|
if (!this.requestedRunning || this.lifecycleSuspended) {
|
|
1305
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "ignored"
|
|
1412
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "ignored", {
|
|
1413
|
+
requestedRunning: this.requestedRunning,
|
|
1414
|
+
lifecycleSuspended: this.lifecycleSuspended
|
|
1415
|
+
});
|
|
1306
1416
|
return;
|
|
1307
1417
|
}
|
|
1308
1418
|
this.lifecycleSuspended = true;
|
|
@@ -1311,13 +1421,15 @@ var DefaultDrivingStatusController = class {
|
|
|
1311
1421
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "returned");
|
|
1312
1422
|
};
|
|
1313
1423
|
this.handleLifecycleResume = () => {
|
|
1314
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "called"
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1424
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "called");
|
|
1425
|
+
const pageHidden = this.requestedRunning && this.lifecycleSuspended && !this.destroyed ? isDrivingPageHidden() : false;
|
|
1426
|
+
if (!this.requestedRunning || !this.lifecycleSuspended || this.destroyed || pageHidden) {
|
|
1427
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "ignored", {
|
|
1428
|
+
requestedRunning: this.requestedRunning,
|
|
1429
|
+
lifecycleSuspended: this.lifecycleSuspended,
|
|
1430
|
+
destroyed: this.destroyed,
|
|
1431
|
+
pageHidden
|
|
1432
|
+
});
|
|
1321
1433
|
return;
|
|
1322
1434
|
}
|
|
1323
1435
|
this.lifecycleSuspended = false;
|
|
@@ -1333,18 +1445,15 @@ var DefaultDrivingStatusController = class {
|
|
|
1333
1445
|
this.onDiagnostic = options.onDiagnostic;
|
|
1334
1446
|
this.managePageLifecycle = options.managePageLifecycle ?? true;
|
|
1335
1447
|
const initialStatus = options.initialStatus ?? "STOPPED";
|
|
1336
|
-
if (!CAR_RUNNING_STATUSES.includes(initialStatus))
|
|
1448
|
+
if (!CAR_RUNNING_STATUSES.includes(initialStatus)) {
|
|
1449
|
+
const error = /* @__PURE__ */ new TypeError(`Unsupported initialStatus: ${initialStatus}`);
|
|
1450
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "threw", error);
|
|
1451
|
+
throw error;
|
|
1452
|
+
}
|
|
1337
1453
|
this.status = initialStatus;
|
|
1338
1454
|
this.baseStatus = isBaseStatus(initialStatus) ? initialStatus : "STOPPED";
|
|
1339
1455
|
this.statusCommittedAtMs = this.clock.now();
|
|
1340
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "created",
|
|
1341
|
-
initialStatus,
|
|
1342
|
-
baseStatus: this.baseStatus,
|
|
1343
|
-
pollIntervalMs: this.pollIntervalMs,
|
|
1344
|
-
unavailableFallbackMs: this.unavailableFallbackMs,
|
|
1345
|
-
tuning: this.tuning,
|
|
1346
|
-
managePageLifecycle: this.managePageLifecycle
|
|
1347
|
-
});
|
|
1456
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "created", this.getSnapshot());
|
|
1348
1457
|
}
|
|
1349
1458
|
start() {
|
|
1350
1459
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.start", "called", this.getSnapshot());
|
|
@@ -1389,16 +1498,17 @@ var DefaultDrivingStatusController = class {
|
|
|
1389
1498
|
}
|
|
1390
1499
|
this.destroyed = true;
|
|
1391
1500
|
this.listeners.clear();
|
|
1392
|
-
this.
|
|
1393
|
-
this.motionWindow.length = 0;
|
|
1501
|
+
this.resetTransientRecognition();
|
|
1394
1502
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.destroy", "returned", this.getSnapshot());
|
|
1395
1503
|
}
|
|
1396
1504
|
getSnapshot() {
|
|
1397
1505
|
const result = {
|
|
1398
1506
|
lifecycle: this.destroyed ? "destroyed" : this.polling ? "running" : "stopped",
|
|
1399
1507
|
status: this.status,
|
|
1508
|
+
baseStatus: this.baseStatus,
|
|
1509
|
+
activeTurn: this.turnStatus,
|
|
1400
1510
|
candidate: this.candidate,
|
|
1401
|
-
|
|
1511
|
+
candidateSinceMs: this.candidateSinceMs,
|
|
1402
1512
|
statusCommittedAtMs: this.statusCommittedAtMs,
|
|
1403
1513
|
lastAvailableAtMs: this.lastAvailableAtMs,
|
|
1404
1514
|
unavailableSinceMs: this.unavailableSinceMs,
|
|
@@ -1409,16 +1519,16 @@ var DefaultDrivingStatusController = class {
|
|
|
1409
1519
|
}
|
|
1410
1520
|
subscribe(listener) {
|
|
1411
1521
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "called", {
|
|
1412
|
-
|
|
1413
|
-
|
|
1522
|
+
listener,
|
|
1523
|
+
destroyed: this.destroyed
|
|
1414
1524
|
});
|
|
1415
1525
|
if (this.destroyed) {
|
|
1416
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "
|
|
1526
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "ignored destroyed controller");
|
|
1417
1527
|
return noop;
|
|
1418
1528
|
}
|
|
1419
1529
|
this.listeners.add(listener);
|
|
1530
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "returned", { listenerCount: this.listeners.size });
|
|
1420
1531
|
let subscribed = true;
|
|
1421
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "subscribed", { listenerCount: this.listeners.size });
|
|
1422
1532
|
return () => {
|
|
1423
1533
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.unsubscribe", "called", { subscribed });
|
|
1424
1534
|
if (!subscribed) {
|
|
@@ -1431,15 +1541,14 @@ var DefaultDrivingStatusController = class {
|
|
|
1431
1541
|
};
|
|
1432
1542
|
}
|
|
1433
1543
|
activatePolling() {
|
|
1434
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "called"
|
|
1435
|
-
destroyed: this.destroyed,
|
|
1436
|
-
polling: this.polling,
|
|
1437
|
-
requestedRunning: this.requestedRunning,
|
|
1438
|
-
lifecycleSuspended: this.lifecycleSuspended,
|
|
1439
|
-
generation: this.generation
|
|
1440
|
-
});
|
|
1544
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "called");
|
|
1441
1545
|
if (this.destroyed || this.polling || !this.requestedRunning || this.lifecycleSuspended) {
|
|
1442
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "ignored"
|
|
1546
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "ignored", {
|
|
1547
|
+
destroyed: this.destroyed,
|
|
1548
|
+
polling: this.polling,
|
|
1549
|
+
requestedRunning: this.requestedRunning,
|
|
1550
|
+
lifecycleSuspended: this.lifecycleSuspended
|
|
1551
|
+
});
|
|
1443
1552
|
return;
|
|
1444
1553
|
}
|
|
1445
1554
|
this.polling = true;
|
|
@@ -1449,14 +1558,10 @@ var DefaultDrivingStatusController = class {
|
|
|
1449
1558
|
}
|
|
1450
1559
|
deactivatePolling() {
|
|
1451
1560
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "called", {
|
|
1452
|
-
|
|
1453
|
-
|
|
1561
|
+
generation: this.generation,
|
|
1562
|
+
hasTimer: this.timer !== void 0,
|
|
1454
1563
|
hasPendingRead: this.pendingRead !== null
|
|
1455
1564
|
});
|
|
1456
|
-
if (!this.polling && this.timer === void 0 && this.pendingRead === null) {
|
|
1457
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "ignored");
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
1565
|
this.polling = false;
|
|
1461
1566
|
this.generation += 1;
|
|
1462
1567
|
if (this.timer !== void 0) {
|
|
@@ -1468,72 +1573,110 @@ var DefaultDrivingStatusController = class {
|
|
|
1468
1573
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "returned", { generation: this.generation });
|
|
1469
1574
|
}
|
|
1470
1575
|
schedulePoll(delayMs, generation) {
|
|
1471
|
-
|
|
1472
|
-
delayMs,
|
|
1473
|
-
generation,
|
|
1474
|
-
currentGeneration: this.generation,
|
|
1475
|
-
polling: this.polling,
|
|
1476
|
-
timer: this.timer
|
|
1477
|
-
});
|
|
1478
|
-
if (!this.polling || generation !== this.generation || this.timer !== void 0) {
|
|
1479
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll", "ignored");
|
|
1480
|
-
return;
|
|
1481
|
-
}
|
|
1576
|
+
if (!this.polling || generation !== this.generation || this.timer !== void 0) return;
|
|
1482
1577
|
this.timer = this.clock.setTimeout(() => {
|
|
1483
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll.callback", "called", { generation });
|
|
1484
1578
|
this.timer = void 0;
|
|
1485
1579
|
this.poll(generation);
|
|
1486
1580
|
}, delayMs);
|
|
1487
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll", "scheduled", {
|
|
1488
|
-
delayMs,
|
|
1489
|
-
generation,
|
|
1490
|
-
timer: this.timer
|
|
1491
|
-
});
|
|
1492
1581
|
}
|
|
1493
1582
|
async poll(generation) {
|
|
1494
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", {
|
|
1495
|
-
generation,
|
|
1496
|
-
currentGeneration: this.generation,
|
|
1497
|
-
polling: this.polling,
|
|
1498
|
-
hasPendingRead: this.pendingRead !== null
|
|
1499
|
-
});
|
|
1583
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", { generation });
|
|
1500
1584
|
if (!this.polling || generation !== this.generation || this.pendingRead !== null) {
|
|
1501
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored"
|
|
1585
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored", {
|
|
1586
|
+
polling: this.polling,
|
|
1587
|
+
generation,
|
|
1588
|
+
currentGeneration: this.generation,
|
|
1589
|
+
hasPendingRead: this.pendingRead !== null
|
|
1590
|
+
});
|
|
1502
1591
|
return;
|
|
1503
1592
|
}
|
|
1504
1593
|
const pendingRead = new AbortController();
|
|
1505
1594
|
this.pendingRead = pendingRead;
|
|
1506
1595
|
try {
|
|
1507
1596
|
const payload = await this.sensorProvider(pendingRead.signal);
|
|
1508
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "provider resolved", { payload });
|
|
1509
1597
|
if (!this.polling || generation !== this.generation || pendingRead.signal.aborted) {
|
|
1510
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "discarded provider result"
|
|
1598
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "discarded provider result", {
|
|
1599
|
+
generation,
|
|
1600
|
+
currentGeneration: this.generation,
|
|
1601
|
+
polling: this.polling,
|
|
1602
|
+
aborted: pendingRead.signal.aborted
|
|
1603
|
+
});
|
|
1511
1604
|
return;
|
|
1512
1605
|
}
|
|
1513
|
-
const
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
|
|
1606
|
+
const gravity = this.resolveGravityFrame(payload);
|
|
1607
|
+
const metrics = gravity === null ? null : extractDrivingSnapshotMetrics(payload, this.tuning, {
|
|
1608
|
+
filteredGravity: gravity.value,
|
|
1609
|
+
filteredGravitySampledAtMs: gravity.sampledAtMs,
|
|
1610
|
+
linearAccelerationBias: this.linearAccelerationBias,
|
|
1611
|
+
gyroscopeBias: this.gyroscopeBias
|
|
1612
|
+
});
|
|
1613
|
+
const accelerometerSample = extractFreshVectorMetric(payload.accelerometer, payload.capturedAtMs, this.tuning.maxSampleAgeMs);
|
|
1614
|
+
if (gravity === null || metrics === null || accelerometerSample === null) {
|
|
1615
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "invalid or stale snapshot", {
|
|
1616
|
+
metrics,
|
|
1617
|
+
accelerometerSample
|
|
1618
|
+
});
|
|
1619
|
+
this.processUnavailable("invalid_or_stale_snapshot");
|
|
1620
|
+
} else {
|
|
1621
|
+
this.processAvailable(metrics, accelerometerSample, gravity);
|
|
1622
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "processed sample", { capturedAtMs: metrics.capturedAtMs });
|
|
1623
|
+
}
|
|
1517
1624
|
} catch (error) {
|
|
1518
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "provider rejected", error);
|
|
1519
1625
|
if (!this.polling || generation !== this.generation || pendingRead.signal.aborted || isAbortError$1(error)) {
|
|
1520
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored
|
|
1626
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored provider error", { error });
|
|
1521
1627
|
return;
|
|
1522
1628
|
}
|
|
1629
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "caught provider error", { error });
|
|
1523
1630
|
this.processUnavailable("provider_error");
|
|
1524
1631
|
} finally {
|
|
1525
1632
|
if (this.pendingRead === pendingRead) this.pendingRead = null;
|
|
1526
1633
|
if (this.polling && generation === this.generation) this.schedulePoll(this.pollIntervalMs, generation);
|
|
1527
1634
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "completed", {
|
|
1528
1635
|
generation,
|
|
1529
|
-
polling: this.polling
|
|
1530
|
-
currentGeneration: this.generation
|
|
1636
|
+
polling: this.polling
|
|
1531
1637
|
});
|
|
1532
1638
|
}
|
|
1533
1639
|
}
|
|
1534
|
-
|
|
1640
|
+
resolveGravityFrame(info) {
|
|
1641
|
+
const sample = extractFreshVectorMetric(info.gravity, info.capturedAtMs, this.tuning.maxSampleAgeMs);
|
|
1642
|
+
if (sample === null) {
|
|
1643
|
+
if (this.filteredGravity === null || this.lastGravitySampledAtMs === null) return null;
|
|
1644
|
+
const ageMs = info.capturedAtMs - this.lastGravitySampledAtMs;
|
|
1645
|
+
return ageMs >= 0 && ageMs <= this.tuning.maxSampleAgeMs ? {
|
|
1646
|
+
value: this.filteredGravity,
|
|
1647
|
+
sampledAtMs: this.lastGravitySampledAtMs,
|
|
1648
|
+
source: "cached"
|
|
1649
|
+
} : null;
|
|
1650
|
+
}
|
|
1651
|
+
if (sample.sampledAtMs === this.lastGravitySampledAtMs && this.filteredGravity !== null) return {
|
|
1652
|
+
value: this.filteredGravity,
|
|
1653
|
+
sampledAtMs: sample.sampledAtMs,
|
|
1654
|
+
source: "live"
|
|
1655
|
+
};
|
|
1656
|
+
if (this.filteredGravity === null || this.lastGravitySampledAtMs === null) this.filteredGravity = { ...sample.value };
|
|
1657
|
+
else {
|
|
1658
|
+
if (sample.sampledAtMs < this.lastGravitySampledAtMs) {
|
|
1659
|
+
const ageMs = info.capturedAtMs - this.lastGravitySampledAtMs;
|
|
1660
|
+
return ageMs >= 0 && ageMs <= this.tuning.maxSampleAgeMs ? {
|
|
1661
|
+
value: this.filteredGravity,
|
|
1662
|
+
sampledAtMs: this.lastGravitySampledAtMs,
|
|
1663
|
+
source: "cached"
|
|
1664
|
+
} : null;
|
|
1665
|
+
}
|
|
1666
|
+
const elapsedMs = Math.max(0, sample.sampledAtMs - this.lastGravitySampledAtMs);
|
|
1667
|
+
const alpha = 1 - Math.exp(-elapsedMs / this.tuning.gravityFilterTimeConstantMs);
|
|
1668
|
+
this.filteredGravity = lerpVector(this.filteredGravity, sample.value, alpha);
|
|
1669
|
+
}
|
|
1670
|
+
this.lastGravitySampledAtMs = sample.sampledAtMs;
|
|
1671
|
+
return {
|
|
1672
|
+
value: this.filteredGravity,
|
|
1673
|
+
sampledAtMs: sample.sampledAtMs,
|
|
1674
|
+
source: "live"
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
processAvailable(metrics, accelerometerSample, gravity) {
|
|
1535
1678
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "called", {
|
|
1536
|
-
|
|
1679
|
+
metrics,
|
|
1537
1680
|
accelerometerSample
|
|
1538
1681
|
});
|
|
1539
1682
|
const now = this.clock.now();
|
|
@@ -1543,56 +1686,211 @@ var DefaultDrivingStatusController = class {
|
|
|
1543
1686
|
this.emitDiagnostic({
|
|
1544
1687
|
type: "sample_available",
|
|
1545
1688
|
atMs: now,
|
|
1546
|
-
capturedAtMs:
|
|
1689
|
+
capturedAtMs: metrics.capturedAtMs,
|
|
1690
|
+
frameSource: gravity.source,
|
|
1691
|
+
frameAgeMs: metrics.capturedAtMs - gravity.sampledAtMs,
|
|
1547
1692
|
tuningVersion: this.tuning.version
|
|
1548
1693
|
});
|
|
1549
|
-
this.
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1694
|
+
const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", Math.max(this.tuning.longitudinalWindowMs, this.tuning.rapidLongitudinalWindowMs));
|
|
1695
|
+
const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs));
|
|
1696
|
+
const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, this.tuning.motionWindowMs);
|
|
1697
|
+
if (!addedLongitudinal && !addedYaw && !addedMotion) {
|
|
1698
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "ignored duplicate sample");
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
const motionMad = calculateCombinedAxisMad(this.motionWindow, this.tuning.motionMinimumSamples);
|
|
1702
|
+
const previousBaseStatus = this.baseStatus;
|
|
1703
|
+
const previousUrgentStatus = this.urgentStatus;
|
|
1704
|
+
const previousLongitudinalStatus = this.normalLongitudinalStatus;
|
|
1705
|
+
const previousTurnStatus = this.turnStatus;
|
|
1706
|
+
const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion);
|
|
1707
|
+
const longitudinalEvidence = detectLongitudinalEvidence(this.longitudinalWindow, this.tuning);
|
|
1708
|
+
if (addedLongitudinal) this.updateLongitudinalDetector(longitudinalEvidence, metrics.linearAccelerationSampledAtMs);
|
|
1709
|
+
const turnEvidence = detectTurnEvidence(this.yawWindow, this.tuning);
|
|
1710
|
+
if (addedYaw) this.updateTurnDetector(turnEvidence);
|
|
1711
|
+
if (this.urgentStatus !== null || this.turnStatus !== null || this.normalLongitudinalStatus !== null) {
|
|
1712
|
+
this.baseStatus = "STEADY_DRIVING";
|
|
1713
|
+
this.stoppedConditionSinceMs = null;
|
|
1714
|
+
this.stationaryConfirmed = false;
|
|
1715
|
+
}
|
|
1716
|
+
if (this.stationaryConfirmed) this.updateBias(metrics);
|
|
1717
|
+
const statistics = {
|
|
1718
|
+
base: {
|
|
1719
|
+
sampleCount: this.motionWindow.length,
|
|
1720
|
+
durationMs: vectorWindowDuration(this.motionWindow),
|
|
1721
|
+
motionMad,
|
|
1722
|
+
gyroMagnitude: metrics.gyroMagnitude,
|
|
1723
|
+
rawGyroMagnitude: metrics.rawGyroMagnitude
|
|
1724
|
+
},
|
|
1725
|
+
longitudinal: longitudinalEvidence.normalStatistics,
|
|
1726
|
+
rapidLongitudinal: longitudinalEvidence.rapidStatistics,
|
|
1727
|
+
turn: turnEvidence.statistics
|
|
1728
|
+
};
|
|
1729
|
+
const baseCandidate = baseCondition === "stationary" ? "STOPPED" : baseCondition === "moving" ? "STEADY_DRIVING" : null;
|
|
1730
|
+
this.updateDetectorCandidate("base", baseCandidate, null, statistics, now);
|
|
1731
|
+
this.updateDetectorCandidate("rapid_longitudinal", longitudinalEvidence.rapidCandidate, longitudinalEvidence.rejectionReason, statistics, now);
|
|
1732
|
+
this.updateDetectorCandidate("longitudinal", longitudinalEvidence.normalCandidate, null, statistics, now);
|
|
1733
|
+
this.updateDetectorCandidate("turn", turnEvidence.candidate, turnEvidence.rejectionReason, statistics, now);
|
|
1734
|
+
this.emitDetectorConfirmation("base", previousBaseStatus, this.baseStatus, statistics, now);
|
|
1735
|
+
this.emitDetectorConfirmation("rapid_longitudinal", previousUrgentStatus, this.urgentStatus, statistics, now);
|
|
1736
|
+
this.emitDetectorConfirmation("longitudinal", previousLongitudinalStatus, this.normalLongitudinalStatus, statistics, now);
|
|
1737
|
+
this.emitDetectorConfirmation("turn", previousTurnStatus, this.turnStatus, statistics, now);
|
|
1738
|
+
const pendingCandidate = instantaneousCandidate(metrics, this.tuning);
|
|
1739
|
+
this.setCandidate(pendingCandidate === this.status ? null : pendingCandidate, observationTime(metrics));
|
|
1740
|
+
this.emitDiagnostic({
|
|
1741
|
+
type: "detector_evidence",
|
|
1742
|
+
atMs: now,
|
|
1743
|
+
motionMad,
|
|
1744
|
+
baseCondition,
|
|
1745
|
+
rapidCandidate: longitudinalEvidence.rapidCandidate,
|
|
1746
|
+
longitudinalCandidate: longitudinalEvidence.normalCandidate,
|
|
1747
|
+
turnCandidate: turnEvidence.candidate,
|
|
1748
|
+
rejectionReason: longitudinalEvidence.rejectionReason ?? turnEvidence.rejectionReason,
|
|
1749
|
+
frameSource: gravity.source,
|
|
1750
|
+
frameAgeMs: metrics.capturedAtMs - gravity.sampledAtMs,
|
|
1751
|
+
statistics,
|
|
1752
|
+
tuningVersion: this.tuning.version
|
|
1561
1753
|
});
|
|
1754
|
+
const hasAction = this.urgentStatus !== null || this.turnStatus !== null || this.normalLongitudinalStatus !== null;
|
|
1755
|
+
if (motionMad !== null || hasAction) this.commitSelectedStatus(now);
|
|
1756
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "returned", {
|
|
1757
|
+
motionMad,
|
|
1758
|
+
baseCondition,
|
|
1759
|
+
status: this.status,
|
|
1760
|
+
baseStatus: this.baseStatus,
|
|
1761
|
+
urgentStatus: this.urgentStatus,
|
|
1762
|
+
turnStatus: this.turnStatus,
|
|
1763
|
+
normalLongitudinalStatus: this.normalLongitudinalStatus
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample) {
|
|
1767
|
+
if (motionMad === null || !isNewMotionSample) return "warming_up";
|
|
1768
|
+
const gyroForStop = this.biasCalibrated ? metrics.gyroMagnitude : metrics.rawGyroMagnitude;
|
|
1769
|
+
const stoppedGyroThreshold = this.biasCalibrated ? this.tuning.stoppedGyroThreshold : this.tuning.stoppedRawGyroThreshold;
|
|
1770
|
+
const drivingGyroThreshold = this.biasCalibrated ? this.tuning.drivingGyroThreshold : this.tuning.drivingRawGyroThreshold;
|
|
1771
|
+
const stationary = motionMad <= this.tuning.stoppedMotionThreshold && gyroForStop <= stoppedGyroThreshold;
|
|
1772
|
+
const moving = motionMad >= this.tuning.drivingMotionThreshold || gyroForStop >= drivingGyroThreshold;
|
|
1773
|
+
if (stationary) {
|
|
1774
|
+
this.drivingConditionSinceMs = null;
|
|
1775
|
+
if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
|
|
1776
|
+
if (sampledAtMs - this.stoppedConditionSinceMs >= this.tuning.stoppedConfirmationMs) {
|
|
1777
|
+
this.baseStatus = "STOPPED";
|
|
1778
|
+
this.stationaryConfirmed = true;
|
|
1779
|
+
}
|
|
1780
|
+
return "stationary";
|
|
1781
|
+
}
|
|
1782
|
+
this.stoppedConditionSinceMs = null;
|
|
1783
|
+
this.stationaryConfirmed = false;
|
|
1784
|
+
if (moving) {
|
|
1785
|
+
if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
|
|
1786
|
+
if (sampledAtMs - this.drivingConditionSinceMs >= this.tuning.drivingConfirmationMs) this.baseStatus = "STEADY_DRIVING";
|
|
1787
|
+
return "moving";
|
|
1788
|
+
}
|
|
1789
|
+
this.drivingConditionSinceMs = null;
|
|
1790
|
+
return "hysteresis";
|
|
1791
|
+
}
|
|
1792
|
+
updateLongitudinalDetector(evidence, sampledAtMs) {
|
|
1793
|
+
if (evidence.rapidCandidate !== null) {
|
|
1794
|
+
if (this.urgentStatus !== evidence.rapidCandidate) {
|
|
1795
|
+
this.urgentStatus = evidence.rapidCandidate;
|
|
1796
|
+
this.urgentStartedAtMs = sampledAtMs;
|
|
1797
|
+
}
|
|
1798
|
+
} else if (this.urgentStatus !== null && this.urgentStartedAtMs !== null && sampledAtMs - this.urgentStartedAtMs >= this.tuning.rapidMinimumDisplayMs) {
|
|
1799
|
+
this.urgentStatus = null;
|
|
1800
|
+
this.urgentStartedAtMs = null;
|
|
1801
|
+
}
|
|
1802
|
+
if (evidence.normalCandidate !== null) {
|
|
1803
|
+
this.normalLongitudinalStatus = evidence.normalCandidate;
|
|
1804
|
+
this.longitudinalClearSinceMs = null;
|
|
1805
|
+
} else if (this.normalLongitudinalStatus !== null) if (evidence.retainedNormal) this.longitudinalClearSinceMs = null;
|
|
1806
|
+
else {
|
|
1807
|
+
if (this.longitudinalClearSinceMs === null) this.longitudinalClearSinceMs = sampledAtMs;
|
|
1808
|
+
if (sampledAtMs - this.longitudinalClearSinceMs >= this.tuning.longitudinalReleaseMs) {
|
|
1809
|
+
this.normalLongitudinalStatus = null;
|
|
1810
|
+
this.longitudinalClearSinceMs = null;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1562
1813
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if (!this.motionWindow.some((existing) => existing.sampledAtMs === sample.sampledAtMs)) {
|
|
1570
|
-
this.motionWindow.push(sample);
|
|
1571
|
-
this.motionWindow.sort((left, right) => left.sampledAtMs - right.sampledAtMs);
|
|
1814
|
+
updateTurnDetector(evidence) {
|
|
1815
|
+
if (this.turnStatus === null) {
|
|
1816
|
+
if (evidence.candidate !== null) this.turnStatus = evidence.candidate;
|
|
1817
|
+
this.turnQuietSinceMs = null;
|
|
1818
|
+
this.oppositeTurnSinceMs = null;
|
|
1819
|
+
return;
|
|
1572
1820
|
}
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1821
|
+
const direction = evidence.latestYaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
|
|
1822
|
+
if (!(Math.abs(evidence.latestYaw) >= this.tuning.turnExitYawThreshold)) {
|
|
1823
|
+
this.oppositeTurnSinceMs = null;
|
|
1824
|
+
if (this.turnQuietSinceMs === null) this.turnQuietSinceMs = evidence.sampledAtMs;
|
|
1825
|
+
if (evidence.sampledAtMs - this.turnQuietSinceMs >= this.tuning.turnExitConfirmationMs) {
|
|
1826
|
+
this.turnStatus = null;
|
|
1827
|
+
this.turnQuietSinceMs = null;
|
|
1828
|
+
this.yawWindow.length = 0;
|
|
1829
|
+
}
|
|
1830
|
+
return;
|
|
1581
1831
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1832
|
+
this.turnQuietSinceMs = null;
|
|
1833
|
+
if (direction === this.turnStatus) {
|
|
1834
|
+
this.oppositeTurnSinceMs = null;
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (this.oppositeTurnSinceMs === null) this.oppositeTurnSinceMs = evidence.sampledAtMs;
|
|
1838
|
+
if (evidence.sampledAtMs - this.oppositeTurnSinceMs >= this.tuning.turnClosureWindowMs) {
|
|
1839
|
+
this.turnStatus = direction;
|
|
1840
|
+
this.oppositeTurnSinceMs = null;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
updateBias(metrics) {
|
|
1844
|
+
const sampledAtMs = Math.min(metrics.linearAccelerationSampledAtMs, metrics.gyroscopeSampledAtMs);
|
|
1845
|
+
if (sampledAtMs === this.lastBiasSampledAtMs) return;
|
|
1846
|
+
if (this.lastBiasSampledAtMs !== null && sampledAtMs < this.lastBiasSampledAtMs) return;
|
|
1847
|
+
if (!this.biasCalibrated || this.lastBiasSampledAtMs === null) {
|
|
1848
|
+
this.linearAccelerationBias = { ...metrics.rawLinearAcceleration };
|
|
1849
|
+
this.gyroscopeBias = { ...metrics.rawGyroscope };
|
|
1850
|
+
this.biasCalibrated = true;
|
|
1851
|
+
} else {
|
|
1852
|
+
const elapsedMs = Math.max(0, sampledAtMs - this.lastBiasSampledAtMs);
|
|
1853
|
+
const alpha = 1 - Math.exp(-elapsedMs / this.tuning.biasFilterTimeConstantMs);
|
|
1854
|
+
this.linearAccelerationBias = lerpVector(this.linearAccelerationBias, metrics.rawLinearAcceleration, alpha);
|
|
1855
|
+
this.gyroscopeBias = lerpVector(this.gyroscopeBias, metrics.rawGyroscope, alpha);
|
|
1856
|
+
}
|
|
1857
|
+
this.lastBiasSampledAtMs = sampledAtMs;
|
|
1858
|
+
}
|
|
1859
|
+
updateDetectorCandidate(detector, candidate, rejectionReason, statistics, atMs) {
|
|
1860
|
+
const previousCandidate = this.detectorCandidates[detector];
|
|
1861
|
+
if (previousCandidate !== candidate) {
|
|
1862
|
+
if (previousCandidate !== null) this.emitDetectorTransition(detector, "candidate_exited", previousCandidate, null, statistics, atMs);
|
|
1863
|
+
if (candidate !== null) this.emitDetectorTransition(detector, "candidate_entered", candidate, null, statistics, atMs);
|
|
1864
|
+
this.detectorCandidates[detector] = candidate;
|
|
1865
|
+
}
|
|
1866
|
+
if (rejectionReason !== null && rejectionReason !== this.detectorRejections[detector]) this.emitDetectorTransition(detector, "candidate_rejected", null, rejectionReason, statistics, atMs);
|
|
1867
|
+
this.detectorRejections[detector] = rejectionReason;
|
|
1868
|
+
}
|
|
1869
|
+
emitDetectorConfirmation(detector, previousStatus, status, statistics, atMs) {
|
|
1870
|
+
if (status !== null && status !== previousStatus) this.emitDetectorTransition(detector, "candidate_confirmed", status, null, statistics, atMs);
|
|
1871
|
+
}
|
|
1872
|
+
emitDetectorTransition(detector, phase, status, rejectionReason, statistics, atMs) {
|
|
1873
|
+
this.emitDiagnostic({
|
|
1874
|
+
type: "detector_transition",
|
|
1875
|
+
atMs,
|
|
1876
|
+
detector,
|
|
1877
|
+
phase,
|
|
1878
|
+
status,
|
|
1879
|
+
rejectionReason,
|
|
1880
|
+
statistics,
|
|
1881
|
+
tuningVersion: this.tuning.version
|
|
1586
1882
|
});
|
|
1587
|
-
|
|
1883
|
+
}
|
|
1884
|
+
commitSelectedStatus(now) {
|
|
1885
|
+
const selected = this.urgentStatus ?? this.turnStatus ?? this.normalLongitudinalStatus ?? this.baseStatus;
|
|
1886
|
+
this.commit(selected, now, "candidate_confirmed");
|
|
1588
1887
|
}
|
|
1589
1888
|
processUnavailable(category) {
|
|
1590
1889
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "called", { category });
|
|
1591
1890
|
const now = this.clock.now();
|
|
1592
1891
|
if (this.unavailableSinceMs === null) this.unavailableSinceMs = now;
|
|
1593
1892
|
const unavailableForMs = Math.max(0, now - this.unavailableSinceMs);
|
|
1594
|
-
this.
|
|
1595
|
-
this.motionWindow.length = 0;
|
|
1893
|
+
this.clearRecognitionWindows();
|
|
1596
1894
|
this.setCandidate(null, now);
|
|
1597
1895
|
this.emitDiagnostic({
|
|
1598
1896
|
type: "sample_unavailable",
|
|
@@ -1602,97 +1900,39 @@ var DefaultDrivingStatusController = class {
|
|
|
1602
1900
|
tuningVersion: this.tuning.version
|
|
1603
1901
|
});
|
|
1604
1902
|
if (unavailableForMs < this.unavailableFallbackMs || this.fallbackApplied) {
|
|
1605
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "
|
|
1903
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "holding previous status", {
|
|
1904
|
+
category,
|
|
1606
1905
|
unavailableForMs,
|
|
1607
1906
|
fallbackApplied: this.fallbackApplied
|
|
1608
1907
|
});
|
|
1609
1908
|
return;
|
|
1610
1909
|
}
|
|
1611
1910
|
this.fallbackApplied = true;
|
|
1911
|
+
this.baseStatus = "STOPPED";
|
|
1612
1912
|
this.emitDiagnostic({
|
|
1613
1913
|
type: "fallback_to_stopped",
|
|
1614
1914
|
atMs: now,
|
|
1615
1915
|
unavailableForMs,
|
|
1616
1916
|
tuningVersion: this.tuning.version
|
|
1617
1917
|
});
|
|
1618
|
-
this.commit("STOPPED", now, "unavailable_fallback"
|
|
1619
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "returned
|
|
1918
|
+
this.commit("STOPPED", now, "unavailable_fallback");
|
|
1919
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "returned fallback", {
|
|
1620
1920
|
unavailableForMs,
|
|
1621
|
-
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
processCandidate(nextCandidate, now) {
|
|
1625
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "called", {
|
|
1626
|
-
nextCandidate,
|
|
1627
|
-
now,
|
|
1628
|
-
currentStatus: this.status,
|
|
1629
|
-
candidate: this.candidate,
|
|
1630
|
-
candidateConfirmationCount: this.candidateConfirmationCount
|
|
1921
|
+
status: this.status
|
|
1631
1922
|
});
|
|
1632
|
-
if (nextCandidate === this.status) {
|
|
1633
|
-
this.setCandidate(null, now);
|
|
1634
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "returned current status");
|
|
1635
|
-
return;
|
|
1636
|
-
}
|
|
1637
|
-
if (nextCandidate !== this.candidate) {
|
|
1638
|
-
this.setCandidate(nextCandidate, now);
|
|
1639
|
-
this.candidateConfirmationCount = 1;
|
|
1640
|
-
this.heldCandidate = null;
|
|
1641
|
-
} else this.candidateConfirmationCount += 1;
|
|
1642
|
-
if (this.candidateConfirmationCount < this.tuning.confirmationSamples[nextCandidate]) {
|
|
1643
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "awaiting confirmation", {
|
|
1644
|
-
nextCandidate,
|
|
1645
|
-
candidateConfirmationCount: this.candidateConfirmationCount,
|
|
1646
|
-
required: this.tuning.confirmationSamples[nextCandidate]
|
|
1647
|
-
});
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
const elapsedMs = Math.max(0, now - this.statusCommittedAtMs);
|
|
1651
|
-
const remainingMs = Math.max(0, this.tuning.minimumDisplayMs[this.status] - elapsedMs);
|
|
1652
|
-
const canPreempt = CAR_RUNNING_STATUS_PRIORITY[nextCandidate] > CAR_RUNNING_STATUS_PRIORITY[this.status];
|
|
1653
|
-
if (remainingMs > 0 && !canPreempt) {
|
|
1654
|
-
if (this.heldCandidate !== nextCandidate) {
|
|
1655
|
-
this.heldCandidate = nextCandidate;
|
|
1656
|
-
this.emitDiagnostic({
|
|
1657
|
-
type: "status_held_by_min_duration",
|
|
1658
|
-
atMs: now,
|
|
1659
|
-
status: this.status,
|
|
1660
|
-
candidate: nextCandidate,
|
|
1661
|
-
remainingMs,
|
|
1662
|
-
tuningVersion: this.tuning.version
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "held by minimum duration", {
|
|
1666
|
-
nextCandidate,
|
|
1667
|
-
remainingMs
|
|
1668
|
-
});
|
|
1669
|
-
return;
|
|
1670
|
-
}
|
|
1671
|
-
this.commit(nextCandidate, now, "candidate_confirmed");
|
|
1672
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "returned after commit", { snapshot: this.getSnapshot() });
|
|
1673
1923
|
}
|
|
1674
|
-
commit(nextStatus, now, reason
|
|
1675
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "called", {
|
|
1676
|
-
nextStatus,
|
|
1677
|
-
now,
|
|
1678
|
-
reason,
|
|
1679
|
-
force,
|
|
1680
|
-
currentStatus: this.status
|
|
1681
|
-
});
|
|
1682
|
-
if (!force && nextStatus === this.status) {
|
|
1683
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored unchanged status");
|
|
1684
|
-
return;
|
|
1685
|
-
}
|
|
1924
|
+
commit(nextStatus, now, reason) {
|
|
1686
1925
|
if (nextStatus === this.status) {
|
|
1687
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored
|
|
1926
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored unchanged status", {
|
|
1927
|
+
status: this.status,
|
|
1928
|
+
reason
|
|
1929
|
+
});
|
|
1688
1930
|
return;
|
|
1689
1931
|
}
|
|
1690
1932
|
const previousStatus = this.status;
|
|
1691
1933
|
this.status = nextStatus;
|
|
1692
|
-
if (isBaseStatus(nextStatus)) this.baseStatus = nextStatus;
|
|
1693
1934
|
this.statusCommittedAtMs = now;
|
|
1694
1935
|
this.setCandidate(null, now);
|
|
1695
|
-
this.heldCandidate = null;
|
|
1696
1936
|
const event = {
|
|
1697
1937
|
type: "status_changed",
|
|
1698
1938
|
status: nextStatus,
|
|
@@ -1709,53 +1949,53 @@ var DefaultDrivingStatusController = class {
|
|
|
1709
1949
|
reason,
|
|
1710
1950
|
tuningVersion: this.tuning.version
|
|
1711
1951
|
});
|
|
1712
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "
|
|
1713
|
-
event,
|
|
1714
|
-
listenerCount: this.listeners.size,
|
|
1715
|
-
snapshot: this.getSnapshot()
|
|
1716
|
-
});
|
|
1952
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "status changed", { event });
|
|
1717
1953
|
}
|
|
1718
|
-
setCandidate(candidate,
|
|
1719
|
-
|
|
1720
|
-
candidate,
|
|
1721
|
-
now,
|
|
1722
|
-
currentCandidate: this.candidate
|
|
1723
|
-
});
|
|
1724
|
-
if (candidate === this.candidate) {
|
|
1725
|
-
if (candidate === null) this.candidateConfirmationCount = 0;
|
|
1726
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "returned unchanged", { candidateConfirmationCount: this.candidateConfirmationCount });
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1954
|
+
setCandidate(candidate, atMs) {
|
|
1955
|
+
if (candidate === this.candidate) return;
|
|
1729
1956
|
const previousCandidate = this.candidate;
|
|
1730
1957
|
this.candidate = candidate;
|
|
1731
|
-
this.
|
|
1732
|
-
this.heldCandidate = null;
|
|
1958
|
+
this.candidateSinceMs = candidate === null ? null : atMs;
|
|
1733
1959
|
this.emitDiagnostic({
|
|
1734
1960
|
type: "candidate_changed",
|
|
1735
|
-
atMs: now,
|
|
1961
|
+
atMs: this.clock.now(),
|
|
1736
1962
|
candidate,
|
|
1737
1963
|
previousCandidate,
|
|
1738
1964
|
tuningVersion: this.tuning.version
|
|
1739
1965
|
});
|
|
1740
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "
|
|
1966
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "candidate changed", {
|
|
1741
1967
|
candidate,
|
|
1742
|
-
previousCandidate
|
|
1968
|
+
previousCandidate,
|
|
1969
|
+
candidateSinceMs: this.candidateSinceMs
|
|
1743
1970
|
});
|
|
1744
1971
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
motionWindow: this.motionWindow,
|
|
1749
|
-
candidate: this.candidate
|
|
1750
|
-
});
|
|
1751
|
-
this.metricWindow.length = 0;
|
|
1972
|
+
clearRecognitionWindows() {
|
|
1973
|
+
this.longitudinalWindow.length = 0;
|
|
1974
|
+
this.yawWindow.length = 0;
|
|
1752
1975
|
this.motionWindow.length = 0;
|
|
1976
|
+
this.stoppedConditionSinceMs = null;
|
|
1977
|
+
this.drivingConditionSinceMs = null;
|
|
1978
|
+
this.stationaryConfirmed = false;
|
|
1979
|
+
this.urgentStatus = null;
|
|
1980
|
+
this.urgentStartedAtMs = null;
|
|
1981
|
+
this.normalLongitudinalStatus = null;
|
|
1982
|
+
this.longitudinalClearSinceMs = null;
|
|
1983
|
+
this.turnStatus = null;
|
|
1984
|
+
this.turnQuietSinceMs = null;
|
|
1985
|
+
this.oppositeTurnSinceMs = null;
|
|
1986
|
+
for (const detector of Object.keys(this.detectorCandidates)) {
|
|
1987
|
+
this.detectorCandidates[detector] = null;
|
|
1988
|
+
this.detectorRejections[detector] = null;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
resetTransientRecognition() {
|
|
1992
|
+
this.clearRecognitionWindows();
|
|
1753
1993
|
this.candidate = null;
|
|
1754
|
-
this.
|
|
1994
|
+
this.candidateSinceMs = null;
|
|
1755
1995
|
this.unavailableSinceMs = null;
|
|
1756
1996
|
this.fallbackApplied = false;
|
|
1757
|
-
this.
|
|
1758
|
-
|
|
1997
|
+
this.filteredGravity = null;
|
|
1998
|
+
this.lastGravitySampledAtMs = null;
|
|
1759
1999
|
}
|
|
1760
2000
|
emitDiagnostic(event) {
|
|
1761
2001
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.emitDiagnostic", "called", {
|
|
@@ -1763,315 +2003,256 @@ var DefaultDrivingStatusController = class {
|
|
|
1763
2003
|
hasListener: this.onDiagnostic !== void 0
|
|
1764
2004
|
});
|
|
1765
2005
|
if (this.onDiagnostic) safeCall(this.onDiagnostic, event);
|
|
1766
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.emitDiagnostic", "returned", { event });
|
|
1767
2006
|
}
|
|
1768
2007
|
attachLifecycleListeners() {
|
|
1769
|
-
|
|
1770
|
-
managePageLifecycle: this.managePageLifecycle,
|
|
1771
|
-
attached: this.unsubscribePageLifecycle !== null
|
|
1772
|
-
});
|
|
1773
|
-
if (!this.managePageLifecycle || this.unsubscribePageLifecycle !== null) {
|
|
1774
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.attachLifecycleListeners", "ignored");
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
2008
|
+
if (!this.managePageLifecycle || this.unsubscribePageLifecycle !== null) return;
|
|
1777
2009
|
this.unsubscribePageLifecycle = subscribeDrivingPageLifecycle(this.handleLifecyclePause, this.handleLifecycleResume);
|
|
1778
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.attachLifecycleListeners", "returned");
|
|
1779
2010
|
}
|
|
1780
2011
|
detachLifecycleListeners() {
|
|
1781
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.detachLifecycleListeners", "called", { attached: this.unsubscribePageLifecycle !== null });
|
|
1782
2012
|
this.unsubscribePageLifecycle?.();
|
|
1783
2013
|
this.unsubscribePageLifecycle = null;
|
|
1784
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.detachLifecycleListeners", "returned");
|
|
1785
2014
|
}
|
|
1786
2015
|
};
|
|
1787
|
-
const
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2016
|
+
const detectLongitudinalEvidence = (window, tuning) => {
|
|
2017
|
+
const rapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2018
|
+
const normalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2019
|
+
let rapidCandidate = null;
|
|
2020
|
+
let rejectionReason = null;
|
|
2021
|
+
const rapidValues = rapidWindow.map((sample) => sample.longitudinal);
|
|
2022
|
+
const rapidMean = trimmedMean(rapidValues);
|
|
2023
|
+
const rapidDirection = Math.sign(rapidMean);
|
|
2024
|
+
const rapidCoherence = signCoherence(rapidValues, rapidDirection, tuning.longitudinalExitThreshold);
|
|
2025
|
+
const rapidPeak = maximumAbsolute(rapidValues);
|
|
2026
|
+
const longitudinalRms = rootMeanSquare(rapidValues);
|
|
2027
|
+
const verticalRms = rootMeanSquare(rapidWindow.map((sample) => sample.vertical));
|
|
2028
|
+
const reversalWindow = recentMetrics(window, tuning.longitudinalReversalWindowMs, "linearAccelerationSampledAtMs");
|
|
2029
|
+
const hasReversal = containsBothSigns(reversalWindow.map((sample) => sample.longitudinal), tuning.longitudinalThreshold);
|
|
2030
|
+
const rapidStatistics = rapidWindow.length === 0 ? null : {
|
|
2031
|
+
sampleCount: rapidWindow.length,
|
|
2032
|
+
durationMs: metricWindowDuration(rapidWindow, "linearAccelerationSampledAtMs"),
|
|
2033
|
+
mean: rapidMean,
|
|
2034
|
+
peak: rapidPeak,
|
|
2035
|
+
coherence: rapidCoherence,
|
|
2036
|
+
longitudinalRms,
|
|
2037
|
+
verticalRms,
|
|
2038
|
+
hasReversal
|
|
2039
|
+
};
|
|
2040
|
+
if (windowCovers(rapidWindow, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs")) {
|
|
2041
|
+
if (rapidPeak >= tuning.rapidLongitudinalPeakThreshold && Math.abs(rapidMean) >= tuning.rapidLongitudinalMeanThreshold) {
|
|
2042
|
+
if (verticalRms > longitudinalRms * tuning.verticalBumpRatio) rejectionReason = "vertical_bump";
|
|
2043
|
+
else if (hasReversal) rejectionReason = "longitudinal_reversal";
|
|
2044
|
+
else if (rapidDirection !== 0 && rapidCoherence >= tuning.rapidLongitudinalSignCoherence) rapidCandidate = rapidDirection > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING";
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
let normalCandidate = null;
|
|
2048
|
+
let retainedNormal = false;
|
|
2049
|
+
let normalStatistics = null;
|
|
2050
|
+
if (normalWindow.length > 0) {
|
|
2051
|
+
const values = normalWindow.map((sample) => sample.longitudinal);
|
|
2052
|
+
const mean = trimmedMean(values);
|
|
2053
|
+
const direction = Math.sign(mean);
|
|
2054
|
+
const coherence = signCoherence(values, direction, tuning.longitudinalExitThreshold);
|
|
2055
|
+
normalStatistics = {
|
|
2056
|
+
sampleCount: normalWindow.length,
|
|
2057
|
+
durationMs: metricWindowDuration(normalWindow, "linearAccelerationSampledAtMs"),
|
|
2058
|
+
mean,
|
|
2059
|
+
peak: maximumAbsolute(values),
|
|
2060
|
+
coherence
|
|
2061
|
+
};
|
|
2062
|
+
retainedNormal = Math.abs(mean) >= tuning.longitudinalExitThreshold;
|
|
2063
|
+
if (windowCovers(normalWindow, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs") && Math.abs(mean) >= tuning.longitudinalThreshold && coherence >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
rapidCandidate,
|
|
2067
|
+
normalCandidate,
|
|
2068
|
+
retainedNormal,
|
|
2069
|
+
rejectionReason,
|
|
2070
|
+
normalStatistics,
|
|
2071
|
+
rapidStatistics
|
|
2072
|
+
};
|
|
1805
2073
|
};
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
return baseStatus;
|
|
2074
|
+
const detectTurnEvidence = (window, tuning) => {
|
|
2075
|
+
const latest = window[window.length - 1];
|
|
2076
|
+
if (!latest) return {
|
|
2077
|
+
candidate: null,
|
|
2078
|
+
latestYaw: 0,
|
|
2079
|
+
sampledAtMs: 0,
|
|
2080
|
+
rejectionReason: null,
|
|
2081
|
+
statistics: null
|
|
2082
|
+
};
|
|
2083
|
+
const strongWindow = recentMetrics(window, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
|
|
2084
|
+
const strongMean = trimmedMean(strongWindow.map((sample) => sample.yaw));
|
|
2085
|
+
const strongDirection = Math.sign(strongMean);
|
|
2086
|
+
const strongCoherence = signCoherence(strongWindow.map((sample) => sample.yaw), strongDirection, tuning.turnExitYawThreshold);
|
|
2087
|
+
const strong = windowCovers(strongWindow, tuning.turnMinimumSamples, tuning.turnConfirmationMs, "gyroscopeSampledAtMs") && Math.abs(strongMean) >= tuning.turnYawThreshold && strongCoherence >= tuning.turnSignCoherence;
|
|
2088
|
+
const signedAngle = integrateYaw(window);
|
|
2089
|
+
const gentleDirection = Math.sign(signedAngle);
|
|
2090
|
+
const gentleCoherence = signCoherence(window.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold);
|
|
2091
|
+
const yawRms = rootMeanSquare(window.map((sample) => sample.yaw));
|
|
2092
|
+
const rawGyroRms = rootMeanSquare(window.map((sample) => sample.rawGyroMagnitude));
|
|
2093
|
+
const yawDominance = rawGyroRms <= Number.EPSILON ? 0 : Math.min(1, yawRms / rawGyroRms);
|
|
2094
|
+
const lateralRms = rootMeanSquare(window.map((sample) => sample.lateral));
|
|
2095
|
+
const verticalRms = rootMeanSquare(window.map((sample) => sample.vertical));
|
|
2096
|
+
const gentleShape = window.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && gentleCoherence >= tuning.turnSignCoherence;
|
|
2097
|
+
const verticalDominant = verticalRms > Math.max(lateralRms, tuning.turnVerticalNoiseFloor) * tuning.turnVerticalBumpRatio;
|
|
2098
|
+
const yawDominant = yawDominance >= tuning.turnYawDominanceThreshold;
|
|
2099
|
+
const laterallySupported = lateralRms >= tuning.turnLateralEvidenceThreshold;
|
|
2100
|
+
const rejectionReason = !strong && gentleShape && verticalDominant && !yawDominant ? "vertical_bump" : null;
|
|
2101
|
+
const direction = strong ? strongDirection : gentleShape && rejectionReason === null && (yawDominant || laterallySupported) ? gentleDirection : 0;
|
|
2102
|
+
return {
|
|
2103
|
+
candidate: direction === 0 ? null : direction > 0 ? "LEFT_TURN" : "RIGHT_TURN",
|
|
2104
|
+
latestYaw: latest.yaw,
|
|
2105
|
+
sampledAtMs: latest.gyroscopeSampledAtMs,
|
|
2106
|
+
rejectionReason,
|
|
2107
|
+
statistics: {
|
|
2108
|
+
sampleCount: window.length,
|
|
2109
|
+
durationMs: metricWindowDuration(window, "gyroscopeSampledAtMs"),
|
|
2110
|
+
strongMean,
|
|
2111
|
+
strongCoherence,
|
|
2112
|
+
signedAngle,
|
|
2113
|
+
gentleCoherence,
|
|
2114
|
+
yawRms,
|
|
2115
|
+
rawGyroRms,
|
|
2116
|
+
yawDominance,
|
|
2117
|
+
lateralRms,
|
|
2118
|
+
verticalRms
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
1854
2121
|
};
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2122
|
+
const instantaneousCandidate = (metrics, tuning) => {
|
|
2123
|
+
const longitudinalMagnitude = Math.abs(metrics.longitudinal);
|
|
2124
|
+
if (longitudinalMagnitude >= tuning.rapidLongitudinalPeakThreshold && Math.abs(metrics.vertical) <= longitudinalMagnitude * tuning.verticalBumpRatio) return metrics.longitudinal > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING";
|
|
2125
|
+
if (Math.abs(metrics.yaw) >= tuning.turnYawThreshold) return metrics.yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
|
|
2126
|
+
if (longitudinalMagnitude >= tuning.longitudinalThreshold) return metrics.longitudinal > 0 ? "ACCELERATION" : "BRAKING";
|
|
2127
|
+
return null;
|
|
2128
|
+
};
|
|
2129
|
+
const recentMetrics = (values, windowMs, timestampKey) => {
|
|
2130
|
+
const latestAtMs = values[values.length - 1]?.[timestampKey] ?? 0;
|
|
2131
|
+
return values.filter((value) => value[timestampKey] >= latestAtMs - windowMs);
|
|
2132
|
+
};
|
|
2133
|
+
const windowCovers = (values, minimumSamples, durationMs, timestampKey) => {
|
|
2134
|
+
if (values.length < minimumSamples) return false;
|
|
2135
|
+
const first = values[0];
|
|
2136
|
+
return values[values.length - 1][timestampKey] - first[timestampKey] >= durationMs;
|
|
2137
|
+
};
|
|
2138
|
+
const integrateYaw = (values) => {
|
|
2139
|
+
let angle = 0;
|
|
2140
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
2141
|
+
const previous = values[index - 1];
|
|
2142
|
+
const current = values[index];
|
|
2143
|
+
const elapsedSeconds = Math.max(0, current.gyroscopeSampledAtMs - previous.gyroscopeSampledAtMs) / 1e3;
|
|
2144
|
+
angle += (previous.yaw + current.yaw) / 2 * elapsedSeconds;
|
|
2145
|
+
}
|
|
2146
|
+
return angle;
|
|
2147
|
+
};
|
|
2148
|
+
const trimmedMean = (values) => {
|
|
2149
|
+
if (values.length === 0) return 0;
|
|
2150
|
+
if (values.length < 4) return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
2151
|
+
const sorted = [...values].sort((left, right) => left - right).slice(1, -1);
|
|
2152
|
+
return sorted.reduce((sum, value) => sum + value, 0) / sorted.length;
|
|
2153
|
+
};
|
|
2154
|
+
const signCoherence = (values, direction, deadband) => {
|
|
2155
|
+
if (values.length === 0 || direction === 0) return 0;
|
|
2156
|
+
return values.filter((value) => Math.abs(value) >= deadband && Math.sign(value) === direction).length / values.length;
|
|
2157
|
+
};
|
|
2158
|
+
const containsBothSigns = (values, threshold) => values.some((value) => value >= threshold) && values.some((value) => value <= -threshold);
|
|
2159
|
+
const rootMeanSquare = (values) => values.length === 0 ? 0 : Math.sqrt(values.reduce((sum, value) => sum + value ** 2, 0) / values.length);
|
|
2160
|
+
const maximumAbsolute = (values) => values.length === 0 ? 0 : Math.max(...values.map(Math.abs));
|
|
2161
|
+
const metricWindowDuration = (values, timestampKey) => {
|
|
2162
|
+
const first = values[0];
|
|
2163
|
+
const last = values[values.length - 1];
|
|
2164
|
+
return first && last ? Math.max(0, last[timestampKey] - first[timestampKey]) : 0;
|
|
2165
|
+
};
|
|
2166
|
+
const calculateCombinedAxisMad = (samples, minimumSamples) => {
|
|
2167
|
+
if (samples.length < minimumSamples) return null;
|
|
2168
|
+
const medianX = median(samples.map((sample) => sample.value.x));
|
|
2169
|
+
const medianY = median(samples.map((sample) => sample.value.y));
|
|
2170
|
+
const medianZ = median(samples.map((sample) => sample.value.z));
|
|
2171
|
+
const madX = median(samples.map((sample) => Math.abs(sample.value.x - medianX)));
|
|
2172
|
+
const madY = median(samples.map((sample) => Math.abs(sample.value.y - medianY)));
|
|
2173
|
+
const madZ = median(samples.map((sample) => Math.abs(sample.value.z - medianZ)));
|
|
2174
|
+
return Math.hypot(madX, madY, madZ);
|
|
2175
|
+
};
|
|
2176
|
+
const vectorWindowDuration = (values) => {
|
|
2177
|
+
const first = values[0];
|
|
2178
|
+
const last = values[values.length - 1];
|
|
2179
|
+
return first && last ? Math.max(0, last.sampledAtMs - first.sampledAtMs) : 0;
|
|
2180
|
+
};
|
|
2181
|
+
const median = (values) => {
|
|
2182
|
+
if (values.length === 0) return 0;
|
|
2183
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
2184
|
+
const middle = Math.floor(sorted.length / 2);
|
|
2185
|
+
return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
|
|
2186
|
+
};
|
|
2187
|
+
const pushUniqueMetric = (window, metrics, timestampKey, windowMs) => {
|
|
2188
|
+
const latest = window[window.length - 1];
|
|
2189
|
+
if (latest && metrics[timestampKey] <= latest[timestampKey]) return false;
|
|
2190
|
+
if (window.some((value) => value[timestampKey] === metrics[timestampKey])) return false;
|
|
2191
|
+
window.push(metrics);
|
|
2192
|
+
const cutoffMs = metrics[timestampKey] - windowMs;
|
|
2193
|
+
while (window[0] && window[0][timestampKey] < cutoffMs) window.shift();
|
|
2194
|
+
return true;
|
|
1893
2195
|
};
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
gyroMagnitude: 0
|
|
1903
|
-
};
|
|
1904
|
-
for (const value of metrics) {
|
|
1905
|
-
result.longitudinal += value.longitudinal;
|
|
1906
|
-
result.lateral += value.lateral;
|
|
1907
|
-
result.yaw += value.yaw;
|
|
1908
|
-
result.motionIntensity += value.motionIntensity;
|
|
1909
|
-
result.gyroMagnitude += value.gyroMagnitude;
|
|
1910
|
-
}
|
|
1911
|
-
const count = Math.max(1, metrics.length);
|
|
1912
|
-
result.longitudinal /= count;
|
|
1913
|
-
result.lateral /= count;
|
|
1914
|
-
result.yaw /= count;
|
|
1915
|
-
result.motionIntensity /= count;
|
|
1916
|
-
result.gyroMagnitude /= count;
|
|
1917
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "averageMetrics", "returned", { result });
|
|
1918
|
-
return result;
|
|
2196
|
+
const pushUniqueVector = (window, sample, windowMs) => {
|
|
2197
|
+
const latest = window[window.length - 1];
|
|
2198
|
+
if (latest && sample.sampledAtMs <= latest.sampledAtMs) return false;
|
|
2199
|
+
if (window.some((value) => value.sampledAtMs === sample.sampledAtMs)) return false;
|
|
2200
|
+
window.push(sample);
|
|
2201
|
+
const cutoffMs = sample.sampledAtMs - windowMs;
|
|
2202
|
+
while (window[0] && window[0].sampledAtMs < cutoffMs) window.shift();
|
|
2203
|
+
return true;
|
|
1919
2204
|
};
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
});
|
|
1925
|
-
const metric = info?.accelerometer;
|
|
1926
|
-
if (!metric?.available || !Number.isFinite(info.capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) {
|
|
1927
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned", { result: null });
|
|
1928
|
-
return null;
|
|
1929
|
-
}
|
|
1930
|
-
const ageMs = info.capturedAtMs - metric.sampledAtMs;
|
|
1931
|
-
if (ageMs < 0 || ageMs > maxSampleAgeMs) {
|
|
1932
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned stale sample", {
|
|
1933
|
-
result: null,
|
|
1934
|
-
ageMs
|
|
1935
|
-
});
|
|
1936
|
-
return null;
|
|
1937
|
-
}
|
|
2205
|
+
const extractFreshVectorMetric = (metric, capturedAtMs, maxSampleAgeMs) => {
|
|
2206
|
+
if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
|
|
2207
|
+
const ageMs = capturedAtMs - metric.sampledAtMs;
|
|
2208
|
+
if (ageMs < 0 || ageMs > maxSampleAgeMs) return null;
|
|
1938
2209
|
const { x, y, z } = metric.value;
|
|
1939
|
-
|
|
2210
|
+
return [
|
|
1940
2211
|
x,
|
|
1941
2212
|
y,
|
|
1942
2213
|
z
|
|
1943
|
-
].every(Number.isFinite)
|
|
1944
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned invalid vector", { result: null });
|
|
1945
|
-
return null;
|
|
1946
|
-
}
|
|
1947
|
-
const result = {
|
|
2214
|
+
].every(Number.isFinite) ? {
|
|
1948
2215
|
sampledAtMs: metric.sampledAtMs,
|
|
1949
2216
|
value: metric.value
|
|
1950
|
-
};
|
|
1951
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned", { result });
|
|
1952
|
-
return result;
|
|
1953
|
-
};
|
|
1954
|
-
const combinedAxisStandardDeviation = (samples) => {
|
|
1955
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "combinedAxisStandardDeviation", "called", { samples });
|
|
1956
|
-
const count = samples.length;
|
|
1957
|
-
let meanX = 0;
|
|
1958
|
-
let meanY = 0;
|
|
1959
|
-
let meanZ = 0;
|
|
1960
|
-
for (const sample of samples) {
|
|
1961
|
-
meanX += sample.value.x;
|
|
1962
|
-
meanY += sample.value.y;
|
|
1963
|
-
meanZ += sample.value.z;
|
|
1964
|
-
}
|
|
1965
|
-
meanX /= count;
|
|
1966
|
-
meanY /= count;
|
|
1967
|
-
meanZ /= count;
|
|
1968
|
-
let varianceX = 0;
|
|
1969
|
-
let varianceY = 0;
|
|
1970
|
-
let varianceZ = 0;
|
|
1971
|
-
for (const sample of samples) {
|
|
1972
|
-
varianceX += (sample.value.x - meanX) ** 2;
|
|
1973
|
-
varianceY += (sample.value.y - meanY) ** 2;
|
|
1974
|
-
varianceZ += (sample.value.z - meanZ) ** 2;
|
|
1975
|
-
}
|
|
1976
|
-
const result = Math.sqrt((varianceX + varianceY + varianceZ) / count);
|
|
1977
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "combinedAxisStandardDeviation", "returned", {
|
|
1978
|
-
meanX,
|
|
1979
|
-
meanY,
|
|
1980
|
-
meanZ,
|
|
1981
|
-
varianceX,
|
|
1982
|
-
varianceY,
|
|
1983
|
-
varianceZ,
|
|
1984
|
-
result
|
|
1985
|
-
});
|
|
1986
|
-
return result;
|
|
2217
|
+
} : null;
|
|
1987
2218
|
};
|
|
2219
|
+
const observationTime = (metrics) => Math.max(metrics.linearAccelerationSampledAtMs, metrics.gyroscopeSampledAtMs);
|
|
2220
|
+
const lerpVector = (from, to, alpha) => ({
|
|
2221
|
+
x: from.x + (to.x - from.x) * alpha,
|
|
2222
|
+
y: from.y + (to.y - from.y) * alpha,
|
|
2223
|
+
z: from.z + (to.z - from.z) * alpha
|
|
2224
|
+
});
|
|
1988
2225
|
const defaultSensorProvider = () => {
|
|
1989
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "called");
|
|
1990
2226
|
const runtimeGlobal = globalThis;
|
|
1991
|
-
if (typeof runtimeGlobal.unifiedSensorInfo !== "function")
|
|
1992
|
-
|
|
1993
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "rejected", error);
|
|
1994
|
-
return Promise.reject(error);
|
|
1995
|
-
}
|
|
1996
|
-
try {
|
|
1997
|
-
const result = runtimeGlobal.unifiedSensorInfo();
|
|
1998
|
-
result.then((payload) => dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "resolved", { payload }), (error) => dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "rejected", error));
|
|
1999
|
-
return result;
|
|
2000
|
-
} catch (error) {
|
|
2001
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "threw", error);
|
|
2002
|
-
throw error;
|
|
2003
|
-
}
|
|
2227
|
+
if (typeof runtimeGlobal.unifiedSensorInfo !== "function") return Promise.reject(/* @__PURE__ */ new Error("window.unifiedSensorInfo is unavailable"));
|
|
2228
|
+
return runtimeGlobal.unifiedSensorInfo();
|
|
2004
2229
|
};
|
|
2005
2230
|
const systemClock = {
|
|
2006
|
-
now: () =>
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
return result;
|
|
2010
|
-
},
|
|
2011
|
-
setTimeout: (callback, delayMs) => {
|
|
2012
|
-
const result = globalThis.setTimeout(callback, delayMs);
|
|
2013
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.setTimeout", "returned", {
|
|
2014
|
-
delayMs,
|
|
2015
|
-
result
|
|
2016
|
-
});
|
|
2017
|
-
return result;
|
|
2018
|
-
},
|
|
2019
|
-
clearTimeout: (handle) => {
|
|
2020
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.clearTimeout", "called", { handle });
|
|
2021
|
-
globalThis.clearTimeout(handle);
|
|
2022
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.clearTimeout", "returned");
|
|
2023
|
-
}
|
|
2024
|
-
};
|
|
2025
|
-
const isOppositeTurn = (candidate, current) => {
|
|
2026
|
-
const result = candidate === "LEFT_TURN" && current === "RIGHT_TURN" || candidate === "RIGHT_TURN" && current === "LEFT_TURN";
|
|
2027
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "isOppositeTurn", "returned", {
|
|
2028
|
-
candidate,
|
|
2029
|
-
current,
|
|
2030
|
-
result
|
|
2031
|
-
});
|
|
2032
|
-
return result;
|
|
2033
|
-
};
|
|
2034
|
-
const isBaseStatus = (status) => {
|
|
2035
|
-
const result = status === "STOPPED" || status === "STEADY_DRIVING";
|
|
2036
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "isBaseStatus", "returned", {
|
|
2037
|
-
status,
|
|
2038
|
-
result
|
|
2039
|
-
});
|
|
2040
|
-
return result;
|
|
2041
|
-
};
|
|
2042
|
-
const isAbortError$1 = (error) => {
|
|
2043
|
-
const result = error instanceof Error && error.name === "AbortError";
|
|
2044
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "isAbortError", "returned", {
|
|
2045
|
-
error,
|
|
2046
|
-
result
|
|
2047
|
-
});
|
|
2048
|
-
return result;
|
|
2231
|
+
now: () => Date.now(),
|
|
2232
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
2233
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle)
|
|
2049
2234
|
};
|
|
2235
|
+
const isBaseStatus = (status) => status === "STOPPED" || status === "STEADY_DRIVING";
|
|
2236
|
+
const isAbortError$1 = (error) => error instanceof Error && error.name === "AbortError";
|
|
2050
2237
|
const requirePositiveFinite = (name, value) => {
|
|
2051
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "called", {
|
|
2052
|
-
name,
|
|
2053
|
-
value
|
|
2054
|
-
});
|
|
2055
2238
|
if (!Number.isFinite(value) || value <= 0) {
|
|
2056
2239
|
const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
|
|
2057
2240
|
dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "threw", error);
|
|
2058
2241
|
throw error;
|
|
2059
2242
|
}
|
|
2060
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "returned", { result: value });
|
|
2061
2243
|
return value;
|
|
2062
2244
|
};
|
|
2063
2245
|
const safeCall = (callback, value) => {
|
|
2064
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "called", { value });
|
|
2065
2246
|
try {
|
|
2066
2247
|
callback(value);
|
|
2067
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "returned");
|
|
2068
2248
|
} catch (error) {
|
|
2069
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw",
|
|
2249
|
+
dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", {
|
|
2250
|
+
error,
|
|
2251
|
+
value
|
|
2252
|
+
});
|
|
2070
2253
|
}
|
|
2071
2254
|
};
|
|
2072
|
-
const noop = () =>
|
|
2073
|
-
dialSdkDebugWarn(DEBUG_MODULE$1, "noop", "called and returned");
|
|
2074
|
-
};
|
|
2255
|
+
const noop = () => void 0;
|
|
2075
2256
|
//#endregion
|
|
2076
2257
|
//#region src/sensor/driving_expression_player.ts
|
|
2077
2258
|
const DEBUG_MODULE = "driving_expression_player.ts";
|
|
@@ -3351,4 +3532,4 @@ const isRecord = (value) => {
|
|
|
3351
3532
|
return result;
|
|
3352
3533
|
};
|
|
3353
3534
|
//#endregion
|
|
3354
|
-
export { BATTERY_HEALTH_LABELS, CAR_RUNNING_LABELS, CAR_RUNNING_STATUSES, CAR_RUNNING_STATUS_PRIORITY, DEFAULT_DRIVING_STATUS_TUNING, DrivingExpressionResolutionError, DrivingExpressionsConfigError, OFFICIAL_DRIVING_EXPRESSIONS_CONFIG, OFFICIAL_DRIVING_EXPRESSION_FALLBACKS,
|
|
3535
|
+
export { BATTERY_HEALTH_LABELS, CAR_RUNNING_LABELS, CAR_RUNNING_STATUSES, CAR_RUNNING_STATUS_PRIORITY, DEFAULT_DRIVING_STATUS_TUNING, DrivingExpressionResolutionError, DrivingExpressionsConfigError, OFFICIAL_DRIVING_EXPRESSIONS_CONFIG, OFFICIAL_DRIVING_EXPRESSION_FALLBACKS, buildVehicleFrame, classifyDrivingSnapshot, createDrivingExpressionPlayer, createDrivingStatusController, extractDrivingSnapshotMetrics, isPublicDrivingMediaUrl, parseDrivingExpressionsConfig, readDrivingExpressionsConfig, resolveDrivingExpression, resolveDrivingExpressionStrict, resolveDrivingStatusTuning, tryParseDrivingExpressionsConfig, unifiedSensorInfo, waitForDrivingExpressionsConfig };
|