@ziztechnology/dial-library 0.0.13 → 0.0.15
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 +9 -9
- package/dist/index.d.mts +11 -9
- package/dist/index.mjs +163 -137
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`@ziztechnology/dial-library` 为 Toooony 表盘提供统一的设备信息和行车状态 API。你可以用它读取传感器,也可以直接订阅经过平滑、确认和回退处理后的行车状态。
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
> SDK 最低支持 Toooony Runtime `v1.5.
|
|
6
|
+
> SDK 最低支持 Toooony Runtime `v1.5.22`。行车状态传感器 Bridge 仅向审核通过的 `PACKAGED_H5` 开放。第三方表盘应使用本 SDK,不要直接依赖 Runtime 注入到 `window` 上的内部方法。
|
|
7
7
|
|
|
8
8
|
## 安装
|
|
9
9
|
|
|
@@ -223,9 +223,9 @@ unsubscribe();
|
|
|
223
223
|
controller.destroy();
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
控制器默认以 200ms 为目标节拍读取,并保证同一时间只有一次 Bridge 读取。下一轮会扣除上一轮 Bridge 耗时;Bridge 本身超过 200ms 时则在返回后立即开始下一轮,避免再叠加固定等待。所有确认都使用传感器时间而不是轮询次数;相同 `sampledAtMs` 的重复数据不会推进窗口。正常 5Hz 采样仍需满足配置的 5/4/3 个样本,较慢的 Bridge 可以用至少 3 个独立观测覆盖同一确认时长;相邻有效观测间隔达到 1 秒时会重新建立动作窗口,不能跨越长数据空洞确认。
|
|
227
227
|
|
|
228
|
-
静止和匀速使用 accelerometer 的 1 秒三轴 MAD
|
|
228
|
+
静止和匀速使用 accelerometer 的 1 秒三轴 MAD 稳健波动窗口;正常 5Hz 采样至少需要 5 个唯一样本,慢 Bridge 使用相同的时间覆盖规则。运动量不高于 `0.12 m/s²` 且陀螺仪足够稳定,连续 1.5 秒才进入 `STOPPED`;运动量达到 `0.18 m/s²` 或陀螺仪出现持续运动后,从当前运动样本开始连续 0.6 秒才进入 `STEADY_DRIVING`。中间区间保持原基础状态,停车时一次短促晃动不会立即切回行驶。
|
|
229
229
|
|
|
230
230
|
普通加速和刹车要求 0.8 秒内至少 4/5 的样本方向一致;急加速和急刹要求峰值、0.6 秒持续均值及方向一致性同时成立,并拒绝垂直能量占主导或短时间反号的颠簸。车辆刚出现方向稳定的物理偏航后,强转向路径要求 0.4 秒内至少 3 个唯一样本,窗口均值达到 `0.08 rad/s`;在默认轮询频率下通常会在 0.4–0.6 秒内触发。转向也可以由累计转角触发,并锁存到回正结束。算法不会预测车辆尚未产生转动时的驾驶意图。最终优先级为:急加减速、已锁存转向、普通加减速、基础状态。
|
|
231
231
|
|
|
@@ -261,7 +261,7 @@ const controller = createDrivingStatusController({
|
|
|
261
261
|
motionMinimumSamples: 6,
|
|
262
262
|
drivingMotionThreshold: 0.2,
|
|
263
263
|
stoppedMotionThreshold: 0.11,
|
|
264
|
-
stoppedConfirmationMs:
|
|
264
|
+
stoppedConfirmationMs: 1_500,
|
|
265
265
|
longitudinalThreshold: 0.75,
|
|
266
266
|
turnYawDominanceThreshold: 0.65,
|
|
267
267
|
turnLateralEvidenceThreshold: 0.3,
|
|
@@ -277,7 +277,7 @@ const controller = createDrivingStatusController({
|
|
|
277
277
|
|
|
278
278
|
检测器状态变化时还会发送 `detector_transition`,其 `phase` 为 `candidate_entered`、`candidate_rejected`、`candidate_confirmed` 或 `candidate_exited`。该事件携带当时的完整 `statistics`,可直接用于真车回放调参;同一状态不会逐轮询重复发送。
|
|
279
279
|
|
|
280
|
-
惯性传感器无法严格区分“完全静止”和“没有任何振动的理想匀速直线运动”。默认算法使用稳健振动统计、陀螺仪扰动和 5 秒确认近似判断,修改阈值后应使用完整路测过程回放验证。
|
|
280
|
+
惯性传感器无法严格区分“完全静止”和“没有任何振动的理想匀速直线运动”。默认算法使用稳健振动统计、陀螺仪扰动和 1.5 秒确认近似判断,修改阈值后应使用完整路测过程回放验证。
|
|
281
281
|
|
|
282
282
|
### 驱动行车表情
|
|
283
283
|
|
|
@@ -328,7 +328,7 @@ TGS 使用 SDK 内置的 SVG light 播放器,并且只在首次显示 TGS 素
|
|
|
328
328
|
|
|
329
329
|
### 传感器不可用时回退到静止
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
控制器遇到一次读取失败、非法快照或过期样本时,会暂时保持最后一个已提交状态和仍在有效时间范围内的识别窗口,避免页面闪烁或丢失转向、刹车证据。连续不可用达到 3 秒后,控制器才清空窗口并提交 `STOPPED`:
|
|
332
332
|
|
|
333
333
|
```ts
|
|
334
334
|
const controller = createDrivingStatusController({
|
|
@@ -347,7 +347,7 @@ controller.subscribe((event) => {
|
|
|
347
347
|
});
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
-
|
|
350
|
+
传感器恢复后,控制器会清除不可用状态;短缺样前后的时间窗仍需满足独立观测数、持续时间和小于 1 秒的相邻间隔约束,不会把跨越长空洞的数据或单个样本提交给 UI。`unavailableFallbackMs` 必须是大于 0 的有限数值。
|
|
351
351
|
|
|
352
352
|
### 素材失败时使用显式回退
|
|
353
353
|
|
|
@@ -398,7 +398,7 @@ const player = createDrivingExpressionPlayer(container, {
|
|
|
398
398
|
|
|
399
399
|
### 配置本身无效时
|
|
400
400
|
|
|
401
|
-
`readDrivingExpressionsConfig()`
|
|
401
|
+
`readDrivingExpressionsConfig()` 只读取 Runtime 注入的 `__TOOOONY_DRIVING_EXPRESSIONS__`。配置不存在或无效时返回 `null`,不会替你补齐默认值:
|
|
402
402
|
|
|
403
403
|
```ts
|
|
404
404
|
const config = readDrivingExpressionsConfig();
|
|
@@ -408,7 +408,7 @@ if (!config) {
|
|
|
408
408
|
}
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
|
|
411
|
+
Runtime v1.5.22 及以上的 PACKAGED_H5 会在 document-start 阶段完成配置注入,因此优先使用上述同步读取。若需要容忍异常的晚注入时序,可以进行一次有上限的异步等待:
|
|
412
412
|
|
|
413
413
|
```ts
|
|
414
414
|
import { waitForDrivingExpressionsConfig } from '@ziztechnology/dial-library';
|
package/dist/index.d.mts
CHANGED
|
@@ -4,10 +4,6 @@ type BatteryStatus = 'CHARGING' | 'DISCHARGING' | 'FULL' | 'NOT_CHARGING' | 'UNK
|
|
|
4
4
|
type BatteryPlugged = 'AC' | 'USB' | 'WIRELESS' | 'DOCK' | 'NONE';
|
|
5
5
|
type BatteryHealth = 'GOOD' | 'OVERHEAT' | 'DEAD' | 'OVER_VOLTAGE' | 'UNSPECIFIED_FAILURE' | 'COLD' | 'UNKNOWN';
|
|
6
6
|
declare const BATTERY_HEALTH_LABELS: Record<BatteryHealth, string>;
|
|
7
|
-
type CarRunningStatus = 'STOPPED' | 'STEADY_DRIVING' | 'ACCELERATION' | 'RAPID_ACCELERATION' | 'BRAKING' | 'SUDDEN_BRAKING' | 'LEFT_TURN' | 'RIGHT_TURN';
|
|
8
|
-
declare const CAR_RUNNING_LABELS: Record<CarRunningStatus, string>;
|
|
9
|
-
declare const CAR_RUNNING_STATUSES: readonly ["STOPPED", "STEADY_DRIVING", "ACCELERATION", "RAPID_ACCELERATION", "BRAKING", "SUDDEN_BRAKING", "LEFT_TURN", "RIGHT_TURN"];
|
|
10
|
-
declare const CAR_RUNNING_STATUS_PRIORITY: Readonly<Record<CarRunningStatus, number>>;
|
|
11
7
|
//#endregion
|
|
12
8
|
//#region src/sensor/unified_info.d.ts
|
|
13
9
|
interface Vector3Value {
|
|
@@ -65,7 +61,13 @@ interface UnifiedSensorInfoPayload {
|
|
|
65
61
|
}
|
|
66
62
|
declare const unifiedSensorInfo: () => Promise<UnifiedSensorInfoPayload>;
|
|
67
63
|
//#endregion
|
|
68
|
-
//#region src/
|
|
64
|
+
//#region src/driving_expression/constants.d.ts
|
|
65
|
+
type CarRunningStatus = 'STOPPED' | 'STEADY_DRIVING' | 'ACCELERATION' | 'RAPID_ACCELERATION' | 'BRAKING' | 'SUDDEN_BRAKING' | 'LEFT_TURN' | 'RIGHT_TURN';
|
|
66
|
+
declare const CAR_RUNNING_LABELS: Record<CarRunningStatus, string>;
|
|
67
|
+
declare const CAR_RUNNING_STATUSES: readonly ["STOPPED", "STEADY_DRIVING", "ACCELERATION", "RAPID_ACCELERATION", "BRAKING", "SUDDEN_BRAKING", "LEFT_TURN", "RIGHT_TURN"];
|
|
68
|
+
declare const CAR_RUNNING_STATUS_PRIORITY: Readonly<Record<CarRunningStatus, number>>;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/driving_expression/check_status.d.ts
|
|
69
71
|
interface DrivingStatusTuning {
|
|
70
72
|
version: string;
|
|
71
73
|
maxSampleAgeMs: number;
|
|
@@ -155,7 +157,7 @@ declare const resolveDrivingStatusTuning: (overrides?: DrivingStatusTuningOverri
|
|
|
155
157
|
declare const extractDrivingSnapshotMetrics: (info: UnifiedSensorInfoPayload, tuning?: Pick<DrivingStatusTuning, "maxSampleAgeMs" | "vehicleRightMinimumNorm">, correction?: DrivingMetricsCorrection) => DrivingSnapshotMetrics | null;
|
|
156
158
|
declare const buildVehicleFrame: (gravity: Vector3Value, rightMinimumNorm?: number) => DrivingVehicleFrame | null;
|
|
157
159
|
//#endregion
|
|
158
|
-
//#region src/
|
|
160
|
+
//#region src/driving_expression/driving_expressions.d.ts
|
|
159
161
|
interface MediaLibraryRef {
|
|
160
162
|
mediaAssetId?: number;
|
|
161
163
|
}
|
|
@@ -228,7 +230,7 @@ interface WaitForDrivingExpressionsConfigOptions {
|
|
|
228
230
|
declare const parseDrivingExpressionsConfig: (raw: unknown, options?: ParseDrivingExpressionsOptions) => CompleteDrivingExpressionsConfig;
|
|
229
231
|
declare const tryParseDrivingExpressionsConfig: (raw: unknown, options?: ParseDrivingExpressionsOptions) => CompleteDrivingExpressionsConfig | null;
|
|
230
232
|
/**
|
|
231
|
-
* Reads Runtime's typed
|
|
233
|
+
* Reads Runtime's typed driving-expressions injection.
|
|
232
234
|
* It never installs official fallbacks implicitly.
|
|
233
235
|
*/
|
|
234
236
|
declare const readDrivingExpressionsConfig: (raw?: unknown, options?: ParseDrivingExpressionsOptions) => CompleteDrivingExpressionsConfig | null;
|
|
@@ -243,7 +245,7 @@ declare const resolveDrivingExpression: (config: DrivingExpressionsConfigInput,
|
|
|
243
245
|
declare const resolveDrivingExpressionStrict: (config: DrivingExpressionsConfigInput, status: CarRunningStatus, fallbacks?: Partial<Readonly<Record<CarRunningStatus, StructuredDrivingExpressionMedia>>>) => DrivingExpressionMedia;
|
|
244
246
|
declare const isPublicDrivingMediaUrl: (value: string) => boolean;
|
|
245
247
|
//#endregion
|
|
246
|
-
//#region src/
|
|
248
|
+
//#region src/driving_expression/driving_status_controller.d.ts
|
|
247
249
|
type DrivingStatusChangeReason = 'candidate_confirmed' | 'unavailable_fallback';
|
|
248
250
|
interface DrivingStatusEvent {
|
|
249
251
|
type: 'status_changed';
|
|
@@ -386,7 +388,7 @@ interface DrivingStatusController {
|
|
|
386
388
|
}
|
|
387
389
|
declare const createDrivingStatusController: (options?: DrivingStatusControllerOptions) => DrivingStatusController;
|
|
388
390
|
//#endregion
|
|
389
|
-
//#region src/
|
|
391
|
+
//#region src/driving_expression/driving_expression_player.d.ts
|
|
390
392
|
type DrivingExpressionPlayerErrorCategory = 'invalid_config' | 'network' | 'timeout' | 'decode' | 'unsupported';
|
|
391
393
|
type DrivingExpressionPlayerDiagnosticEvent = {
|
|
392
394
|
type: 'media_ready';
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Gunzip } from "fflate";
|
|
2
|
-
const resolveLogIntervalMs = (intervalMs = void 0) => typeof intervalMs === "number" && Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs :
|
|
2
|
+
const resolveLogIntervalMs = (intervalMs = void 0) => typeof intervalMs === "number" && Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 200;
|
|
3
3
|
//#endregion
|
|
4
4
|
//#region src/helper/debugger.ts
|
|
5
5
|
const logIntervalMs = resolveLogIntervalMs();
|
|
6
6
|
const lastLoggedAtByType = /* @__PURE__ */ new Map();
|
|
7
|
+
let hasLoggedSdkVersion = false;
|
|
7
8
|
const createLogTypeKey = (moduleName, scope, phase) => JSON.stringify([
|
|
8
9
|
moduleName,
|
|
9
10
|
scope,
|
|
@@ -38,11 +39,76 @@ const dialSdkDebugWarn = (moduleName, scope, phase, data) => {
|
|
|
38
39
|
if (!shouldLog(moduleName, scope, phase)) return;
|
|
39
40
|
const message = `[DIAL_SDK_DEBUG - ${moduleName}] ${scope}: ${phase}`;
|
|
40
41
|
try {
|
|
42
|
+
if (!hasLoggedSdkVersion) {
|
|
43
|
+
console.log(`[DIAL_SDK_DEBUG] SDK version: 0.0.15`);
|
|
44
|
+
hasLoggedSdkVersion = true;
|
|
45
|
+
}
|
|
41
46
|
if (data === void 0) console.log(message);
|
|
42
47
|
else console.log(message, stringifyDebugData(data));
|
|
43
48
|
} catch {}
|
|
44
49
|
};
|
|
45
50
|
//#endregion
|
|
51
|
+
//#region src/sensor/unified_info.ts
|
|
52
|
+
const DEBUG_MODULE$5 = "unified_info.ts";
|
|
53
|
+
const unifiedSensorInfo = () => {
|
|
54
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "called");
|
|
55
|
+
try {
|
|
56
|
+
return window.unifiedSensorInfo().then((payload) => {
|
|
57
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "resolved", { payload });
|
|
58
|
+
return payload;
|
|
59
|
+
}, (error) => {
|
|
60
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "rejected", error);
|
|
61
|
+
throw error;
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "threw", error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/sensor/constants.ts
|
|
70
|
+
const BATTERY_HEALTH_LABELS = {
|
|
71
|
+
GOOD: "良好",
|
|
72
|
+
OVERHEAT: "过热",
|
|
73
|
+
DEAD: "失效",
|
|
74
|
+
OVER_VOLTAGE: "电压过高",
|
|
75
|
+
UNSPECIFIED_FAILURE: "异常",
|
|
76
|
+
COLD: "温度过低",
|
|
77
|
+
UNKNOWN: "未知"
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/driving_expression/constants.ts
|
|
81
|
+
const CAR_RUNNING_LABELS = {
|
|
82
|
+
STOPPED: "静止",
|
|
83
|
+
STEADY_DRIVING: "匀速行驶",
|
|
84
|
+
SUDDEN_BRAKING: "急刹车",
|
|
85
|
+
BRAKING: "刹车",
|
|
86
|
+
LEFT_TURN: "左转",
|
|
87
|
+
RIGHT_TURN: "右转",
|
|
88
|
+
ACCELERATION: "加速",
|
|
89
|
+
RAPID_ACCELERATION: "急加速"
|
|
90
|
+
};
|
|
91
|
+
const CAR_RUNNING_STATUSES = [
|
|
92
|
+
"STOPPED",
|
|
93
|
+
"STEADY_DRIVING",
|
|
94
|
+
"ACCELERATION",
|
|
95
|
+
"RAPID_ACCELERATION",
|
|
96
|
+
"BRAKING",
|
|
97
|
+
"SUDDEN_BRAKING",
|
|
98
|
+
"LEFT_TURN",
|
|
99
|
+
"RIGHT_TURN"
|
|
100
|
+
];
|
|
101
|
+
const CAR_RUNNING_STATUS_PRIORITY = {
|
|
102
|
+
STOPPED: 0,
|
|
103
|
+
STEADY_DRIVING: 0,
|
|
104
|
+
ACCELERATION: 1,
|
|
105
|
+
BRAKING: 2,
|
|
106
|
+
LEFT_TURN: 3,
|
|
107
|
+
RIGHT_TURN: 3,
|
|
108
|
+
RAPID_ACCELERATION: 4,
|
|
109
|
+
SUDDEN_BRAKING: 5
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
46
112
|
//#region src/helper/sensor.ts
|
|
47
113
|
const extractFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
|
|
48
114
|
if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
|
|
@@ -82,8 +148,8 @@ const isRecord = (value, debug) => {
|
|
|
82
148
|
return result;
|
|
83
149
|
};
|
|
84
150
|
//#endregion
|
|
85
|
-
//#region src/
|
|
86
|
-
const DEBUG_MODULE$
|
|
151
|
+
//#region src/driving_expression/check_status.ts
|
|
152
|
+
const DEBUG_MODULE$4 = "check_status.ts";
|
|
87
153
|
const DEVICE_RIGHT = {
|
|
88
154
|
x: 1,
|
|
89
155
|
y: 0,
|
|
@@ -95,7 +161,7 @@ const ZERO_VECTOR$1 = {
|
|
|
95
161
|
z: 0
|
|
96
162
|
};
|
|
97
163
|
const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
98
|
-
version: "driving-status-calibrated-2026-07-17",
|
|
164
|
+
version: "driving-status-calibrated-2026-07-17-r2",
|
|
99
165
|
maxSampleAgeMs: 1e3,
|
|
100
166
|
gravityFilterTimeConstantMs: 1e3,
|
|
101
167
|
biasFilterTimeConstantMs: 1e4,
|
|
@@ -108,7 +174,7 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
|
108
174
|
stoppedRawGyroThreshold: .065,
|
|
109
175
|
drivingGyroThreshold: .04,
|
|
110
176
|
drivingRawGyroThreshold: .08,
|
|
111
|
-
stoppedConfirmationMs:
|
|
177
|
+
stoppedConfirmationMs: 1500,
|
|
112
178
|
drivingConfirmationMs: 600,
|
|
113
179
|
longitudinalWindowMs: 800,
|
|
114
180
|
longitudinalMinimumSamples: 5,
|
|
@@ -139,14 +205,14 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
|
139
205
|
turnClosureWindowMs: 1500
|
|
140
206
|
});
|
|
141
207
|
const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
142
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
208
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "called", {
|
|
143
209
|
info,
|
|
144
210
|
tuningOverrides
|
|
145
211
|
});
|
|
146
212
|
const tuning = resolveDrivingStatusTuning(tuningOverrides);
|
|
147
213
|
const metrics = extractDrivingSnapshotMetrics(info, tuning);
|
|
148
214
|
if (metrics === null) {
|
|
149
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
215
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result: null });
|
|
150
216
|
return null;
|
|
151
217
|
}
|
|
152
218
|
const { candidateStatus, rejectionReason } = classifyInstantaneousEvidence(metrics, tuning);
|
|
@@ -160,11 +226,11 @@ const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
|
160
226
|
maximumSampleAgeMs: Math.max(info.capturedAtMs - metrics.linearAccelerationSampledAtMs, info.capturedAtMs - metrics.gyroscopeSampledAtMs, info.capturedAtMs - metrics.gravitySampledAtMs)
|
|
161
227
|
}
|
|
162
228
|
};
|
|
163
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
229
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result });
|
|
164
230
|
return result;
|
|
165
231
|
};
|
|
166
232
|
const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
167
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
233
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "called", { overrides });
|
|
168
234
|
const tuning = {
|
|
169
235
|
...DEFAULT_DRIVING_STATUS_TUNING,
|
|
170
236
|
...overrides
|
|
@@ -204,7 +270,7 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
|
204
270
|
"turnExitConfirmationMs",
|
|
205
271
|
"turnClosureWindowMs"
|
|
206
272
|
]) requirePositiveFinite(field, tuning[field], {
|
|
207
|
-
moduleName: DEBUG_MODULE$
|
|
273
|
+
moduleName: DEBUG_MODULE$4,
|
|
208
274
|
scope: "assertPositiveFinite"
|
|
209
275
|
});
|
|
210
276
|
for (const field of [
|
|
@@ -229,17 +295,17 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
|
229
295
|
if (tuning.rapidLongitudinalMeanThreshold <= tuning.longitudinalThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalMeanThreshold must be greater than longitudinalThreshold");
|
|
230
296
|
if (tuning.rapidLongitudinalPeakThreshold < tuning.rapidLongitudinalMeanThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalPeakThreshold must be greater than or equal to rapidLongitudinalMeanThreshold");
|
|
231
297
|
if (tuning.turnYawThreshold <= tuning.turnExitYawThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "turnYawThreshold must be greater than turnExitYawThreshold");
|
|
232
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
298
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "returned", { tuning });
|
|
233
299
|
return tuning;
|
|
234
300
|
};
|
|
235
301
|
const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING, correction = {}) => {
|
|
236
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
302
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "called", {
|
|
237
303
|
info,
|
|
238
304
|
tuning,
|
|
239
305
|
correction
|
|
240
306
|
});
|
|
241
307
|
if (!info || !Number.isFinite(info.capturedAtMs)) {
|
|
242
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
308
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
|
|
243
309
|
return null;
|
|
244
310
|
}
|
|
245
311
|
const linearAcceleration = extractFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
|
|
@@ -250,7 +316,7 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
250
316
|
sampledAtMs: gravityMetric.sampledAtMs
|
|
251
317
|
} : gravityMetric);
|
|
252
318
|
if (linearAcceleration === null || gyroscope === null || gravity === null) {
|
|
253
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
319
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
|
|
254
320
|
linearAcceleration,
|
|
255
321
|
gyroscope,
|
|
256
322
|
gravity
|
|
@@ -259,7 +325,7 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
259
325
|
}
|
|
260
326
|
const frame = buildVehicleFrame(gravity.value, tuning.vehicleRightMinimumNorm);
|
|
261
327
|
if (frame === null) {
|
|
262
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
328
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
|
|
263
329
|
gravity: gravity.value,
|
|
264
330
|
vehicleRightMinimumNorm: tuning.vehicleRightMinimumNorm
|
|
265
331
|
});
|
|
@@ -282,28 +348,28 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
282
348
|
rawGyroscope: gyroscope.value,
|
|
283
349
|
frame
|
|
284
350
|
};
|
|
285
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
351
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned", { result });
|
|
286
352
|
return result;
|
|
287
353
|
};
|
|
288
354
|
const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TUNING.vehicleRightMinimumNorm) => {
|
|
289
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
355
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "called", {
|
|
290
356
|
gravity,
|
|
291
357
|
rightMinimumNorm
|
|
292
358
|
});
|
|
293
359
|
const gravityDirection = normalize(gravity);
|
|
294
360
|
if (gravityDirection === null) {
|
|
295
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
361
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid gravity", { result: null });
|
|
296
362
|
return null;
|
|
297
363
|
}
|
|
298
364
|
const up = gravityDirection;
|
|
299
365
|
const projectedRight = subtract(DEVICE_RIGHT, scale(up, dot(DEVICE_RIGHT, up)));
|
|
300
366
|
if (magnitude(projectedRight) < rightMinimumNorm) {
|
|
301
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
367
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned degenerate right axis", { result: null });
|
|
302
368
|
return null;
|
|
303
369
|
}
|
|
304
370
|
const right = normalize(projectedRight);
|
|
305
371
|
if (right === null) {
|
|
306
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
372
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid right axis", { result: null });
|
|
307
373
|
return null;
|
|
308
374
|
}
|
|
309
375
|
const forward = normalize(cross(right, up));
|
|
@@ -312,7 +378,7 @@ const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TU
|
|
|
312
378
|
right,
|
|
313
379
|
forward
|
|
314
380
|
};
|
|
315
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
381
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned", { result });
|
|
316
382
|
return result;
|
|
317
383
|
};
|
|
318
384
|
const classifyInstantaneousEvidence = (metrics, tuning) => {
|
|
@@ -381,69 +447,10 @@ const normalize = (value) => {
|
|
|
381
447
|
};
|
|
382
448
|
const throwLoggedRangeError = (scope, message) => {
|
|
383
449
|
const error = new RangeError(message);
|
|
384
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
450
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, scope, "threw", error);
|
|
385
451
|
throw error;
|
|
386
452
|
};
|
|
387
453
|
//#endregion
|
|
388
|
-
//#region src/sensor/unified_info.ts
|
|
389
|
-
const DEBUG_MODULE$4 = "unified_info.ts";
|
|
390
|
-
const unifiedSensorInfo = () => {
|
|
391
|
-
dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "called");
|
|
392
|
-
try {
|
|
393
|
-
return window.unifiedSensorInfo().then((payload) => {
|
|
394
|
-
dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "resolved", { payload });
|
|
395
|
-
return payload;
|
|
396
|
-
}, (error) => {
|
|
397
|
-
dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "rejected", error);
|
|
398
|
-
throw error;
|
|
399
|
-
});
|
|
400
|
-
} catch (error) {
|
|
401
|
-
dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "threw", error);
|
|
402
|
-
throw error;
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
//#endregion
|
|
406
|
-
//#region src/sensor/constants.ts
|
|
407
|
-
const BATTERY_HEALTH_LABELS = {
|
|
408
|
-
GOOD: "良好",
|
|
409
|
-
OVERHEAT: "过热",
|
|
410
|
-
DEAD: "失效",
|
|
411
|
-
OVER_VOLTAGE: "电压过高",
|
|
412
|
-
UNSPECIFIED_FAILURE: "异常",
|
|
413
|
-
COLD: "温度过低",
|
|
414
|
-
UNKNOWN: "未知"
|
|
415
|
-
};
|
|
416
|
-
const CAR_RUNNING_LABELS = {
|
|
417
|
-
STOPPED: "静止",
|
|
418
|
-
STEADY_DRIVING: "匀速行驶",
|
|
419
|
-
SUDDEN_BRAKING: "急刹车",
|
|
420
|
-
BRAKING: "刹车",
|
|
421
|
-
LEFT_TURN: "左转",
|
|
422
|
-
RIGHT_TURN: "右转",
|
|
423
|
-
ACCELERATION: "加速",
|
|
424
|
-
RAPID_ACCELERATION: "急加速"
|
|
425
|
-
};
|
|
426
|
-
const CAR_RUNNING_STATUSES = [
|
|
427
|
-
"STOPPED",
|
|
428
|
-
"STEADY_DRIVING",
|
|
429
|
-
"ACCELERATION",
|
|
430
|
-
"RAPID_ACCELERATION",
|
|
431
|
-
"BRAKING",
|
|
432
|
-
"SUDDEN_BRAKING",
|
|
433
|
-
"LEFT_TURN",
|
|
434
|
-
"RIGHT_TURN"
|
|
435
|
-
];
|
|
436
|
-
const CAR_RUNNING_STATUS_PRIORITY = {
|
|
437
|
-
STOPPED: 0,
|
|
438
|
-
STEADY_DRIVING: 0,
|
|
439
|
-
ACCELERATION: 1,
|
|
440
|
-
BRAKING: 2,
|
|
441
|
-
LEFT_TURN: 3,
|
|
442
|
-
RIGHT_TURN: 3,
|
|
443
|
-
RAPID_ACCELERATION: 4,
|
|
444
|
-
SUDDEN_BRAKING: 5
|
|
445
|
-
};
|
|
446
|
-
//#endregion
|
|
447
454
|
//#region src/helper/async.ts
|
|
448
455
|
const createAbortError = (message, debug) => {
|
|
449
456
|
dialSdkDebugWarn(debug.moduleName, debug.scope, "called");
|
|
@@ -475,7 +482,7 @@ const invokeSafely = (callback, args, onError) => {
|
|
|
475
482
|
}
|
|
476
483
|
};
|
|
477
484
|
//#endregion
|
|
478
|
-
//#region src/
|
|
485
|
+
//#region src/driving_expression/driving_expressions.ts
|
|
479
486
|
const DEBUG_MODULE$3 = "driving_expressions.ts";
|
|
480
487
|
const IS_RECORD_DEBUG$1 = {
|
|
481
488
|
moduleName: DEBUG_MODULE$3,
|
|
@@ -591,7 +598,7 @@ const tryParseDrivingExpressionsConfig = (raw, options) => {
|
|
|
591
598
|
}
|
|
592
599
|
};
|
|
593
600
|
/**
|
|
594
|
-
* Reads Runtime's typed
|
|
601
|
+
* Reads Runtime's typed driving-expressions injection.
|
|
595
602
|
* It never installs official fallbacks implicitly.
|
|
596
603
|
*/
|
|
597
604
|
const readDrivingExpressionsConfig = (raw, options) => {
|
|
@@ -1097,24 +1104,8 @@ const parseJsonObject = (raw) => {
|
|
|
1097
1104
|
};
|
|
1098
1105
|
const readRuntimeDrivingExpressionsValue = () => {
|
|
1099
1106
|
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "called");
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
const result = runtimeGlobal.__TOOOONY_DRIVING_EXPRESSIONS__;
|
|
1103
|
-
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned typed injection", { result });
|
|
1104
|
-
return result;
|
|
1105
|
-
}
|
|
1106
|
-
const faceConfig = runtimeGlobal.__TOOOONY_FACE_CONFIG__;
|
|
1107
|
-
if (!isRecord(faceConfig, IS_RECORD_DEBUG$1)) {
|
|
1108
|
-
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned", { result: void 0 });
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1111
|
-
if (faceConfig.drivingExpressions !== void 0) {
|
|
1112
|
-
const result = faceConfig.drivingExpressions;
|
|
1113
|
-
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned face config", { result });
|
|
1114
|
-
return result;
|
|
1115
|
-
}
|
|
1116
|
-
const result = isRecord(faceConfig.values, IS_RECORD_DEBUG$1) ? faceConfig.values.drivingExpressions : void 0;
|
|
1117
|
-
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned legacy face config", { result });
|
|
1107
|
+
const result = globalThis.__TOOOONY_DRIVING_EXPRESSIONS__;
|
|
1108
|
+
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned typed injection", { result });
|
|
1118
1109
|
return result;
|
|
1119
1110
|
};
|
|
1120
1111
|
const parseMediaAssetId = (raw, status) => {
|
|
@@ -1249,7 +1240,7 @@ const requireNonNegativeFinite = (name, value) => {
|
|
|
1249
1240
|
return value;
|
|
1250
1241
|
};
|
|
1251
1242
|
//#endregion
|
|
1252
|
-
//#region src/
|
|
1243
|
+
//#region src/driving_expression/page_lifecycle.ts
|
|
1253
1244
|
const DEBUG_MODULE$2 = "page_lifecycle.ts";
|
|
1254
1245
|
const subscribers = /* @__PURE__ */ new Set();
|
|
1255
1246
|
let installedWindow = null;
|
|
@@ -1387,7 +1378,7 @@ const safeInvoke = (callback) => {
|
|
|
1387
1378
|
})) dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "returned");
|
|
1388
1379
|
};
|
|
1389
1380
|
//#endregion
|
|
1390
|
-
//#region src/
|
|
1381
|
+
//#region src/driving_expression/driving_status_controller.ts
|
|
1391
1382
|
const DEBUG_MODULE$1 = "driving_status_controller.ts";
|
|
1392
1383
|
const ZERO_VECTOR = {
|
|
1393
1384
|
x: 0,
|
|
@@ -1624,6 +1615,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1624
1615
|
}, delayMs);
|
|
1625
1616
|
}
|
|
1626
1617
|
async poll(generation) {
|
|
1618
|
+
const pollStartedAtMs = this.clock.now();
|
|
1627
1619
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", { generation });
|
|
1628
1620
|
if (!this.polling || generation !== this.generation || this.pendingRead !== null) {
|
|
1629
1621
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored", {
|
|
@@ -1674,7 +1666,10 @@ var DefaultDrivingStatusController = class {
|
|
|
1674
1666
|
this.processUnavailable("provider_error");
|
|
1675
1667
|
} finally {
|
|
1676
1668
|
if (this.pendingRead === pendingRead) this.pendingRead = null;
|
|
1677
|
-
if (this.polling && generation === this.generation)
|
|
1669
|
+
if (this.polling && generation === this.generation) {
|
|
1670
|
+
const elapsedMs = Math.max(0, this.clock.now() - pollStartedAtMs);
|
|
1671
|
+
this.schedulePoll(Math.max(0, this.pollIntervalMs - elapsedMs), generation);
|
|
1672
|
+
}
|
|
1678
1673
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "completed", {
|
|
1679
1674
|
generation,
|
|
1680
1675
|
polling: this.polling
|
|
@@ -1735,19 +1730,23 @@ var DefaultDrivingStatusController = class {
|
|
|
1735
1730
|
frameAgeMs: metrics.capturedAtMs - gravity.sampledAtMs,
|
|
1736
1731
|
tuningVersion: this.tuning.version
|
|
1737
1732
|
});
|
|
1738
|
-
const
|
|
1739
|
-
const
|
|
1740
|
-
const
|
|
1733
|
+
const longitudinalRetentionMs = Math.max(recognitionRetentionMs(this.tuning.longitudinalWindowMs, this.tuning.longitudinalMinimumSamples), recognitionRetentionMs(this.tuning.rapidLongitudinalWindowMs, this.tuning.rapidLongitudinalMinimumSamples));
|
|
1734
|
+
const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", longitudinalRetentionMs);
|
|
1735
|
+
const yawRetentionMs = Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs, recognitionRetentionMs(this.tuning.turnConfirmationMs, this.tuning.turnMinimumSamples));
|
|
1736
|
+
const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", yawRetentionMs);
|
|
1737
|
+
const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, recognitionRetentionMs(this.tuning.motionWindowMs, this.tuning.motionMinimumSamples));
|
|
1741
1738
|
if (!addedLongitudinal && !addedYaw && !addedMotion) {
|
|
1742
1739
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "ignored duplicate sample");
|
|
1743
1740
|
return;
|
|
1744
1741
|
}
|
|
1745
|
-
const
|
|
1742
|
+
const motionCoverageWindow = selectCoverageWindow(this.motionWindow, this.tuning.motionMinimumSamples, this.tuning.motionWindowMs, (sample) => sample.sampledAtMs);
|
|
1743
|
+
const motionStatisticsWindow = motionCoverageWindow ?? recentVectors(this.motionWindow, this.tuning.motionWindowMs);
|
|
1744
|
+
const motionMad = motionCoverageWindow === null ? null : calculateCombinedAxisMad(motionCoverageWindow, minimumTemporalSamples(this.tuning.motionMinimumSamples));
|
|
1746
1745
|
const previousBaseStatus = this.baseStatus;
|
|
1747
1746
|
const previousUrgentStatus = this.urgentStatus;
|
|
1748
1747
|
const previousLongitudinalStatus = this.normalLongitudinalStatus;
|
|
1749
1748
|
const previousTurnStatus = this.turnStatus;
|
|
1750
|
-
const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion);
|
|
1749
|
+
const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion, motionCoverageWindow?.[0]?.sampledAtMs ?? accelerometerSample.sampledAtMs);
|
|
1751
1750
|
const longitudinalEvidence = detectLongitudinalEvidence(this.longitudinalWindow, this.tuning);
|
|
1752
1751
|
if (addedLongitudinal) this.updateLongitudinalDetector(longitudinalEvidence, metrics.linearAccelerationSampledAtMs);
|
|
1753
1752
|
const turnEvidence = detectTurnEvidence(this.yawWindow, this.tuning);
|
|
@@ -1760,8 +1759,8 @@ var DefaultDrivingStatusController = class {
|
|
|
1760
1759
|
if (this.stationaryConfirmed) this.updateBias(metrics);
|
|
1761
1760
|
const statistics = {
|
|
1762
1761
|
base: {
|
|
1763
|
-
sampleCount:
|
|
1764
|
-
durationMs: vectorWindowDuration(
|
|
1762
|
+
sampleCount: motionStatisticsWindow.length,
|
|
1763
|
+
durationMs: vectorWindowDuration(motionStatisticsWindow),
|
|
1765
1764
|
motionMad,
|
|
1766
1765
|
gyroMagnitude: metrics.gyroMagnitude,
|
|
1767
1766
|
rawGyroMagnitude: metrics.rawGyroMagnitude
|
|
@@ -1807,7 +1806,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1807
1806
|
normalLongitudinalStatus: this.normalLongitudinalStatus
|
|
1808
1807
|
});
|
|
1809
1808
|
}
|
|
1810
|
-
updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample) {
|
|
1809
|
+
updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample, conditionStartedAtMs) {
|
|
1811
1810
|
if (motionMad === null || !isNewMotionSample) return "warming_up";
|
|
1812
1811
|
const gyroForStop = this.biasCalibrated ? metrics.gyroMagnitude : metrics.rawGyroMagnitude;
|
|
1813
1812
|
const stoppedGyroThreshold = this.biasCalibrated ? this.tuning.stoppedGyroThreshold : this.tuning.stoppedRawGyroThreshold;
|
|
@@ -1816,7 +1815,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1816
1815
|
const moving = motionMad >= this.tuning.drivingMotionThreshold || gyroForStop >= drivingGyroThreshold;
|
|
1817
1816
|
if (stationary) {
|
|
1818
1817
|
this.drivingConditionSinceMs = null;
|
|
1819
|
-
if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs =
|
|
1818
|
+
if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = conditionStartedAtMs;
|
|
1820
1819
|
if (sampledAtMs - this.stoppedConditionSinceMs >= this.tuning.stoppedConfirmationMs) {
|
|
1821
1820
|
this.baseStatus = "STOPPED";
|
|
1822
1821
|
this.stationaryConfirmed = true;
|
|
@@ -1826,7 +1825,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1826
1825
|
this.stoppedConditionSinceMs = null;
|
|
1827
1826
|
this.stationaryConfirmed = false;
|
|
1828
1827
|
if (moving) {
|
|
1829
|
-
if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs =
|
|
1828
|
+
if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = sampledAtMs;
|
|
1830
1829
|
if (sampledAtMs - this.drivingConditionSinceMs >= this.tuning.drivingConfirmationMs) this.baseStatus = "STEADY_DRIVING";
|
|
1831
1830
|
return "moving";
|
|
1832
1831
|
}
|
|
@@ -1942,7 +1941,6 @@ var DefaultDrivingStatusController = class {
|
|
|
1942
1941
|
const now = this.clock.now();
|
|
1943
1942
|
if (this.unavailableSinceMs === null) this.unavailableSinceMs = now;
|
|
1944
1943
|
const unavailableForMs = Math.max(0, now - this.unavailableSinceMs);
|
|
1945
|
-
this.clearRecognitionWindows();
|
|
1946
1944
|
this.setCandidate(null, now);
|
|
1947
1945
|
this.emitDiagnostic({
|
|
1948
1946
|
type: "sample_unavailable",
|
|
@@ -1960,6 +1958,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1960
1958
|
return;
|
|
1961
1959
|
}
|
|
1962
1960
|
this.fallbackApplied = true;
|
|
1961
|
+
this.clearRecognitionWindows();
|
|
1963
1962
|
this.baseStatus = "STOPPED";
|
|
1964
1963
|
this.emitDiagnostic({
|
|
1965
1964
|
type: "fallback_to_stopped",
|
|
@@ -2076,8 +2075,12 @@ var DefaultDrivingStatusController = class {
|
|
|
2076
2075
|
}
|
|
2077
2076
|
};
|
|
2078
2077
|
const detectLongitudinalEvidence = (window, tuning) => {
|
|
2079
|
-
const
|
|
2080
|
-
const
|
|
2078
|
+
const recentRapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2079
|
+
const rapidCoverageWindow = selectCoverageWindow(window, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
|
|
2080
|
+
const rapidWindow = rapidCoverageWindow ?? recentRapidWindow;
|
|
2081
|
+
const recentNormalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2082
|
+
const normalCoverageWindow = selectCoverageWindow(window, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
|
|
2083
|
+
const normalWindow = normalCoverageWindow ?? recentNormalWindow;
|
|
2081
2084
|
let rapidCandidate = null;
|
|
2082
2085
|
let rejectionReason = null;
|
|
2083
2086
|
const rapidValues = rapidWindow.map((sample) => sample.longitudinal);
|
|
@@ -2099,7 +2102,7 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2099
2102
|
verticalRms,
|
|
2100
2103
|
hasReversal
|
|
2101
2104
|
};
|
|
2102
|
-
if (
|
|
2105
|
+
if (rapidCoverageWindow !== null) {
|
|
2103
2106
|
if (rapidPeak >= tuning.rapidLongitudinalPeakThreshold && Math.abs(rapidMean) >= tuning.rapidLongitudinalMeanThreshold) {
|
|
2104
2107
|
if (verticalRms > longitudinalRms * tuning.verticalBumpRatio) rejectionReason = "vertical_bump";
|
|
2105
2108
|
else if (hasReversal) rejectionReason = "longitudinal_reversal";
|
|
@@ -2121,8 +2124,9 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2121
2124
|
peak: maximumAbsolute(values),
|
|
2122
2125
|
coherence
|
|
2123
2126
|
};
|
|
2124
|
-
|
|
2125
|
-
|
|
2127
|
+
const retentionValues = recentNormalWindow.map((sample) => sample.longitudinal);
|
|
2128
|
+
retainedNormal = Math.abs(trimmedMean(retentionValues)) >= tuning.longitudinalExitThreshold;
|
|
2129
|
+
if (normalCoverageWindow !== null && Math.abs(mean) >= tuning.longitudinalThreshold && coherence >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
|
|
2126
2130
|
}
|
|
2127
2131
|
return {
|
|
2128
2132
|
rapidCandidate,
|
|
@@ -2134,7 +2138,8 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2134
2138
|
};
|
|
2135
2139
|
};
|
|
2136
2140
|
const detectTurnEvidence = (window, tuning) => {
|
|
2137
|
-
const
|
|
2141
|
+
const turnWindow = recentMetrics(window, tuning.turnWindowMs, "gyroscopeSampledAtMs");
|
|
2142
|
+
const latest = turnWindow[turnWindow.length - 1];
|
|
2138
2143
|
if (!latest) return {
|
|
2139
2144
|
candidate: null,
|
|
2140
2145
|
latestYaw: 0,
|
|
@@ -2142,20 +2147,22 @@ const detectTurnEvidence = (window, tuning) => {
|
|
|
2142
2147
|
rejectionReason: null,
|
|
2143
2148
|
statistics: null
|
|
2144
2149
|
};
|
|
2145
|
-
const
|
|
2150
|
+
const recentStrongWindow = recentMetrics(turnWindow, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
|
|
2151
|
+
const strongCoverageWindow = selectCoverageWindow(window, tuning.turnMinimumSamples, tuning.turnConfirmationMs, (sample) => sample.gyroscopeSampledAtMs);
|
|
2152
|
+
const strongWindow = strongCoverageWindow ?? recentStrongWindow;
|
|
2146
2153
|
const strongMean = trimmedMean(strongWindow.map((sample) => sample.yaw));
|
|
2147
2154
|
const strongDirection = Math.sign(strongMean);
|
|
2148
2155
|
const strongCoherence = signCoherence(strongWindow.map((sample) => sample.yaw), strongDirection, tuning.turnExitYawThreshold);
|
|
2149
|
-
const strong =
|
|
2150
|
-
const signedAngle = integrateYaw(
|
|
2156
|
+
const strong = strongCoverageWindow !== null && Math.abs(strongMean) >= tuning.turnYawThreshold && strongCoherence >= tuning.turnSignCoherence;
|
|
2157
|
+
const signedAngle = integrateYaw(turnWindow);
|
|
2151
2158
|
const gentleDirection = Math.sign(signedAngle);
|
|
2152
|
-
const gentleCoherence = signCoherence(
|
|
2153
|
-
const yawRms = rootMeanSquare(
|
|
2154
|
-
const rawGyroRms = rootMeanSquare(
|
|
2159
|
+
const gentleCoherence = signCoherence(turnWindow.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold);
|
|
2160
|
+
const yawRms = rootMeanSquare(turnWindow.map((sample) => sample.yaw));
|
|
2161
|
+
const rawGyroRms = rootMeanSquare(turnWindow.map((sample) => sample.rawGyroMagnitude));
|
|
2155
2162
|
const yawDominance = rawGyroRms <= Number.EPSILON ? 0 : Math.min(1, yawRms / rawGyroRms);
|
|
2156
|
-
const lateralRms = rootMeanSquare(
|
|
2157
|
-
const verticalRms = rootMeanSquare(
|
|
2158
|
-
const gentleShape =
|
|
2163
|
+
const lateralRms = rootMeanSquare(turnWindow.map((sample) => sample.lateral));
|
|
2164
|
+
const verticalRms = rootMeanSquare(turnWindow.map((sample) => sample.vertical));
|
|
2165
|
+
const gentleShape = turnWindow.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && gentleCoherence >= tuning.turnSignCoherence;
|
|
2159
2166
|
const verticalDominant = verticalRms > Math.max(lateralRms, tuning.turnVerticalNoiseFloor) * tuning.turnVerticalBumpRatio;
|
|
2160
2167
|
const yawDominant = yawDominance >= tuning.turnYawDominanceThreshold;
|
|
2161
2168
|
const laterallySupported = lateralRms >= tuning.turnLateralEvidenceThreshold;
|
|
@@ -2167,8 +2174,8 @@ const detectTurnEvidence = (window, tuning) => {
|
|
|
2167
2174
|
sampledAtMs: latest.gyroscopeSampledAtMs,
|
|
2168
2175
|
rejectionReason,
|
|
2169
2176
|
statistics: {
|
|
2170
|
-
sampleCount:
|
|
2171
|
-
durationMs: metricWindowDuration(
|
|
2177
|
+
sampleCount: turnWindow.length,
|
|
2178
|
+
durationMs: metricWindowDuration(turnWindow, "gyroscopeSampledAtMs"),
|
|
2172
2179
|
strongMean,
|
|
2173
2180
|
strongCoherence,
|
|
2174
2181
|
signedAngle,
|
|
@@ -2192,10 +2199,29 @@ const recentMetrics = (values, windowMs, timestampKey) => {
|
|
|
2192
2199
|
const latestAtMs = values[values.length - 1]?.[timestampKey] ?? 0;
|
|
2193
2200
|
return values.filter((value) => value[timestampKey] >= latestAtMs - windowMs);
|
|
2194
2201
|
};
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2202
|
+
const MINIMUM_TEMPORAL_SAMPLES = 3;
|
|
2203
|
+
const MAXIMUM_RECOGNITION_GAP_MS = 1e3;
|
|
2204
|
+
const minimumTemporalSamples = (minimumSamples) => Math.min(minimumSamples, MINIMUM_TEMPORAL_SAMPLES);
|
|
2205
|
+
const maximumRecognitionGapMs = (minimumSamples, durationMs) => {
|
|
2206
|
+
const nominalSampleIntervalMs = durationMs / Math.max(1, minimumSamples - 1);
|
|
2207
|
+
return Math.max(MAXIMUM_RECOGNITION_GAP_MS, nominalSampleIntervalMs * 2.5);
|
|
2208
|
+
};
|
|
2209
|
+
const recognitionRetentionMs = (durationMs, minimumSamples) => Math.max(durationMs, maximumRecognitionGapMs(minimumSamples, durationMs) * Math.max(1, minimumTemporalSamples(minimumSamples) - 1));
|
|
2210
|
+
const selectCoverageWindow = (values, minimumSamples, durationMs, timestampOf) => {
|
|
2211
|
+
const requiredSamples = minimumTemporalSamples(minimumSamples);
|
|
2212
|
+
if (values.length < requiredSamples) return null;
|
|
2213
|
+
const lastAtMs = timestampOf(values[values.length - 1]);
|
|
2214
|
+
const maximumGapMs = maximumRecognitionGapMs(minimumSamples, durationMs);
|
|
2215
|
+
for (let startIndex = values.length - requiredSamples; startIndex >= 0; startIndex -= 1) {
|
|
2216
|
+
const coveredValues = values.slice(startIndex);
|
|
2217
|
+
if (lastAtMs - timestampOf(coveredValues[0]) < durationMs) continue;
|
|
2218
|
+
if (coveredValues.slice(1).every((value, index) => timestampOf(value) - timestampOf(coveredValues[index]) < maximumGapMs)) return coveredValues;
|
|
2219
|
+
}
|
|
2220
|
+
return null;
|
|
2221
|
+
};
|
|
2222
|
+
const recentVectors = (values, windowMs) => {
|
|
2223
|
+
const latestAtMs = values[values.length - 1]?.sampledAtMs ?? 0;
|
|
2224
|
+
return values.filter((value) => value.sampledAtMs >= latestAtMs - windowMs);
|
|
2199
2225
|
};
|
|
2200
2226
|
const integrateYaw = (values) => {
|
|
2201
2227
|
let angle = 0;
|
|
@@ -2276,7 +2302,7 @@ const systemClock = {
|
|
|
2276
2302
|
const isBaseStatus = (status) => status === "STOPPED" || status === "STEADY_DRIVING";
|
|
2277
2303
|
const noop = () => void 0;
|
|
2278
2304
|
//#endregion
|
|
2279
|
-
//#region src/
|
|
2305
|
+
//#region src/driving_expression/driving_expression_player.ts
|
|
2280
2306
|
const DEBUG_MODULE = "driving_expression_player.ts";
|
|
2281
2307
|
const MAX_RETAINED_IMAGES = 3;
|
|
2282
2308
|
const POSITIVE_FINITE_DEBUG = {
|