@camstack/addon-osd-manager 0.1.156 → 0.1.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{MaskShapeCanvas-BEVxAjNq.mjs → MaskShapeCanvas-BT714ljC.mjs} +113 -113
- package/dist/MotionZonesSettings-gJgHYJJc.mjs +310 -0
- package/dist/{PrivacyMaskSettings-Ci65qYWB.mjs → PrivacyMaskSettings-HmUc5RFn.mjs} +35 -60
- package/dist/{SceneMonitorEditor-BG9mHyrC.mjs → SceneMonitorEditor-Dqc4eAZT.mjs} +307 -307
- package/dist/_stub.js +12141 -4504
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BDKvyj3r.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BX8u4O5k.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BzuGVFNp.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-CHxDN0gE.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react__loadShare__.js-Bs1t18EM.mjs → _virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react__loadShare__.js-C5jUMd7X.mjs} +1 -1
- package/dist/{_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react_mf_2_dom__loadShare__.js-C0K4h_0e.mjs → _virtual_mf___mfe_internal__addon_osd_manager_page__loadShare__react_mf_2_dom__loadShare__.js-qB5uHZAd.mjs} +8 -7
- package/dist/addon-osd-manager.css +1 -1
- package/dist/{hostInit-DdhUwSkK.mjs → hostInit-o9ggbWOT.mjs} +2 -2
- package/dist/index.js +770 -50
- package/dist/index.mjs +750 -51
- package/dist/{player-overlays-C_Gmuzev.mjs → player-overlays-BgDqoYs3.mjs} +14 -14
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-C-9Ko8kt.mjs → responsive-B7zCyVCX.mjs} +31 -27
- package/dist/{scene-monitor-copy-DudW1LZg.mjs → scene-monitor-copy-DWIcj1X6.mjs} +13 -13
- package/dist/{square-CpweDmax.mjs → square-D0YQUHMC.mjs} +29 -4
- package/dist/{trash-2-cb_YtxOy.mjs → trash-2-C_1H5SIl.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-Dly_n9Qj.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-BAuPDG6y.mjs} +1 -1
- package/package.json +1 -1
- package/dist/MotionZonesSettings-DaDfau7A.mjs +0 -310
- package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BTkfkwrM.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-DmzTg7Gl.mjs +0 -25
package/dist/index.js
CHANGED
|
@@ -15445,6 +15445,51 @@ var SourceInfoSchema = object({
|
|
|
15445
15445
|
raw: record(string(), unknown()).optional()
|
|
15446
15446
|
});
|
|
15447
15447
|
/**
|
|
15448
|
+
* Coerce common unit-of-measurement string variants to a canonical form
|
|
15449
|
+
* so the UI's display templates can rely on stable strings. Pure
|
|
15450
|
+
* passthrough for unknown units — kept conservative on purpose, the goal
|
|
15451
|
+
* is to normalise the long tail of HA `unit_of_measurement` values that
|
|
15452
|
+
* have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
|
|
15453
|
+
*
|
|
15454
|
+
* Extend the map as we encounter real-world data. Returning the raw
|
|
15455
|
+
* input for unknown values is intentional: providers should never
|
|
15456
|
+
* lose information silently here.
|
|
15457
|
+
*/
|
|
15458
|
+
function normalizeUnit(raw) {
|
|
15459
|
+
if (raw == null) return void 0;
|
|
15460
|
+
const trimmed = raw.trim();
|
|
15461
|
+
if (trimmed === "") return void 0;
|
|
15462
|
+
const lower = trimmed.toLowerCase();
|
|
15463
|
+
if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
|
|
15464
|
+
if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
|
|
15465
|
+
if (lower === "k" || lower === "kelvin") return "K";
|
|
15466
|
+
if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
|
|
15467
|
+
if (lower === "lx" || lower === "lux") return "lx";
|
|
15468
|
+
if (lower === "hpa" || lower === "hectopascal") return "hPa";
|
|
15469
|
+
if (lower === "kpa") return "kPa";
|
|
15470
|
+
if (lower === "pa") return "Pa";
|
|
15471
|
+
if (lower === "mbar" || lower === "millibar") return "mbar";
|
|
15472
|
+
if (lower === "w" || lower === "watt") return "W";
|
|
15473
|
+
if (lower === "kw" || lower === "kilowatt") return "kW";
|
|
15474
|
+
if (lower === "wh" || lower === "watt-hour") return "Wh";
|
|
15475
|
+
if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
|
|
15476
|
+
if (lower === "v" || lower === "volt") return "V";
|
|
15477
|
+
if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
|
|
15478
|
+
if (lower === "ma" || lower === "milliampere") return "mA";
|
|
15479
|
+
if (lower === "va") return "VA";
|
|
15480
|
+
if (lower === "ppm") return "ppm";
|
|
15481
|
+
if (lower === "ppb") return "ppb";
|
|
15482
|
+
if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
|
|
15483
|
+
if (lower === "km/h" || lower === "kph") return "km/h";
|
|
15484
|
+
if (lower === "mph") return "mph";
|
|
15485
|
+
if (lower === "m/s") return "m/s";
|
|
15486
|
+
if (lower === "m") return "m";
|
|
15487
|
+
if (lower === "km") return "km";
|
|
15488
|
+
if (lower === "kg") return "kg";
|
|
15489
|
+
if (lower === "g") return "g";
|
|
15490
|
+
return trimmed;
|
|
15491
|
+
}
|
|
15492
|
+
/**
|
|
15448
15493
|
* device-discovery — device-scoped capability for parents that host /
|
|
15449
15494
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
15450
15495
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -39296,28 +39341,50 @@ setOverlay: method(object({
|
|
|
39296
39341
|
*
|
|
39297
39342
|
* This cap owns the other half: a per-(camera, slot) BINDING that says
|
|
39298
39343
|
* which value feeds the slot, how it is formatted, and under which
|
|
39299
|
-
* conditions it is shown at all.
|
|
39300
|
-
*
|
|
39301
|
-
|
|
39302
|
-
|
|
39303
|
-
|
|
39304
|
-
|
|
39305
|
-
|
|
39306
|
-
|
|
39307
|
-
|
|
39308
|
-
|
|
39309
|
-
*
|
|
39310
|
-
*
|
|
39311
|
-
*
|
|
39312
|
-
|
|
39313
|
-
|
|
39314
|
-
|
|
39315
|
-
|
|
39316
|
-
|
|
39317
|
-
|
|
39318
|
-
*
|
|
39319
|
-
*
|
|
39344
|
+
* conditions it is shown at all. Named TEMPLATES combine many device
|
|
39345
|
+
* fields into one overlay string (Scrypted OSD Manager parity).
|
|
39346
|
+
*/
|
|
39347
|
+
/** What `{alias}` expands to for one template binding. Exactly one choice. */
|
|
39348
|
+
var OsdTemplateFormatSchema = _enum([
|
|
39349
|
+
"value+unit",
|
|
39350
|
+
"value",
|
|
39351
|
+
"unit"
|
|
39352
|
+
]);
|
|
39353
|
+
/**
|
|
39354
|
+
* How the operator reached the leaf device. `deviceId` is ALWAYS the
|
|
39355
|
+
* OSD-supported leaf that is read; `containerId` is set when the pick
|
|
39356
|
+
* went through a container (gateway) first.
|
|
39357
|
+
*/
|
|
39358
|
+
var OsdTemplatePickSchema = object({
|
|
39359
|
+
containerId: number().int().optional(),
|
|
39360
|
+
deviceId: number().int()
|
|
39361
|
+
});
|
|
39362
|
+
/**
|
|
39363
|
+
* One named binding inside a multi-device template. The parser only
|
|
39364
|
+
* sees `{alias}`; format / unit / decimals live here structurally.
|
|
39365
|
+
*/
|
|
39366
|
+
var OsdTemplateBindingSchema = object({
|
|
39367
|
+
/** Token in the parser, e.g. `t` → `{t}`. Unique within the template. */
|
|
39368
|
+
alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
|
|
39369
|
+
pick: OsdTemplatePickSchema,
|
|
39370
|
+
capName: string().min(1).max(64),
|
|
39371
|
+
valuePath: string().min(1).max(64),
|
|
39372
|
+
format: OsdTemplateFormatSchema.default("value+unit"),
|
|
39373
|
+
/** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
|
|
39374
|
+
displayUnit: string().max(16).optional(),
|
|
39375
|
+
maxDecimals: number().int().min(0).max(4).default(1)
|
|
39376
|
+
});
|
|
39377
|
+
/**
|
|
39378
|
+
* A named template: bindings (alias → leaf field) + a parser that only
|
|
39379
|
+
* references `{alias}` tokens.
|
|
39320
39380
|
*/
|
|
39381
|
+
var OsdNamedTemplateSchema = object({
|
|
39382
|
+
id: string().min(1).max(64),
|
|
39383
|
+
name: string().min(1).max(64),
|
|
39384
|
+
bindings: array(OsdTemplateBindingSchema).max(32).default([]),
|
|
39385
|
+
/** Parser with alias placeholders, e.g. `{t} / {h}`. */
|
|
39386
|
+
parser: string().max(256).default("")
|
|
39387
|
+
});
|
|
39321
39388
|
/** Where a slot's value comes from. */
|
|
39322
39389
|
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
39323
39390
|
object({
|
|
@@ -39336,11 +39403,17 @@ var OsdSourceSchema = discriminatedUnion("kind", [
|
|
|
39336
39403
|
deviceId: number().int().optional(),
|
|
39337
39404
|
capName: string().min(1).max(64),
|
|
39338
39405
|
/** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
|
|
39339
|
-
valuePath: string().min(1).max(64)
|
|
39406
|
+
valuePath: string().min(1).max(64),
|
|
39407
|
+
/** Convert TO this unit at render. Absent = no conversion. */
|
|
39408
|
+
displayUnit: string().max(16).optional()
|
|
39340
39409
|
}),
|
|
39341
39410
|
object({
|
|
39342
39411
|
kind: literal("latest-recognition"),
|
|
39343
39412
|
recognition: _enum(["person", "plate"])
|
|
39413
|
+
}),
|
|
39414
|
+
object({
|
|
39415
|
+
kind: literal("template"),
|
|
39416
|
+
templateId: string().min(1).max(64)
|
|
39344
39417
|
})
|
|
39345
39418
|
]);
|
|
39346
39419
|
var OsdSlotBindingSchema = object({
|
|
@@ -39356,7 +39429,10 @@ var OsdSlotBindingSchema = object({
|
|
|
39356
39429
|
* documented workaround for firmwares that reject `.` in overlay text.
|
|
39357
39430
|
*/
|
|
39358
39431
|
maxDecimals: number().int().min(0).max(4).default(1),
|
|
39359
|
-
/**
|
|
39432
|
+
/**
|
|
39433
|
+
* Appended via `${unit}` when no unit conversion produces a label.
|
|
39434
|
+
* Prefer `source.displayUnit` for converted device-state values.
|
|
39435
|
+
*/
|
|
39360
39436
|
unitLabel: string().max(8).optional(),
|
|
39361
39437
|
/** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
|
|
39362
39438
|
valueMap: record(string(), string()).optional(),
|
|
@@ -39510,6 +39586,21 @@ var osdManagerCapability = {
|
|
|
39510
39586
|
renderDevice: method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
|
|
39511
39587
|
kind: "mutation",
|
|
39512
39588
|
auth: "admin"
|
|
39589
|
+
}),
|
|
39590
|
+
/** Every named multi-device template. */
|
|
39591
|
+
listTemplates: method(object({}), object({ templates: array(OsdNamedTemplateSchema) }), { auth: "admin" }),
|
|
39592
|
+
/**
|
|
39593
|
+
* Create or replace a named template by `id`. Returns the stored record
|
|
39594
|
+
* after Zod normalisation.
|
|
39595
|
+
*/
|
|
39596
|
+
upsertTemplate: method(object({ template: OsdNamedTemplateSchema }), object({ template: OsdNamedTemplateSchema }), {
|
|
39597
|
+
kind: "mutation",
|
|
39598
|
+
auth: "admin"
|
|
39599
|
+
}),
|
|
39600
|
+
/** Drop a named template. Bindings that still reference it render unreadable. */
|
|
39601
|
+
deleteTemplate: method(object({ templateId: string().min(1).max(64) }), object({ success: literal(true) }), {
|
|
39602
|
+
kind: "mutation",
|
|
39603
|
+
auth: "admin"
|
|
39513
39604
|
})
|
|
39514
39605
|
}
|
|
39515
39606
|
};
|
|
@@ -45260,6 +45351,265 @@ var zoneRulesCapability = {
|
|
|
45260
45351
|
*/
|
|
45261
45352
|
durability: "restored"
|
|
45262
45353
|
};
|
|
45354
|
+
/**
|
|
45355
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
45356
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
45357
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
45358
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
45359
|
+
*/
|
|
45360
|
+
var UNIT_TABLE = {
|
|
45361
|
+
"°C": {
|
|
45362
|
+
dimension: "temperature",
|
|
45363
|
+
factor: 1,
|
|
45364
|
+
offset: 0
|
|
45365
|
+
},
|
|
45366
|
+
"°F": {
|
|
45367
|
+
dimension: "temperature",
|
|
45368
|
+
factor: 5 / 9,
|
|
45369
|
+
offset: -160 / 9
|
|
45370
|
+
},
|
|
45371
|
+
K: {
|
|
45372
|
+
dimension: "temperature",
|
|
45373
|
+
factor: 1,
|
|
45374
|
+
offset: -273.15
|
|
45375
|
+
},
|
|
45376
|
+
hPa: {
|
|
45377
|
+
dimension: "pressure",
|
|
45378
|
+
factor: 1,
|
|
45379
|
+
offset: 0
|
|
45380
|
+
},
|
|
45381
|
+
kPa: {
|
|
45382
|
+
dimension: "pressure",
|
|
45383
|
+
factor: 10,
|
|
45384
|
+
offset: 0
|
|
45385
|
+
},
|
|
45386
|
+
Pa: {
|
|
45387
|
+
dimension: "pressure",
|
|
45388
|
+
factor: .01,
|
|
45389
|
+
offset: 0
|
|
45390
|
+
},
|
|
45391
|
+
mbar: {
|
|
45392
|
+
dimension: "pressure",
|
|
45393
|
+
factor: 1,
|
|
45394
|
+
offset: 0
|
|
45395
|
+
},
|
|
45396
|
+
bar: {
|
|
45397
|
+
dimension: "pressure",
|
|
45398
|
+
factor: 1e3,
|
|
45399
|
+
offset: 0
|
|
45400
|
+
},
|
|
45401
|
+
inHg: {
|
|
45402
|
+
dimension: "pressure",
|
|
45403
|
+
factor: 33.8639,
|
|
45404
|
+
offset: 0
|
|
45405
|
+
},
|
|
45406
|
+
mmHg: {
|
|
45407
|
+
dimension: "pressure",
|
|
45408
|
+
factor: 1.33322,
|
|
45409
|
+
offset: 0
|
|
45410
|
+
},
|
|
45411
|
+
psi: {
|
|
45412
|
+
dimension: "pressure",
|
|
45413
|
+
factor: 68.9476,
|
|
45414
|
+
offset: 0
|
|
45415
|
+
},
|
|
45416
|
+
"m/s": {
|
|
45417
|
+
dimension: "speed",
|
|
45418
|
+
factor: 1,
|
|
45419
|
+
offset: 0
|
|
45420
|
+
},
|
|
45421
|
+
"km/h": {
|
|
45422
|
+
dimension: "speed",
|
|
45423
|
+
factor: 1 / 3.6,
|
|
45424
|
+
offset: 0
|
|
45425
|
+
},
|
|
45426
|
+
mph: {
|
|
45427
|
+
dimension: "speed",
|
|
45428
|
+
factor: .44704,
|
|
45429
|
+
offset: 0
|
|
45430
|
+
},
|
|
45431
|
+
kn: {
|
|
45432
|
+
dimension: "speed",
|
|
45433
|
+
factor: .514444,
|
|
45434
|
+
offset: 0
|
|
45435
|
+
},
|
|
45436
|
+
"mm/h": {
|
|
45437
|
+
dimension: "precipitation-rate",
|
|
45438
|
+
factor: 1,
|
|
45439
|
+
offset: 0
|
|
45440
|
+
},
|
|
45441
|
+
"in/h": {
|
|
45442
|
+
dimension: "precipitation-rate",
|
|
45443
|
+
factor: 25.4,
|
|
45444
|
+
offset: 0
|
|
45445
|
+
},
|
|
45446
|
+
m: {
|
|
45447
|
+
dimension: "length",
|
|
45448
|
+
factor: 1,
|
|
45449
|
+
offset: 0
|
|
45450
|
+
},
|
|
45451
|
+
mm: {
|
|
45452
|
+
dimension: "length",
|
|
45453
|
+
factor: .001,
|
|
45454
|
+
offset: 0
|
|
45455
|
+
},
|
|
45456
|
+
cm: {
|
|
45457
|
+
dimension: "length",
|
|
45458
|
+
factor: .01,
|
|
45459
|
+
offset: 0
|
|
45460
|
+
},
|
|
45461
|
+
km: {
|
|
45462
|
+
dimension: "length",
|
|
45463
|
+
factor: 1e3,
|
|
45464
|
+
offset: 0
|
|
45465
|
+
},
|
|
45466
|
+
in: {
|
|
45467
|
+
dimension: "length",
|
|
45468
|
+
factor: .0254,
|
|
45469
|
+
offset: 0
|
|
45470
|
+
},
|
|
45471
|
+
ft: {
|
|
45472
|
+
dimension: "length",
|
|
45473
|
+
factor: .3048,
|
|
45474
|
+
offset: 0
|
|
45475
|
+
},
|
|
45476
|
+
mi: {
|
|
45477
|
+
dimension: "length",
|
|
45478
|
+
factor: 1609.344,
|
|
45479
|
+
offset: 0
|
|
45480
|
+
},
|
|
45481
|
+
lx: {
|
|
45482
|
+
dimension: "illuminance",
|
|
45483
|
+
factor: 1,
|
|
45484
|
+
offset: 0
|
|
45485
|
+
},
|
|
45486
|
+
"W/m²": {
|
|
45487
|
+
dimension: "irradiance",
|
|
45488
|
+
factor: 1,
|
|
45489
|
+
offset: 0
|
|
45490
|
+
},
|
|
45491
|
+
W: {
|
|
45492
|
+
dimension: "power",
|
|
45493
|
+
factor: 1,
|
|
45494
|
+
offset: 0
|
|
45495
|
+
},
|
|
45496
|
+
kW: {
|
|
45497
|
+
dimension: "power",
|
|
45498
|
+
factor: 1e3,
|
|
45499
|
+
offset: 0
|
|
45500
|
+
},
|
|
45501
|
+
VA: {
|
|
45502
|
+
dimension: "apparent-power",
|
|
45503
|
+
factor: 1,
|
|
45504
|
+
offset: 0
|
|
45505
|
+
},
|
|
45506
|
+
Wh: {
|
|
45507
|
+
dimension: "energy",
|
|
45508
|
+
factor: 1,
|
|
45509
|
+
offset: 0
|
|
45510
|
+
},
|
|
45511
|
+
kWh: {
|
|
45512
|
+
dimension: "energy",
|
|
45513
|
+
factor: 1e3,
|
|
45514
|
+
offset: 0
|
|
45515
|
+
},
|
|
45516
|
+
MWh: {
|
|
45517
|
+
dimension: "energy",
|
|
45518
|
+
factor: 1e6,
|
|
45519
|
+
offset: 0
|
|
45520
|
+
},
|
|
45521
|
+
V: {
|
|
45522
|
+
dimension: "voltage",
|
|
45523
|
+
factor: 1,
|
|
45524
|
+
offset: 0
|
|
45525
|
+
},
|
|
45526
|
+
mV: {
|
|
45527
|
+
dimension: "voltage",
|
|
45528
|
+
factor: .001,
|
|
45529
|
+
offset: 0
|
|
45530
|
+
},
|
|
45531
|
+
A: {
|
|
45532
|
+
dimension: "current",
|
|
45533
|
+
factor: 1,
|
|
45534
|
+
offset: 0
|
|
45535
|
+
},
|
|
45536
|
+
mA: {
|
|
45537
|
+
dimension: "current",
|
|
45538
|
+
factor: .001,
|
|
45539
|
+
offset: 0
|
|
45540
|
+
},
|
|
45541
|
+
ppm: {
|
|
45542
|
+
dimension: "concentration-volume",
|
|
45543
|
+
factor: 1,
|
|
45544
|
+
offset: 0
|
|
45545
|
+
},
|
|
45546
|
+
ppb: {
|
|
45547
|
+
dimension: "concentration-volume",
|
|
45548
|
+
factor: .001,
|
|
45549
|
+
offset: 0
|
|
45550
|
+
},
|
|
45551
|
+
"µg/m³": {
|
|
45552
|
+
dimension: "concentration-mass",
|
|
45553
|
+
factor: 1,
|
|
45554
|
+
offset: 0
|
|
45555
|
+
},
|
|
45556
|
+
"mg/m³": {
|
|
45557
|
+
dimension: "concentration-mass",
|
|
45558
|
+
factor: 1e3,
|
|
45559
|
+
offset: 0
|
|
45560
|
+
},
|
|
45561
|
+
kg: {
|
|
45562
|
+
dimension: "mass",
|
|
45563
|
+
factor: 1,
|
|
45564
|
+
offset: 0
|
|
45565
|
+
},
|
|
45566
|
+
g: {
|
|
45567
|
+
dimension: "mass",
|
|
45568
|
+
factor: .001,
|
|
45569
|
+
offset: 0
|
|
45570
|
+
},
|
|
45571
|
+
lb: {
|
|
45572
|
+
dimension: "mass",
|
|
45573
|
+
factor: .453592,
|
|
45574
|
+
offset: 0
|
|
45575
|
+
},
|
|
45576
|
+
oz: {
|
|
45577
|
+
dimension: "mass",
|
|
45578
|
+
factor: .0283495,
|
|
45579
|
+
offset: 0
|
|
45580
|
+
},
|
|
45581
|
+
"%": {
|
|
45582
|
+
dimension: "percentage",
|
|
45583
|
+
factor: 1,
|
|
45584
|
+
offset: 0
|
|
45585
|
+
}
|
|
45586
|
+
};
|
|
45587
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
45588
|
+
function specFor(unit) {
|
|
45589
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
45590
|
+
}
|
|
45591
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
45592
|
+
function toCanonical(value, spec) {
|
|
45593
|
+
return value * spec.factor + spec.offset;
|
|
45594
|
+
}
|
|
45595
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
45596
|
+
function fromCanonical(canonical, spec) {
|
|
45597
|
+
return (canonical - spec.offset) / spec.factor;
|
|
45598
|
+
}
|
|
45599
|
+
/**
|
|
45600
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
45601
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
45602
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
45603
|
+
*/
|
|
45604
|
+
function tryConvertUnit(value, from, to) {
|
|
45605
|
+
if (from === to) return value;
|
|
45606
|
+
const fromSpec = specFor(from);
|
|
45607
|
+
if (!fromSpec) return void 0;
|
|
45608
|
+
const toSpec = specFor(to);
|
|
45609
|
+
if (!toSpec) return void 0;
|
|
45610
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
45611
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
45612
|
+
}
|
|
45263
45613
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
45264
45614
|
/**
|
|
45265
45615
|
* Most specific first. Extending this list is how a new device kind becomes
|
|
@@ -50628,6 +50978,12 @@ Object.freeze({
|
|
|
50628
50978
|
addonId: null,
|
|
50629
50979
|
access: "create"
|
|
50630
50980
|
},
|
|
50981
|
+
"osdManager.deleteTemplate": {
|
|
50982
|
+
capName: "osd-manager",
|
|
50983
|
+
capScope: "system",
|
|
50984
|
+
addonId: null,
|
|
50985
|
+
access: "delete"
|
|
50986
|
+
},
|
|
50631
50987
|
"osdManager.getConditionSupport": {
|
|
50632
50988
|
capName: "osd-manager",
|
|
50633
50989
|
capScope: "system",
|
|
@@ -50646,6 +51002,12 @@ Object.freeze({
|
|
|
50646
51002
|
addonId: null,
|
|
50647
51003
|
access: "view"
|
|
50648
51004
|
},
|
|
51005
|
+
"osdManager.listTemplates": {
|
|
51006
|
+
capName: "osd-manager",
|
|
51007
|
+
capScope: "system",
|
|
51008
|
+
addonId: null,
|
|
51009
|
+
access: "view"
|
|
51010
|
+
},
|
|
50649
51011
|
"osdManager.previewSlot": {
|
|
50650
51012
|
capName: "osd-manager",
|
|
50651
51013
|
capScope: "system",
|
|
@@ -50664,6 +51026,12 @@ Object.freeze({
|
|
|
50664
51026
|
addonId: null,
|
|
50665
51027
|
access: "create"
|
|
50666
51028
|
},
|
|
51029
|
+
"osdManager.upsertTemplate": {
|
|
51030
|
+
capName: "osd-manager",
|
|
51031
|
+
capScope: "system",
|
|
51032
|
+
addonId: null,
|
|
51033
|
+
access: "create"
|
|
51034
|
+
},
|
|
50667
51035
|
"petFeeder.callPet": {
|
|
50668
51036
|
capName: "pet-feeder",
|
|
50669
51037
|
capScope: "device",
|
|
@@ -57912,10 +58280,10 @@ function limitText(text, max) {
|
|
|
57912
58280
|
return `${text.slice(0, max - 3)}...`;
|
|
57913
58281
|
}
|
|
57914
58282
|
/** Turn a raw state value into the string that goes into `${value}`. */
|
|
57915
|
-
function displayValue(raw, binding) {
|
|
58283
|
+
function displayValue(raw, binding, maxDecimals = binding.maxDecimals) {
|
|
57916
58284
|
const mapped = binding.valueMap?.[String(raw)];
|
|
57917
58285
|
if (mapped !== void 0) return mapped;
|
|
57918
|
-
if (typeof raw === "number") return truncateDecimals(raw,
|
|
58286
|
+
if (typeof raw === "number") return truncateDecimals(raw, maxDecimals);
|
|
57919
58287
|
if (typeof raw === "boolean") return raw ? "ON" : "OFF";
|
|
57920
58288
|
if (raw === null || raw === void 0) return "";
|
|
57921
58289
|
if (typeof raw === "string") return raw;
|
|
@@ -57929,9 +58297,10 @@ function applyTemplate(template, value, unit) {
|
|
|
57929
58297
|
return template.replaceAll("${value}", value).replaceAll("${unit}", unit);
|
|
57930
58298
|
}
|
|
57931
58299
|
/** The whole formatting chain: raw value → the text to burn into the slot. */
|
|
57932
|
-
function formatSlotText(raw, binding) {
|
|
57933
|
-
const value = displayValue(raw, binding);
|
|
57934
|
-
|
|
58300
|
+
function formatSlotText(raw, binding, options = {}) {
|
|
58301
|
+
const value = displayValue(options.displayRaw ?? raw, binding);
|
|
58302
|
+
const unit = options.unit ?? binding.unitLabel ?? "";
|
|
58303
|
+
return limitText(applyTemplate(binding.template, value, unit).trim(), binding.maxCharacters);
|
|
57935
58304
|
}
|
|
57936
58305
|
//#endregion
|
|
57937
58306
|
//#region src/state-leaves.ts
|
|
@@ -58019,6 +58388,144 @@ function readStatePath(slice, path) {
|
|
|
58019
58388
|
return current;
|
|
58020
58389
|
}
|
|
58021
58390
|
//#endregion
|
|
58391
|
+
//#region src/template-tokens.ts
|
|
58392
|
+
/** Build the placeholder token for one binding alias. */
|
|
58393
|
+
function aliasToken(alias) {
|
|
58394
|
+
return `{${alias}}`;
|
|
58395
|
+
}
|
|
58396
|
+
/** Chip / parser token for a binding. */
|
|
58397
|
+
function bindingToken(binding) {
|
|
58398
|
+
return aliasToken(binding.alias);
|
|
58399
|
+
}
|
|
58400
|
+
/** Parse `{alias}` tokens from a parser string (letters, digits, `_`, `-`). */
|
|
58401
|
+
function parseAliasTokens(parser) {
|
|
58402
|
+
const out = [];
|
|
58403
|
+
for (const match of parser.matchAll(/\{([A-Za-z][A-Za-z0-9_-]*)\}/g)) {
|
|
58404
|
+
const alias = match[1];
|
|
58405
|
+
if (alias === void 0 || alias === "") continue;
|
|
58406
|
+
if (!out.includes(alias)) out.push(alias);
|
|
58407
|
+
}
|
|
58408
|
+
return out;
|
|
58409
|
+
}
|
|
58410
|
+
/** Replace every occurrence of `token` in `parser` with `value`. */
|
|
58411
|
+
function replaceToken(parser, token, value) {
|
|
58412
|
+
return parser.replaceAll(token, value);
|
|
58413
|
+
}
|
|
58414
|
+
/**
|
|
58415
|
+
* Suggest a short unique alias from a device/field name.
|
|
58416
|
+
* Falls back to `b1`, `b2`, … when the slug collides.
|
|
58417
|
+
*/
|
|
58418
|
+
function suggestAlias(seed, taken, fallbackPrefix = "b") {
|
|
58419
|
+
const slug = seed.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^(\d)/, "a$1").slice(0, 24);
|
|
58420
|
+
const base = slug.length > 0 ? slug : fallbackPrefix;
|
|
58421
|
+
if (!taken.has(base)) return base;
|
|
58422
|
+
for (let i = 2; i < 1e3; i += 1) {
|
|
58423
|
+
const candidate = `${base}${i}`.slice(0, 32);
|
|
58424
|
+
if (!taken.has(candidate)) return candidate;
|
|
58425
|
+
}
|
|
58426
|
+
return `${fallbackPrefix}${Date.now().toString(36)}`;
|
|
58427
|
+
}
|
|
58428
|
+
//#endregion
|
|
58429
|
+
//#region src/unit-display.ts
|
|
58430
|
+
/**
|
|
58431
|
+
* Convert a numeric state value for OSD display using `@camstack/types`
|
|
58432
|
+
* UNIT_TABLE / tryConvertUnit (same set as D699).
|
|
58433
|
+
*/
|
|
58434
|
+
/**
|
|
58435
|
+
* Convert `raw` from `sourceUnit` to `displayUnit` when both are known and
|
|
58436
|
+
* same-dimension. On refusal, keep the raw value and the source unit so the
|
|
58437
|
+
* label never lies about the number.
|
|
58438
|
+
*/
|
|
58439
|
+
function convertForDisplay(raw, sourceUnit, displayUnit) {
|
|
58440
|
+
const from = sourceUnit !== void 0 ? normalizeUnit(sourceUnit) : void 0;
|
|
58441
|
+
const to = displayUnit !== void 0 ? normalizeUnit(displayUnit) : void 0;
|
|
58442
|
+
if (from === void 0 || to === void 0 || from === to) return {
|
|
58443
|
+
value: raw,
|
|
58444
|
+
unit: to ?? from ?? ""
|
|
58445
|
+
};
|
|
58446
|
+
const converted = tryConvertUnit(raw, from, to);
|
|
58447
|
+
if (converted === void 0) return {
|
|
58448
|
+
value: raw,
|
|
58449
|
+
unit: from
|
|
58450
|
+
};
|
|
58451
|
+
return {
|
|
58452
|
+
value: converted,
|
|
58453
|
+
unit: to
|
|
58454
|
+
};
|
|
58455
|
+
}
|
|
58456
|
+
/**
|
|
58457
|
+
* Best-effort unit from a state slice: sibling `unit` at the same depth as
|
|
58458
|
+
* `valuePath`, else top-level `unit`.
|
|
58459
|
+
*/
|
|
58460
|
+
function inferSourceUnit(slice, valuePath) {
|
|
58461
|
+
if (typeof slice !== "object" || slice === null || Array.isArray(slice)) return void 0;
|
|
58462
|
+
const root = { ...slice };
|
|
58463
|
+
const segments = valuePath.split(".");
|
|
58464
|
+
if (segments.length >= 1) {
|
|
58465
|
+
let parent = root;
|
|
58466
|
+
for (const segment of segments.slice(0, -1)) {
|
|
58467
|
+
if (typeof parent !== "object" || parent === null || Array.isArray(parent)) {
|
|
58468
|
+
parent = void 0;
|
|
58469
|
+
break;
|
|
58470
|
+
}
|
|
58471
|
+
parent = { ...parent }[segment];
|
|
58472
|
+
}
|
|
58473
|
+
if (typeof parent === "object" && parent !== null && !Array.isArray(parent)) {
|
|
58474
|
+
const unit = { ...parent }.unit;
|
|
58475
|
+
if (typeof unit === "string" && unit.length > 0) return unit;
|
|
58476
|
+
}
|
|
58477
|
+
}
|
|
58478
|
+
const top = root.unit;
|
|
58479
|
+
return typeof top === "string" && top.length > 0 ? top : void 0;
|
|
58480
|
+
}
|
|
58481
|
+
//#endregion
|
|
58482
|
+
//#region src/named-template-render.ts
|
|
58483
|
+
/** Combine value/unit according to the binding's single format choice. */
|
|
58484
|
+
function applyTemplateFormat(value, unit, format) {
|
|
58485
|
+
if (format === "value") return value;
|
|
58486
|
+
if (format === "unit") return unit;
|
|
58487
|
+
const v = value.trim();
|
|
58488
|
+
const u = unit.trim();
|
|
58489
|
+
if (v === "") return u;
|
|
58490
|
+
if (u === "") return v;
|
|
58491
|
+
return `${v} ${u}`;
|
|
58492
|
+
}
|
|
58493
|
+
/** Resolve one binding to live value/unit/formatted parts (or a reason). */
|
|
58494
|
+
function resolveBindingLive(binding, facts) {
|
|
58495
|
+
const deviceId = binding.pick.deviceId;
|
|
58496
|
+
const deviceState = facts.stateByDevice.get(deviceId);
|
|
58497
|
+
if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
|
|
58498
|
+
const slice = deviceState[binding.capName];
|
|
58499
|
+
if (slice === void 0) return { reason: `device ${deviceId} publishes no "${binding.capName}" state` };
|
|
58500
|
+
const raw = readStatePath(slice, binding.valuePath);
|
|
58501
|
+
if (raw === void 0) return { reason: `"${binding.capName}.${binding.valuePath}" is absent on device ${deviceId}` };
|
|
58502
|
+
let value;
|
|
58503
|
+
let unit = "";
|
|
58504
|
+
if (typeof raw === "number") {
|
|
58505
|
+
const converted = convertForDisplay(raw, inferSourceUnit(slice, binding.valuePath), binding.displayUnit);
|
|
58506
|
+
value = truncateDecimals(converted.value, binding.maxDecimals);
|
|
58507
|
+
unit = converted.unit;
|
|
58508
|
+
} else if (typeof raw === "boolean") value = raw ? "ON" : "OFF";
|
|
58509
|
+
else if (raw === null) value = "";
|
|
58510
|
+
else value = String(raw);
|
|
58511
|
+
return {
|
|
58512
|
+
value,
|
|
58513
|
+
unit,
|
|
58514
|
+
formatted: applyTemplateFormat(value, unit, binding.format)
|
|
58515
|
+
};
|
|
58516
|
+
}
|
|
58517
|
+
/** Render one named template's parser from live states. */
|
|
58518
|
+
function renderNamedTemplate(template, facts) {
|
|
58519
|
+
let text = template.parser;
|
|
58520
|
+
if (template.bindings.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no bindings` } : { text };
|
|
58521
|
+
for (const binding of template.bindings) {
|
|
58522
|
+
const live = resolveBindingLive(binding, facts);
|
|
58523
|
+
if ("reason" in live) return live;
|
|
58524
|
+
text = replaceToken(text, aliasToken(binding.alias), live.formatted);
|
|
58525
|
+
}
|
|
58526
|
+
return { text };
|
|
58527
|
+
}
|
|
58528
|
+
//#endregion
|
|
58022
58529
|
//#region src/render.ts
|
|
58023
58530
|
/**
|
|
58024
58531
|
* Read the source's value. `null` means "unreadable" — a missing device, a
|
|
@@ -58033,6 +58540,16 @@ function resolveSource(binding, cameraDeviceId, facts) {
|
|
|
58033
58540
|
const value = facts.recognitionByDevice.get(cameraDeviceId)?.[source.recognition];
|
|
58034
58541
|
return value === void 0 ? { reason: `no recognized ${source.recognition} has been persisted for device ${cameraDeviceId}` } : { value: { raw: value } };
|
|
58035
58542
|
}
|
|
58543
|
+
if (source.kind === "template") {
|
|
58544
|
+
const named = facts.templatesById.get(source.templateId);
|
|
58545
|
+
if (named === void 0) return { reason: `named template "${source.templateId}" is missing` };
|
|
58546
|
+
const rendered = renderNamedTemplate(named, { stateByDevice: facts.stateByDevice });
|
|
58547
|
+
if ("reason" in rendered) return rendered;
|
|
58548
|
+
return { value: {
|
|
58549
|
+
raw: rendered.text,
|
|
58550
|
+
preformatted: rendered.text
|
|
58551
|
+
} };
|
|
58552
|
+
}
|
|
58036
58553
|
const deviceId = source.deviceId ?? cameraDeviceId;
|
|
58037
58554
|
const deviceState = facts.stateByDevice.get(deviceId);
|
|
58038
58555
|
if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
|
|
@@ -58040,6 +58557,14 @@ function resolveSource(binding, cameraDeviceId, facts) {
|
|
|
58040
58557
|
if (slice === void 0) return { reason: `device ${deviceId} publishes no "${source.capName}" state` };
|
|
58041
58558
|
const raw = readStatePath(slice, source.valuePath);
|
|
58042
58559
|
if (raw === void 0) return { reason: `"${source.capName}.${source.valuePath}" is absent on device ${deviceId}` };
|
|
58560
|
+
if (typeof raw === "number") {
|
|
58561
|
+
const converted = convertForDisplay(raw, inferSourceUnit(slice, source.valuePath), source.displayUnit);
|
|
58562
|
+
return { value: {
|
|
58563
|
+
raw,
|
|
58564
|
+
displayRaw: converted.value,
|
|
58565
|
+
unit: converted.unit || binding.unitLabel
|
|
58566
|
+
} };
|
|
58567
|
+
}
|
|
58043
58568
|
return { value: { raw } };
|
|
58044
58569
|
}
|
|
58045
58570
|
/**
|
|
@@ -58072,7 +58597,12 @@ function renderSlot(slotId, binding, cameraDeviceId, facts, previousText) {
|
|
|
58072
58597
|
if (!gate.open) return settle(slotId, fallback, previousText, "gated", `condition "${gate.failedCondition}"`);
|
|
58073
58598
|
const resolved = resolveSource(binding, cameraDeviceId, facts);
|
|
58074
58599
|
if ("reason" in resolved) return settle(slotId, fallback, previousText, "unreadable", resolved.reason);
|
|
58075
|
-
return settle(slotId,
|
|
58600
|
+
if (resolved.value.preformatted !== void 0 && binding.source.kind === "template") return settle(slotId, limitText(resolved.value.preformatted.trim(), binding.maxCharacters), previousText, "written");
|
|
58601
|
+
const unit = resolved.value.unit ?? binding.unitLabel;
|
|
58602
|
+
return settle(slotId, formatSlotText(resolved.value.raw, binding, {
|
|
58603
|
+
...resolved.value.displayRaw !== void 0 ? { displayRaw: resolved.value.displayRaw } : {},
|
|
58604
|
+
...unit !== void 0 ? { unit } : {}
|
|
58605
|
+
}), previousText, "written");
|
|
58076
58606
|
}
|
|
58077
58607
|
function settle(slotId, text, previousText, outcome, reason) {
|
|
58078
58608
|
if (previousText !== void 0 && previousText === text) return {
|
|
@@ -58093,20 +58623,98 @@ function settle(slotId, text, previousText, outcome, reason) {
|
|
|
58093
58623
|
};
|
|
58094
58624
|
}
|
|
58095
58625
|
//#endregion
|
|
58626
|
+
//#region src/osd-source-fields.ts
|
|
58627
|
+
/** Caps the OSD source picker may bind. */
|
|
58628
|
+
var OSD_SOURCE_CAP_NAMES = new Set([
|
|
58629
|
+
"temperature-sensor",
|
|
58630
|
+
"humidity-sensor",
|
|
58631
|
+
"climate-control",
|
|
58632
|
+
"numeric-sensor",
|
|
58633
|
+
"weather",
|
|
58634
|
+
"air-quality-sensor",
|
|
58635
|
+
"lock-control",
|
|
58636
|
+
"contact",
|
|
58637
|
+
"binary",
|
|
58638
|
+
"battery"
|
|
58639
|
+
]);
|
|
58640
|
+
/**
|
|
58641
|
+
* Per-cap leaf allowlist. Absent entry ⇒ every leaf from `stateLeavesOf`
|
|
58642
|
+
* that survives bookkeeping (single-metric sensors). Present ⇒ only these
|
|
58643
|
+
* paths (climate / weather / battery).
|
|
58644
|
+
*/
|
|
58645
|
+
var OSD_SOURCE_PATHS = new Map([
|
|
58646
|
+
["climate-control", new Set([
|
|
58647
|
+
"currentTemp",
|
|
58648
|
+
"currentHumidity",
|
|
58649
|
+
"target",
|
|
58650
|
+
"targetHumidity",
|
|
58651
|
+
"mode"
|
|
58652
|
+
])],
|
|
58653
|
+
["weather", new Set([
|
|
58654
|
+
"condition",
|
|
58655
|
+
"temperature",
|
|
58656
|
+
"humidity",
|
|
58657
|
+
"pressure",
|
|
58658
|
+
"windSpeed",
|
|
58659
|
+
"windBearing"
|
|
58660
|
+
])],
|
|
58661
|
+
["battery", new Set(["percentage"])],
|
|
58662
|
+
["temperature-sensor", new Set(["celsius"])],
|
|
58663
|
+
["humidity-sensor", new Set(["percent"])],
|
|
58664
|
+
["numeric-sensor", new Set(["value"])],
|
|
58665
|
+
["air-quality-sensor", new Set([
|
|
58666
|
+
"co2Ppm",
|
|
58667
|
+
"vocPpb",
|
|
58668
|
+
"pm25",
|
|
58669
|
+
"pm10",
|
|
58670
|
+
"aqi"
|
|
58671
|
+
])],
|
|
58672
|
+
["binary", new Set(["on"])],
|
|
58673
|
+
["contact", new Set(["entryOpen"])],
|
|
58674
|
+
["lock-control", new Set(["state"])]
|
|
58675
|
+
]);
|
|
58676
|
+
function isOsdSourceCap(capName) {
|
|
58677
|
+
return OSD_SOURCE_CAP_NAMES.has(capName);
|
|
58678
|
+
}
|
|
58679
|
+
/** Bindable OSD leaves for one cap — empty when the cap is not OSD-relevant. */
|
|
58680
|
+
function osdStateLeavesOf(cap) {
|
|
58681
|
+
if (!isOsdSourceCap(cap.name)) return [];
|
|
58682
|
+
const leaves = stateLeavesOf(cap);
|
|
58683
|
+
const allowed = OSD_SOURCE_PATHS.get(cap.name);
|
|
58684
|
+
if (allowed === void 0) return leaves;
|
|
58685
|
+
return leaves.filter((leaf) => allowed.has(leaf.path));
|
|
58686
|
+
}
|
|
58687
|
+
/** Device types that never contribute their OWN catalog leaves. */
|
|
58688
|
+
var OSD_NON_LEAF_TYPES = new Set(["container"]);
|
|
58689
|
+
/** True when this device type may expose OSD leaves (not a container gateway). */
|
|
58690
|
+
function isOsdLeafDeviceType(type) {
|
|
58691
|
+
if (type === null || type === void 0) return true;
|
|
58692
|
+
return !OSD_NON_LEAF_TYPES.has(type);
|
|
58693
|
+
}
|
|
58694
|
+
/** @deprecated Prefer {@link isOsdLeafDeviceType}. */
|
|
58695
|
+
var OSD_PICKER_EXCLUDED_TYPES = OSD_NON_LEAF_TYPES;
|
|
58696
|
+
/** @deprecated Prefer {@link isOsdLeafDeviceType}. */
|
|
58697
|
+
function isOsdPickerDeviceType(type) {
|
|
58698
|
+
return isOsdLeafDeviceType(type);
|
|
58699
|
+
}
|
|
58700
|
+
function isContainerDeviceType(type) {
|
|
58701
|
+
return type === "container";
|
|
58702
|
+
}
|
|
58703
|
+
//#endregion
|
|
58096
58704
|
//#region src/source-catalog.ts
|
|
58097
58705
|
/**
|
|
58098
|
-
* Build the editor's source picker from
|
|
58099
|
-
*
|
|
58100
|
-
*
|
|
58706
|
+
* Build the editor's source picker from OSD-relevant cap definitions.
|
|
58707
|
+
* Gateway / infrastructure caps (broker, mqtt, deviceExport, …) never
|
|
58708
|
+
* appear — see `osd-source-fields.ts`.
|
|
58101
58709
|
*/
|
|
58102
58710
|
/** Turn `air-quality-sensor` + `pm25` into `Air Quality Sensor · pm25`. */
|
|
58103
58711
|
function humanizeCapName(capName) {
|
|
58104
58712
|
return capName.split("-").map((part) => part.length === 0 ? part : part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
58105
58713
|
}
|
|
58106
58714
|
/**
|
|
58107
|
-
* Every bindable value across the cluster, with the camera's own
|
|
58108
|
-
* first — that is what an operator reaches for, and a picker that
|
|
58109
|
-
* it under 200 other devices is a picker nobody uses.
|
|
58715
|
+
* Every OSD-bindable value across the cluster, with the camera's own
|
|
58716
|
+
* state first — that is what an operator reaches for, and a picker that
|
|
58717
|
+
* buries it under 200 other devices is a picker nobody uses.
|
|
58110
58718
|
*/
|
|
58111
58719
|
function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
|
|
58112
58720
|
const capsByName = new Map(ALL_CAPABILITY_DEFINITIONS.map((cap) => [cap.name, cap]));
|
|
@@ -58116,22 +58724,44 @@ function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
|
|
|
58116
58724
|
return a.id - b.id;
|
|
58117
58725
|
});
|
|
58118
58726
|
const options = [];
|
|
58119
|
-
for (const device of ordered)
|
|
58120
|
-
|
|
58121
|
-
|
|
58122
|
-
|
|
58123
|
-
|
|
58124
|
-
|
|
58125
|
-
|
|
58126
|
-
|
|
58127
|
-
|
|
58128
|
-
|
|
58129
|
-
|
|
58130
|
-
|
|
58727
|
+
for (const device of ordered) {
|
|
58728
|
+
if (!isOsdLeafDeviceType(device.type ?? null)) continue;
|
|
58729
|
+
for (const capName of capsByDevice.get(device.id) ?? []) {
|
|
58730
|
+
if (!isOsdSourceCap(capName)) continue;
|
|
58731
|
+
const cap = capsByName.get(capName);
|
|
58732
|
+
if (cap === void 0) continue;
|
|
58733
|
+
for (const leaf of osdStateLeavesOf(cap)) options.push({
|
|
58734
|
+
deviceId: device.id,
|
|
58735
|
+
deviceName: device.name,
|
|
58736
|
+
capName,
|
|
58737
|
+
valuePath: leaf.path,
|
|
58738
|
+
label: `${humanizeCapName(capName)} · ${leaf.path}`,
|
|
58739
|
+
valueType: leaf.valueType,
|
|
58740
|
+
...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
|
|
58741
|
+
});
|
|
58742
|
+
}
|
|
58131
58743
|
}
|
|
58132
58744
|
return options;
|
|
58133
58745
|
}
|
|
58134
58746
|
//#endregion
|
|
58747
|
+
//#region src/templates-store.ts
|
|
58748
|
+
/**
|
|
58749
|
+
* Named OSD templates — durable in the addon's own (global) store.
|
|
58750
|
+
*/
|
|
58751
|
+
var OSD_TEMPLATES_KEY = "namedTemplates";
|
|
58752
|
+
var OsdNamedTemplatesSchema = array(OsdNamedTemplateSchema);
|
|
58753
|
+
var EMPTY_TEMPLATES = [];
|
|
58754
|
+
function upsertNamedTemplate(prev, template) {
|
|
58755
|
+
const parsed = OsdNamedTemplateSchema.parse(template);
|
|
58756
|
+
return [...prev.filter((t) => t.id !== parsed.id), parsed].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
58757
|
+
}
|
|
58758
|
+
function deleteNamedTemplate(prev, templateId) {
|
|
58759
|
+
return prev.filter((t) => t.id !== templateId);
|
|
58760
|
+
}
|
|
58761
|
+
function findNamedTemplate(templates, templateId) {
|
|
58762
|
+
return templates.find((t) => t.id === templateId);
|
|
58763
|
+
}
|
|
58764
|
+
//#endregion
|
|
58135
58765
|
//#region src/osd-manager.ts
|
|
58136
58766
|
/**
|
|
58137
58767
|
* The `osd-manager` provider — the render loop and the operator surface.
|
|
@@ -58495,6 +59125,26 @@ var OsdManager = class {
|
|
|
58495
59125
|
renderDevice = async ({ deviceId }) => {
|
|
58496
59126
|
return { results: [...await this.renderDeviceInternal(deviceId, null)] };
|
|
58497
59127
|
};
|
|
59128
|
+
listTemplates = async () => {
|
|
59129
|
+
return { templates: [...await this.deps.templates.get()] };
|
|
59130
|
+
};
|
|
59131
|
+
upsertTemplate = async ({ template }) => {
|
|
59132
|
+
const next = upsertNamedTemplate(await this.deps.templates.get(), template);
|
|
59133
|
+
await this.deps.templates.set(next);
|
|
59134
|
+
const stored = findNamedTemplate(next, template.id);
|
|
59135
|
+
if (stored === void 0) throw new Error(`osd-manager: template "${template.id}" missing after upsert`);
|
|
59136
|
+
this.logger.info("osd-manager: named template upserted", { meta: {
|
|
59137
|
+
templateId: stored.id,
|
|
59138
|
+
bindingCount: stored.bindings.length
|
|
59139
|
+
} });
|
|
59140
|
+
return { template: stored };
|
|
59141
|
+
};
|
|
59142
|
+
deleteTemplate = async ({ templateId }) => {
|
|
59143
|
+
const prev = await this.deps.templates.get();
|
|
59144
|
+
await this.deps.templates.set(deleteNamedTemplate(prev, templateId));
|
|
59145
|
+
this.logger.info("osd-manager: named template deleted", { meta: { templateId } });
|
|
59146
|
+
return { success: true };
|
|
59147
|
+
};
|
|
58498
59148
|
async renderAllBound() {
|
|
58499
59149
|
for (const deviceId of [...this.boundDevices]) try {
|
|
58500
59150
|
await this.renderDeviceInternal(deviceId, null);
|
|
@@ -58562,11 +59212,19 @@ var OsdManager = class {
|
|
|
58562
59212
|
async collectFacts(cameraDeviceId, bindings) {
|
|
58563
59213
|
const wanted = new Set([cameraDeviceId]);
|
|
58564
59214
|
const needsRecognition = Object.values(bindings).some((binding) => binding.source.kind === "latest-recognition");
|
|
59215
|
+
const templateIds = /* @__PURE__ */ new Set();
|
|
58565
59216
|
for (const binding of Object.values(bindings)) {
|
|
58566
59217
|
if (binding.source.kind === "device-state" && binding.source.deviceId !== void 0) wanted.add(binding.source.deviceId);
|
|
59218
|
+
if (binding.source.kind === "template") templateIds.add(binding.source.templateId);
|
|
58567
59219
|
const gated = binding.conditions?.deviceState?.deviceId;
|
|
58568
59220
|
if (gated !== void 0) wanted.add(gated);
|
|
58569
59221
|
}
|
|
59222
|
+
const allTemplates = await this.deps.templates.get();
|
|
59223
|
+
const templatesById = /* @__PURE__ */ new Map();
|
|
59224
|
+
for (const named of allTemplates) {
|
|
59225
|
+
templatesById.set(named.id, named);
|
|
59226
|
+
if (templateIds.has(named.id)) for (const binding of named.bindings) wanted.add(binding.pick.deviceId);
|
|
59227
|
+
}
|
|
58570
59228
|
const stateByDevice = /* @__PURE__ */ new Map();
|
|
58571
59229
|
const gateState = /* @__PURE__ */ new Map();
|
|
58572
59230
|
for (const id of wanted) try {
|
|
@@ -58591,7 +59249,8 @@ var OsdManager = class {
|
|
|
58591
59249
|
atMs: this.now(),
|
|
58592
59250
|
stateByDevice,
|
|
58593
59251
|
recognitionByDevice,
|
|
58594
|
-
deviceState: (id) => gateState.get(id)
|
|
59252
|
+
deviceState: (id) => gateState.get(id),
|
|
59253
|
+
templatesById
|
|
58595
59254
|
};
|
|
58596
59255
|
}
|
|
58597
59256
|
async hydrateRecognitions(deviceId) {
|
|
@@ -58732,6 +59391,7 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58732
59391
|
const ctx = this.ctx;
|
|
58733
59392
|
const store = new OsdBindingsStore((deviceId, key, schema, fallback) => this.deviceState(deviceId, key, schema, fallback));
|
|
58734
59393
|
const boundIndex = this.state(OSD_BOUND_INDEX_KEY, OsdBoundIndexSchema, null);
|
|
59394
|
+
const templates = this.state(OSD_TEMPLATES_KEY, OsdNamedTemplatesSchema, EMPTY_TEMPLATES);
|
|
58735
59395
|
const manager = new OsdManager({
|
|
58736
59396
|
logger: ctx.logger,
|
|
58737
59397
|
readOsdStatus: async (deviceId) => {
|
|
@@ -58765,7 +59425,8 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58765
59425
|
listDevices: async () => {
|
|
58766
59426
|
return (await ctx.api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
|
|
58767
59427
|
id: d.id,
|
|
58768
|
-
name: d.name
|
|
59428
|
+
name: d.name,
|
|
59429
|
+
type: d.type ?? null
|
|
58769
59430
|
}));
|
|
58770
59431
|
},
|
|
58771
59432
|
listCameras: async () => {
|
|
@@ -58805,7 +59466,8 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58805
59466
|
};
|
|
58806
59467
|
},
|
|
58807
59468
|
bindings: (deviceId) => store.forDevice(deviceId),
|
|
58808
|
-
boundIndex
|
|
59469
|
+
boundIndex,
|
|
59470
|
+
templates
|
|
58809
59471
|
});
|
|
58810
59472
|
this.manager = manager;
|
|
58811
59473
|
this.subscribe({ category: EventCategory.PipelineAnalyticsTrackLifecycle }, (event) => {
|
|
@@ -58862,28 +59524,86 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58862
59524
|
}
|
|
58863
59525
|
};
|
|
58864
59526
|
//#endregion
|
|
59527
|
+
//#region src/osd-picker-devices.ts
|
|
59528
|
+
/**
|
|
59529
|
+
* Devices offered to the OSD DevicePicker — containers INCLUDED so the
|
|
59530
|
+
* operator can pick a gateway, then an OSD-supported child.
|
|
59531
|
+
*/
|
|
59532
|
+
function toOsdPickerDevices(devices) {
|
|
59533
|
+
return devices.map((d) => ({
|
|
59534
|
+
id: d.id,
|
|
59535
|
+
name: d.name,
|
|
59536
|
+
type: d.type ?? null,
|
|
59537
|
+
location: d.location ?? null,
|
|
59538
|
+
parentDeviceId: d.parentDeviceId ?? null
|
|
59539
|
+
}));
|
|
59540
|
+
}
|
|
59541
|
+
/** Children of a container that have at least one OSD catalog field. */
|
|
59542
|
+
function osdSupportedChildrenOf(containerId, devices, deviceIdsWithOsdFields) {
|
|
59543
|
+
return devices.filter((d) => d.parentDeviceId === containerId && isOsdLeafDeviceType(d.type) && deviceIdsWithOsdFields.has(d.id)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
59544
|
+
}
|
|
59545
|
+
function deviceById(devices, id) {
|
|
59546
|
+
return devices.find((d) => d.id === id);
|
|
59547
|
+
}
|
|
59548
|
+
//#endregion
|
|
59549
|
+
//#region src/template-id.ts
|
|
59550
|
+
/**
|
|
59551
|
+
* Named-template ids are system-generated — never an operator text field.
|
|
59552
|
+
* Create uses a uuid; the editable surface is the template name.
|
|
59553
|
+
*/
|
|
59554
|
+
function generateTemplateId() {
|
|
59555
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
59556
|
+
return `tpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
59557
|
+
}
|
|
59558
|
+
/** Optional slug helper when a stable short id from the name is preferred. */
|
|
59559
|
+
function templateIdFromName(name) {
|
|
59560
|
+
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
59561
|
+
return `${slug.length > 0 ? slug : "template"}-${Date.now().toString(36)}`;
|
|
59562
|
+
}
|
|
59563
|
+
//#endregion
|
|
58865
59564
|
exports.DEFAULT_TICK_MS = DEFAULT_TICK_MS;
|
|
58866
59565
|
exports.EMPTY_BINDINGS = EMPTY_BINDINGS;
|
|
58867
59566
|
exports.OSD_BINDINGS_KEY = OSD_BINDINGS_KEY;
|
|
59567
|
+
exports.OSD_NON_LEAF_TYPES = OSD_NON_LEAF_TYPES;
|
|
59568
|
+
exports.OSD_PICKER_EXCLUDED_TYPES = OSD_PICKER_EXCLUDED_TYPES;
|
|
59569
|
+
exports.OSD_SOURCE_CAP_NAMES = OSD_SOURCE_CAP_NAMES;
|
|
58868
59570
|
exports.OSD_SUPPORTED_CONDITIONS = OSD_SUPPORTED_CONDITIONS;
|
|
58869
59571
|
exports.OsdBindingsStore = OsdBindingsStore;
|
|
58870
59572
|
exports.OsdDeviceBindingsSchema = OsdDeviceBindingsSchema;
|
|
58871
59573
|
exports.OsdManager = OsdManager;
|
|
58872
59574
|
exports.OsdManagerAddon = OsdManagerAddon;
|
|
59575
|
+
exports.aliasToken = aliasToken;
|
|
58873
59576
|
exports.applyTemplate = applyTemplate;
|
|
59577
|
+
exports.applyTemplateFormat = applyTemplateFormat;
|
|
58874
59578
|
exports.assertSupportedConditions = assertSupportedConditions;
|
|
59579
|
+
exports.bindingToken = bindingToken;
|
|
58875
59580
|
exports.default = OsdManagerAddon;
|
|
59581
|
+
exports.deviceById = deviceById;
|
|
58876
59582
|
exports.displayValue = displayValue;
|
|
58877
59583
|
exports.evaluateGate = evaluateGate;
|
|
58878
59584
|
exports.formatClock = formatClock;
|
|
58879
59585
|
exports.formatSlotText = formatSlotText;
|
|
59586
|
+
exports.generateTemplateId = generateTemplateId;
|
|
58880
59587
|
exports.humanizeCapName = humanizeCapName;
|
|
59588
|
+
exports.isContainerDeviceType = isContainerDeviceType;
|
|
59589
|
+
exports.isOsdLeafDeviceType = isOsdLeafDeviceType;
|
|
59590
|
+
exports.isOsdPickerDeviceType = isOsdPickerDeviceType;
|
|
59591
|
+
exports.isOsdSourceCap = isOsdSourceCap;
|
|
58881
59592
|
exports.limitText = limitText;
|
|
59593
|
+
exports.osdStateLeavesOf = osdStateLeavesOf;
|
|
59594
|
+
exports.osdSupportedChildrenOf = osdSupportedChildrenOf;
|
|
59595
|
+
exports.parseAliasTokens = parseAliasTokens;
|
|
58882
59596
|
exports.readStatePath = readStatePath;
|
|
59597
|
+
exports.renderNamedTemplate = renderNamedTemplate;
|
|
58883
59598
|
exports.renderSlot = renderSlot;
|
|
59599
|
+
exports.replaceToken = replaceToken;
|
|
59600
|
+
exports.resolveBindingLive = resolveBindingLive;
|
|
58884
59601
|
exports.resolveSource = resolveSource;
|
|
58885
59602
|
exports.sourceOptionsFor = sourceOptionsFor;
|
|
58886
59603
|
exports.stateLeavesOf = stateLeavesOf;
|
|
59604
|
+
exports.suggestAlias = suggestAlias;
|
|
59605
|
+
exports.templateIdFromName = templateIdFromName;
|
|
59606
|
+
exports.toOsdPickerDevices = toOsdPickerDevices;
|
|
58887
59607
|
exports.truncateDecimals = truncateDecimals;
|
|
58888
59608
|
exports.unsupportedConditionKeys = unsupportedConditionKeys;
|
|
58889
59609
|
exports.withBinding = withBinding;
|