@ziztechnology/dial-library 0.0.13 → 0.0.14
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 +157 -136
- 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
|
@@ -43,6 +43,67 @@ const dialSdkDebugWarn = (moduleName, scope, phase, data) => {
|
|
|
43
43
|
} catch {}
|
|
44
44
|
};
|
|
45
45
|
//#endregion
|
|
46
|
+
//#region src/sensor/unified_info.ts
|
|
47
|
+
const DEBUG_MODULE$5 = "unified_info.ts";
|
|
48
|
+
const unifiedSensorInfo = () => {
|
|
49
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "called");
|
|
50
|
+
try {
|
|
51
|
+
return window.unifiedSensorInfo().then((payload) => {
|
|
52
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "resolved", { payload });
|
|
53
|
+
return payload;
|
|
54
|
+
}, (error) => {
|
|
55
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "rejected", error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "threw", error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/sensor/constants.ts
|
|
65
|
+
const BATTERY_HEALTH_LABELS = {
|
|
66
|
+
GOOD: "良好",
|
|
67
|
+
OVERHEAT: "过热",
|
|
68
|
+
DEAD: "失效",
|
|
69
|
+
OVER_VOLTAGE: "电压过高",
|
|
70
|
+
UNSPECIFIED_FAILURE: "异常",
|
|
71
|
+
COLD: "温度过低",
|
|
72
|
+
UNKNOWN: "未知"
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/driving_expression/constants.ts
|
|
76
|
+
const CAR_RUNNING_LABELS = {
|
|
77
|
+
STOPPED: "静止",
|
|
78
|
+
STEADY_DRIVING: "匀速行驶",
|
|
79
|
+
SUDDEN_BRAKING: "急刹车",
|
|
80
|
+
BRAKING: "刹车",
|
|
81
|
+
LEFT_TURN: "左转",
|
|
82
|
+
RIGHT_TURN: "右转",
|
|
83
|
+
ACCELERATION: "加速",
|
|
84
|
+
RAPID_ACCELERATION: "急加速"
|
|
85
|
+
};
|
|
86
|
+
const CAR_RUNNING_STATUSES = [
|
|
87
|
+
"STOPPED",
|
|
88
|
+
"STEADY_DRIVING",
|
|
89
|
+
"ACCELERATION",
|
|
90
|
+
"RAPID_ACCELERATION",
|
|
91
|
+
"BRAKING",
|
|
92
|
+
"SUDDEN_BRAKING",
|
|
93
|
+
"LEFT_TURN",
|
|
94
|
+
"RIGHT_TURN"
|
|
95
|
+
];
|
|
96
|
+
const CAR_RUNNING_STATUS_PRIORITY = {
|
|
97
|
+
STOPPED: 0,
|
|
98
|
+
STEADY_DRIVING: 0,
|
|
99
|
+
ACCELERATION: 1,
|
|
100
|
+
BRAKING: 2,
|
|
101
|
+
LEFT_TURN: 3,
|
|
102
|
+
RIGHT_TURN: 3,
|
|
103
|
+
RAPID_ACCELERATION: 4,
|
|
104
|
+
SUDDEN_BRAKING: 5
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
46
107
|
//#region src/helper/sensor.ts
|
|
47
108
|
const extractFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
|
|
48
109
|
if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
|
|
@@ -82,8 +143,8 @@ const isRecord = (value, debug) => {
|
|
|
82
143
|
return result;
|
|
83
144
|
};
|
|
84
145
|
//#endregion
|
|
85
|
-
//#region src/
|
|
86
|
-
const DEBUG_MODULE$
|
|
146
|
+
//#region src/driving_expression/check_status.ts
|
|
147
|
+
const DEBUG_MODULE$4 = "check_status.ts";
|
|
87
148
|
const DEVICE_RIGHT = {
|
|
88
149
|
x: 1,
|
|
89
150
|
y: 0,
|
|
@@ -95,7 +156,7 @@ const ZERO_VECTOR$1 = {
|
|
|
95
156
|
z: 0
|
|
96
157
|
};
|
|
97
158
|
const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
98
|
-
version: "driving-status-calibrated-2026-07-17",
|
|
159
|
+
version: "driving-status-calibrated-2026-07-17-r2",
|
|
99
160
|
maxSampleAgeMs: 1e3,
|
|
100
161
|
gravityFilterTimeConstantMs: 1e3,
|
|
101
162
|
biasFilterTimeConstantMs: 1e4,
|
|
@@ -108,7 +169,7 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
|
108
169
|
stoppedRawGyroThreshold: .065,
|
|
109
170
|
drivingGyroThreshold: .04,
|
|
110
171
|
drivingRawGyroThreshold: .08,
|
|
111
|
-
stoppedConfirmationMs:
|
|
172
|
+
stoppedConfirmationMs: 1500,
|
|
112
173
|
drivingConfirmationMs: 600,
|
|
113
174
|
longitudinalWindowMs: 800,
|
|
114
175
|
longitudinalMinimumSamples: 5,
|
|
@@ -139,14 +200,14 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
|
|
|
139
200
|
turnClosureWindowMs: 1500
|
|
140
201
|
});
|
|
141
202
|
const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
142
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
203
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "called", {
|
|
143
204
|
info,
|
|
144
205
|
tuningOverrides
|
|
145
206
|
});
|
|
146
207
|
const tuning = resolveDrivingStatusTuning(tuningOverrides);
|
|
147
208
|
const metrics = extractDrivingSnapshotMetrics(info, tuning);
|
|
148
209
|
if (metrics === null) {
|
|
149
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
210
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result: null });
|
|
150
211
|
return null;
|
|
151
212
|
}
|
|
152
213
|
const { candidateStatus, rejectionReason } = classifyInstantaneousEvidence(metrics, tuning);
|
|
@@ -160,11 +221,11 @@ const classifyDrivingSnapshot = (info, tuningOverrides) => {
|
|
|
160
221
|
maximumSampleAgeMs: Math.max(info.capturedAtMs - metrics.linearAccelerationSampledAtMs, info.capturedAtMs - metrics.gyroscopeSampledAtMs, info.capturedAtMs - metrics.gravitySampledAtMs)
|
|
161
222
|
}
|
|
162
223
|
};
|
|
163
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
224
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result });
|
|
164
225
|
return result;
|
|
165
226
|
};
|
|
166
227
|
const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
167
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
228
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "called", { overrides });
|
|
168
229
|
const tuning = {
|
|
169
230
|
...DEFAULT_DRIVING_STATUS_TUNING,
|
|
170
231
|
...overrides
|
|
@@ -204,7 +265,7 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
|
204
265
|
"turnExitConfirmationMs",
|
|
205
266
|
"turnClosureWindowMs"
|
|
206
267
|
]) requirePositiveFinite(field, tuning[field], {
|
|
207
|
-
moduleName: DEBUG_MODULE$
|
|
268
|
+
moduleName: DEBUG_MODULE$4,
|
|
208
269
|
scope: "assertPositiveFinite"
|
|
209
270
|
});
|
|
210
271
|
for (const field of [
|
|
@@ -229,17 +290,17 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
|
|
|
229
290
|
if (tuning.rapidLongitudinalMeanThreshold <= tuning.longitudinalThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalMeanThreshold must be greater than longitudinalThreshold");
|
|
230
291
|
if (tuning.rapidLongitudinalPeakThreshold < tuning.rapidLongitudinalMeanThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalPeakThreshold must be greater than or equal to rapidLongitudinalMeanThreshold");
|
|
231
292
|
if (tuning.turnYawThreshold <= tuning.turnExitYawThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "turnYawThreshold must be greater than turnExitYawThreshold");
|
|
232
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
293
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "returned", { tuning });
|
|
233
294
|
return tuning;
|
|
234
295
|
};
|
|
235
296
|
const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING, correction = {}) => {
|
|
236
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
297
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "called", {
|
|
237
298
|
info,
|
|
238
299
|
tuning,
|
|
239
300
|
correction
|
|
240
301
|
});
|
|
241
302
|
if (!info || !Number.isFinite(info.capturedAtMs)) {
|
|
242
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
303
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
|
|
243
304
|
return null;
|
|
244
305
|
}
|
|
245
306
|
const linearAcceleration = extractFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
|
|
@@ -250,7 +311,7 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
250
311
|
sampledAtMs: gravityMetric.sampledAtMs
|
|
251
312
|
} : gravityMetric);
|
|
252
313
|
if (linearAcceleration === null || gyroscope === null || gravity === null) {
|
|
253
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
314
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
|
|
254
315
|
linearAcceleration,
|
|
255
316
|
gyroscope,
|
|
256
317
|
gravity
|
|
@@ -259,7 +320,7 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
259
320
|
}
|
|
260
321
|
const frame = buildVehicleFrame(gravity.value, tuning.vehicleRightMinimumNorm);
|
|
261
322
|
if (frame === null) {
|
|
262
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
323
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
|
|
263
324
|
gravity: gravity.value,
|
|
264
325
|
vehicleRightMinimumNorm: tuning.vehicleRightMinimumNorm
|
|
265
326
|
});
|
|
@@ -282,28 +343,28 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
|
|
|
282
343
|
rawGyroscope: gyroscope.value,
|
|
283
344
|
frame
|
|
284
345
|
};
|
|
285
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
346
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned", { result });
|
|
286
347
|
return result;
|
|
287
348
|
};
|
|
288
349
|
const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TUNING.vehicleRightMinimumNorm) => {
|
|
289
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
350
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "called", {
|
|
290
351
|
gravity,
|
|
291
352
|
rightMinimumNorm
|
|
292
353
|
});
|
|
293
354
|
const gravityDirection = normalize(gravity);
|
|
294
355
|
if (gravityDirection === null) {
|
|
295
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
356
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid gravity", { result: null });
|
|
296
357
|
return null;
|
|
297
358
|
}
|
|
298
359
|
const up = gravityDirection;
|
|
299
360
|
const projectedRight = subtract(DEVICE_RIGHT, scale(up, dot(DEVICE_RIGHT, up)));
|
|
300
361
|
if (magnitude(projectedRight) < rightMinimumNorm) {
|
|
301
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
362
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned degenerate right axis", { result: null });
|
|
302
363
|
return null;
|
|
303
364
|
}
|
|
304
365
|
const right = normalize(projectedRight);
|
|
305
366
|
if (right === null) {
|
|
306
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
367
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid right axis", { result: null });
|
|
307
368
|
return null;
|
|
308
369
|
}
|
|
309
370
|
const forward = normalize(cross(right, up));
|
|
@@ -312,7 +373,7 @@ const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TU
|
|
|
312
373
|
right,
|
|
313
374
|
forward
|
|
314
375
|
};
|
|
315
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
376
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned", { result });
|
|
316
377
|
return result;
|
|
317
378
|
};
|
|
318
379
|
const classifyInstantaneousEvidence = (metrics, tuning) => {
|
|
@@ -381,69 +442,10 @@ const normalize = (value) => {
|
|
|
381
442
|
};
|
|
382
443
|
const throwLoggedRangeError = (scope, message) => {
|
|
383
444
|
const error = new RangeError(message);
|
|
384
|
-
dialSdkDebugWarn(DEBUG_MODULE$
|
|
445
|
+
dialSdkDebugWarn(DEBUG_MODULE$4, scope, "threw", error);
|
|
385
446
|
throw error;
|
|
386
447
|
};
|
|
387
448
|
//#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
449
|
//#region src/helper/async.ts
|
|
448
450
|
const createAbortError = (message, debug) => {
|
|
449
451
|
dialSdkDebugWarn(debug.moduleName, debug.scope, "called");
|
|
@@ -475,7 +477,7 @@ const invokeSafely = (callback, args, onError) => {
|
|
|
475
477
|
}
|
|
476
478
|
};
|
|
477
479
|
//#endregion
|
|
478
|
-
//#region src/
|
|
480
|
+
//#region src/driving_expression/driving_expressions.ts
|
|
479
481
|
const DEBUG_MODULE$3 = "driving_expressions.ts";
|
|
480
482
|
const IS_RECORD_DEBUG$1 = {
|
|
481
483
|
moduleName: DEBUG_MODULE$3,
|
|
@@ -591,7 +593,7 @@ const tryParseDrivingExpressionsConfig = (raw, options) => {
|
|
|
591
593
|
}
|
|
592
594
|
};
|
|
593
595
|
/**
|
|
594
|
-
* Reads Runtime's typed
|
|
596
|
+
* Reads Runtime's typed driving-expressions injection.
|
|
595
597
|
* It never installs official fallbacks implicitly.
|
|
596
598
|
*/
|
|
597
599
|
const readDrivingExpressionsConfig = (raw, options) => {
|
|
@@ -1097,24 +1099,8 @@ const parseJsonObject = (raw) => {
|
|
|
1097
1099
|
};
|
|
1098
1100
|
const readRuntimeDrivingExpressionsValue = () => {
|
|
1099
1101
|
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 });
|
|
1102
|
+
const result = globalThis.__TOOOONY_DRIVING_EXPRESSIONS__;
|
|
1103
|
+
dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned typed injection", { result });
|
|
1118
1104
|
return result;
|
|
1119
1105
|
};
|
|
1120
1106
|
const parseMediaAssetId = (raw, status) => {
|
|
@@ -1249,7 +1235,7 @@ const requireNonNegativeFinite = (name, value) => {
|
|
|
1249
1235
|
return value;
|
|
1250
1236
|
};
|
|
1251
1237
|
//#endregion
|
|
1252
|
-
//#region src/
|
|
1238
|
+
//#region src/driving_expression/page_lifecycle.ts
|
|
1253
1239
|
const DEBUG_MODULE$2 = "page_lifecycle.ts";
|
|
1254
1240
|
const subscribers = /* @__PURE__ */ new Set();
|
|
1255
1241
|
let installedWindow = null;
|
|
@@ -1387,7 +1373,7 @@ const safeInvoke = (callback) => {
|
|
|
1387
1373
|
})) dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "returned");
|
|
1388
1374
|
};
|
|
1389
1375
|
//#endregion
|
|
1390
|
-
//#region src/
|
|
1376
|
+
//#region src/driving_expression/driving_status_controller.ts
|
|
1391
1377
|
const DEBUG_MODULE$1 = "driving_status_controller.ts";
|
|
1392
1378
|
const ZERO_VECTOR = {
|
|
1393
1379
|
x: 0,
|
|
@@ -1624,6 +1610,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1624
1610
|
}, delayMs);
|
|
1625
1611
|
}
|
|
1626
1612
|
async poll(generation) {
|
|
1613
|
+
const pollStartedAtMs = this.clock.now();
|
|
1627
1614
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", { generation });
|
|
1628
1615
|
if (!this.polling || generation !== this.generation || this.pendingRead !== null) {
|
|
1629
1616
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored", {
|
|
@@ -1674,7 +1661,10 @@ var DefaultDrivingStatusController = class {
|
|
|
1674
1661
|
this.processUnavailable("provider_error");
|
|
1675
1662
|
} finally {
|
|
1676
1663
|
if (this.pendingRead === pendingRead) this.pendingRead = null;
|
|
1677
|
-
if (this.polling && generation === this.generation)
|
|
1664
|
+
if (this.polling && generation === this.generation) {
|
|
1665
|
+
const elapsedMs = Math.max(0, this.clock.now() - pollStartedAtMs);
|
|
1666
|
+
this.schedulePoll(Math.max(0, this.pollIntervalMs - elapsedMs), generation);
|
|
1667
|
+
}
|
|
1678
1668
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "completed", {
|
|
1679
1669
|
generation,
|
|
1680
1670
|
polling: this.polling
|
|
@@ -1735,19 +1725,23 @@ var DefaultDrivingStatusController = class {
|
|
|
1735
1725
|
frameAgeMs: metrics.capturedAtMs - gravity.sampledAtMs,
|
|
1736
1726
|
tuningVersion: this.tuning.version
|
|
1737
1727
|
});
|
|
1738
|
-
const
|
|
1739
|
-
const
|
|
1740
|
-
const
|
|
1728
|
+
const longitudinalRetentionMs = Math.max(recognitionRetentionMs(this.tuning.longitudinalWindowMs, this.tuning.longitudinalMinimumSamples), recognitionRetentionMs(this.tuning.rapidLongitudinalWindowMs, this.tuning.rapidLongitudinalMinimumSamples));
|
|
1729
|
+
const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", longitudinalRetentionMs);
|
|
1730
|
+
const yawRetentionMs = Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs, recognitionRetentionMs(this.tuning.turnConfirmationMs, this.tuning.turnMinimumSamples));
|
|
1731
|
+
const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", yawRetentionMs);
|
|
1732
|
+
const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, recognitionRetentionMs(this.tuning.motionWindowMs, this.tuning.motionMinimumSamples));
|
|
1741
1733
|
if (!addedLongitudinal && !addedYaw && !addedMotion) {
|
|
1742
1734
|
dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "ignored duplicate sample");
|
|
1743
1735
|
return;
|
|
1744
1736
|
}
|
|
1745
|
-
const
|
|
1737
|
+
const motionCoverageWindow = selectCoverageWindow(this.motionWindow, this.tuning.motionMinimumSamples, this.tuning.motionWindowMs, (sample) => sample.sampledAtMs);
|
|
1738
|
+
const motionStatisticsWindow = motionCoverageWindow ?? recentVectors(this.motionWindow, this.tuning.motionWindowMs);
|
|
1739
|
+
const motionMad = motionCoverageWindow === null ? null : calculateCombinedAxisMad(motionCoverageWindow, minimumTemporalSamples(this.tuning.motionMinimumSamples));
|
|
1746
1740
|
const previousBaseStatus = this.baseStatus;
|
|
1747
1741
|
const previousUrgentStatus = this.urgentStatus;
|
|
1748
1742
|
const previousLongitudinalStatus = this.normalLongitudinalStatus;
|
|
1749
1743
|
const previousTurnStatus = this.turnStatus;
|
|
1750
|
-
const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion);
|
|
1744
|
+
const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion, motionCoverageWindow?.[0]?.sampledAtMs ?? accelerometerSample.sampledAtMs);
|
|
1751
1745
|
const longitudinalEvidence = detectLongitudinalEvidence(this.longitudinalWindow, this.tuning);
|
|
1752
1746
|
if (addedLongitudinal) this.updateLongitudinalDetector(longitudinalEvidence, metrics.linearAccelerationSampledAtMs);
|
|
1753
1747
|
const turnEvidence = detectTurnEvidence(this.yawWindow, this.tuning);
|
|
@@ -1760,8 +1754,8 @@ var DefaultDrivingStatusController = class {
|
|
|
1760
1754
|
if (this.stationaryConfirmed) this.updateBias(metrics);
|
|
1761
1755
|
const statistics = {
|
|
1762
1756
|
base: {
|
|
1763
|
-
sampleCount:
|
|
1764
|
-
durationMs: vectorWindowDuration(
|
|
1757
|
+
sampleCount: motionStatisticsWindow.length,
|
|
1758
|
+
durationMs: vectorWindowDuration(motionStatisticsWindow),
|
|
1765
1759
|
motionMad,
|
|
1766
1760
|
gyroMagnitude: metrics.gyroMagnitude,
|
|
1767
1761
|
rawGyroMagnitude: metrics.rawGyroMagnitude
|
|
@@ -1807,7 +1801,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1807
1801
|
normalLongitudinalStatus: this.normalLongitudinalStatus
|
|
1808
1802
|
});
|
|
1809
1803
|
}
|
|
1810
|
-
updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample) {
|
|
1804
|
+
updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample, conditionStartedAtMs) {
|
|
1811
1805
|
if (motionMad === null || !isNewMotionSample) return "warming_up";
|
|
1812
1806
|
const gyroForStop = this.biasCalibrated ? metrics.gyroMagnitude : metrics.rawGyroMagnitude;
|
|
1813
1807
|
const stoppedGyroThreshold = this.biasCalibrated ? this.tuning.stoppedGyroThreshold : this.tuning.stoppedRawGyroThreshold;
|
|
@@ -1816,7 +1810,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1816
1810
|
const moving = motionMad >= this.tuning.drivingMotionThreshold || gyroForStop >= drivingGyroThreshold;
|
|
1817
1811
|
if (stationary) {
|
|
1818
1812
|
this.drivingConditionSinceMs = null;
|
|
1819
|
-
if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs =
|
|
1813
|
+
if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = conditionStartedAtMs;
|
|
1820
1814
|
if (sampledAtMs - this.stoppedConditionSinceMs >= this.tuning.stoppedConfirmationMs) {
|
|
1821
1815
|
this.baseStatus = "STOPPED";
|
|
1822
1816
|
this.stationaryConfirmed = true;
|
|
@@ -1826,7 +1820,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1826
1820
|
this.stoppedConditionSinceMs = null;
|
|
1827
1821
|
this.stationaryConfirmed = false;
|
|
1828
1822
|
if (moving) {
|
|
1829
|
-
if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs =
|
|
1823
|
+
if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = sampledAtMs;
|
|
1830
1824
|
if (sampledAtMs - this.drivingConditionSinceMs >= this.tuning.drivingConfirmationMs) this.baseStatus = "STEADY_DRIVING";
|
|
1831
1825
|
return "moving";
|
|
1832
1826
|
}
|
|
@@ -1942,7 +1936,6 @@ var DefaultDrivingStatusController = class {
|
|
|
1942
1936
|
const now = this.clock.now();
|
|
1943
1937
|
if (this.unavailableSinceMs === null) this.unavailableSinceMs = now;
|
|
1944
1938
|
const unavailableForMs = Math.max(0, now - this.unavailableSinceMs);
|
|
1945
|
-
this.clearRecognitionWindows();
|
|
1946
1939
|
this.setCandidate(null, now);
|
|
1947
1940
|
this.emitDiagnostic({
|
|
1948
1941
|
type: "sample_unavailable",
|
|
@@ -1960,6 +1953,7 @@ var DefaultDrivingStatusController = class {
|
|
|
1960
1953
|
return;
|
|
1961
1954
|
}
|
|
1962
1955
|
this.fallbackApplied = true;
|
|
1956
|
+
this.clearRecognitionWindows();
|
|
1963
1957
|
this.baseStatus = "STOPPED";
|
|
1964
1958
|
this.emitDiagnostic({
|
|
1965
1959
|
type: "fallback_to_stopped",
|
|
@@ -2076,8 +2070,12 @@ var DefaultDrivingStatusController = class {
|
|
|
2076
2070
|
}
|
|
2077
2071
|
};
|
|
2078
2072
|
const detectLongitudinalEvidence = (window, tuning) => {
|
|
2079
|
-
const
|
|
2080
|
-
const
|
|
2073
|
+
const recentRapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2074
|
+
const rapidCoverageWindow = selectCoverageWindow(window, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
|
|
2075
|
+
const rapidWindow = rapidCoverageWindow ?? recentRapidWindow;
|
|
2076
|
+
const recentNormalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
|
|
2077
|
+
const normalCoverageWindow = selectCoverageWindow(window, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
|
|
2078
|
+
const normalWindow = normalCoverageWindow ?? recentNormalWindow;
|
|
2081
2079
|
let rapidCandidate = null;
|
|
2082
2080
|
let rejectionReason = null;
|
|
2083
2081
|
const rapidValues = rapidWindow.map((sample) => sample.longitudinal);
|
|
@@ -2099,7 +2097,7 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2099
2097
|
verticalRms,
|
|
2100
2098
|
hasReversal
|
|
2101
2099
|
};
|
|
2102
|
-
if (
|
|
2100
|
+
if (rapidCoverageWindow !== null) {
|
|
2103
2101
|
if (rapidPeak >= tuning.rapidLongitudinalPeakThreshold && Math.abs(rapidMean) >= tuning.rapidLongitudinalMeanThreshold) {
|
|
2104
2102
|
if (verticalRms > longitudinalRms * tuning.verticalBumpRatio) rejectionReason = "vertical_bump";
|
|
2105
2103
|
else if (hasReversal) rejectionReason = "longitudinal_reversal";
|
|
@@ -2121,8 +2119,9 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2121
2119
|
peak: maximumAbsolute(values),
|
|
2122
2120
|
coherence
|
|
2123
2121
|
};
|
|
2124
|
-
|
|
2125
|
-
|
|
2122
|
+
const retentionValues = recentNormalWindow.map((sample) => sample.longitudinal);
|
|
2123
|
+
retainedNormal = Math.abs(trimmedMean(retentionValues)) >= tuning.longitudinalExitThreshold;
|
|
2124
|
+
if (normalCoverageWindow !== null && Math.abs(mean) >= tuning.longitudinalThreshold && coherence >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
|
|
2126
2125
|
}
|
|
2127
2126
|
return {
|
|
2128
2127
|
rapidCandidate,
|
|
@@ -2134,7 +2133,8 @@ const detectLongitudinalEvidence = (window, tuning) => {
|
|
|
2134
2133
|
};
|
|
2135
2134
|
};
|
|
2136
2135
|
const detectTurnEvidence = (window, tuning) => {
|
|
2137
|
-
const
|
|
2136
|
+
const turnWindow = recentMetrics(window, tuning.turnWindowMs, "gyroscopeSampledAtMs");
|
|
2137
|
+
const latest = turnWindow[turnWindow.length - 1];
|
|
2138
2138
|
if (!latest) return {
|
|
2139
2139
|
candidate: null,
|
|
2140
2140
|
latestYaw: 0,
|
|
@@ -2142,20 +2142,22 @@ const detectTurnEvidence = (window, tuning) => {
|
|
|
2142
2142
|
rejectionReason: null,
|
|
2143
2143
|
statistics: null
|
|
2144
2144
|
};
|
|
2145
|
-
const
|
|
2145
|
+
const recentStrongWindow = recentMetrics(turnWindow, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
|
|
2146
|
+
const strongCoverageWindow = selectCoverageWindow(window, tuning.turnMinimumSamples, tuning.turnConfirmationMs, (sample) => sample.gyroscopeSampledAtMs);
|
|
2147
|
+
const strongWindow = strongCoverageWindow ?? recentStrongWindow;
|
|
2146
2148
|
const strongMean = trimmedMean(strongWindow.map((sample) => sample.yaw));
|
|
2147
2149
|
const strongDirection = Math.sign(strongMean);
|
|
2148
2150
|
const strongCoherence = signCoherence(strongWindow.map((sample) => sample.yaw), strongDirection, tuning.turnExitYawThreshold);
|
|
2149
|
-
const strong =
|
|
2150
|
-
const signedAngle = integrateYaw(
|
|
2151
|
+
const strong = strongCoverageWindow !== null && Math.abs(strongMean) >= tuning.turnYawThreshold && strongCoherence >= tuning.turnSignCoherence;
|
|
2152
|
+
const signedAngle = integrateYaw(turnWindow);
|
|
2151
2153
|
const gentleDirection = Math.sign(signedAngle);
|
|
2152
|
-
const gentleCoherence = signCoherence(
|
|
2153
|
-
const yawRms = rootMeanSquare(
|
|
2154
|
-
const rawGyroRms = rootMeanSquare(
|
|
2154
|
+
const gentleCoherence = signCoherence(turnWindow.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold);
|
|
2155
|
+
const yawRms = rootMeanSquare(turnWindow.map((sample) => sample.yaw));
|
|
2156
|
+
const rawGyroRms = rootMeanSquare(turnWindow.map((sample) => sample.rawGyroMagnitude));
|
|
2155
2157
|
const yawDominance = rawGyroRms <= Number.EPSILON ? 0 : Math.min(1, yawRms / rawGyroRms);
|
|
2156
|
-
const lateralRms = rootMeanSquare(
|
|
2157
|
-
const verticalRms = rootMeanSquare(
|
|
2158
|
-
const gentleShape =
|
|
2158
|
+
const lateralRms = rootMeanSquare(turnWindow.map((sample) => sample.lateral));
|
|
2159
|
+
const verticalRms = rootMeanSquare(turnWindow.map((sample) => sample.vertical));
|
|
2160
|
+
const gentleShape = turnWindow.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && gentleCoherence >= tuning.turnSignCoherence;
|
|
2159
2161
|
const verticalDominant = verticalRms > Math.max(lateralRms, tuning.turnVerticalNoiseFloor) * tuning.turnVerticalBumpRatio;
|
|
2160
2162
|
const yawDominant = yawDominance >= tuning.turnYawDominanceThreshold;
|
|
2161
2163
|
const laterallySupported = lateralRms >= tuning.turnLateralEvidenceThreshold;
|
|
@@ -2167,8 +2169,8 @@ const detectTurnEvidence = (window, tuning) => {
|
|
|
2167
2169
|
sampledAtMs: latest.gyroscopeSampledAtMs,
|
|
2168
2170
|
rejectionReason,
|
|
2169
2171
|
statistics: {
|
|
2170
|
-
sampleCount:
|
|
2171
|
-
durationMs: metricWindowDuration(
|
|
2172
|
+
sampleCount: turnWindow.length,
|
|
2173
|
+
durationMs: metricWindowDuration(turnWindow, "gyroscopeSampledAtMs"),
|
|
2172
2174
|
strongMean,
|
|
2173
2175
|
strongCoherence,
|
|
2174
2176
|
signedAngle,
|
|
@@ -2192,10 +2194,29 @@ const recentMetrics = (values, windowMs, timestampKey) => {
|
|
|
2192
2194
|
const latestAtMs = values[values.length - 1]?.[timestampKey] ?? 0;
|
|
2193
2195
|
return values.filter((value) => value[timestampKey] >= latestAtMs - windowMs);
|
|
2194
2196
|
};
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2197
|
+
const MINIMUM_TEMPORAL_SAMPLES = 3;
|
|
2198
|
+
const MAXIMUM_RECOGNITION_GAP_MS = 1e3;
|
|
2199
|
+
const minimumTemporalSamples = (minimumSamples) => Math.min(minimumSamples, MINIMUM_TEMPORAL_SAMPLES);
|
|
2200
|
+
const maximumRecognitionGapMs = (minimumSamples, durationMs) => {
|
|
2201
|
+
const nominalSampleIntervalMs = durationMs / Math.max(1, minimumSamples - 1);
|
|
2202
|
+
return Math.max(MAXIMUM_RECOGNITION_GAP_MS, nominalSampleIntervalMs * 2.5);
|
|
2203
|
+
};
|
|
2204
|
+
const recognitionRetentionMs = (durationMs, minimumSamples) => Math.max(durationMs, maximumRecognitionGapMs(minimumSamples, durationMs) * Math.max(1, minimumTemporalSamples(minimumSamples) - 1));
|
|
2205
|
+
const selectCoverageWindow = (values, minimumSamples, durationMs, timestampOf) => {
|
|
2206
|
+
const requiredSamples = minimumTemporalSamples(minimumSamples);
|
|
2207
|
+
if (values.length < requiredSamples) return null;
|
|
2208
|
+
const lastAtMs = timestampOf(values[values.length - 1]);
|
|
2209
|
+
const maximumGapMs = maximumRecognitionGapMs(minimumSamples, durationMs);
|
|
2210
|
+
for (let startIndex = values.length - requiredSamples; startIndex >= 0; startIndex -= 1) {
|
|
2211
|
+
const coveredValues = values.slice(startIndex);
|
|
2212
|
+
if (lastAtMs - timestampOf(coveredValues[0]) < durationMs) continue;
|
|
2213
|
+
if (coveredValues.slice(1).every((value, index) => timestampOf(value) - timestampOf(coveredValues[index]) < maximumGapMs)) return coveredValues;
|
|
2214
|
+
}
|
|
2215
|
+
return null;
|
|
2216
|
+
};
|
|
2217
|
+
const recentVectors = (values, windowMs) => {
|
|
2218
|
+
const latestAtMs = values[values.length - 1]?.sampledAtMs ?? 0;
|
|
2219
|
+
return values.filter((value) => value.sampledAtMs >= latestAtMs - windowMs);
|
|
2199
2220
|
};
|
|
2200
2221
|
const integrateYaw = (values) => {
|
|
2201
2222
|
let angle = 0;
|
|
@@ -2276,7 +2297,7 @@ const systemClock = {
|
|
|
2276
2297
|
const isBaseStatus = (status) => status === "STOPPED" || status === "STEADY_DRIVING";
|
|
2277
2298
|
const noop = () => void 0;
|
|
2278
2299
|
//#endregion
|
|
2279
|
-
//#region src/
|
|
2300
|
+
//#region src/driving_expression/driving_expression_player.ts
|
|
2280
2301
|
const DEBUG_MODULE = "driving_expression_player.ts";
|
|
2281
2302
|
const MAX_RETAINED_IMAGES = 3;
|
|
2282
2303
|
const POSITIVE_FINITE_DEBUG = {
|