@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.mjs
CHANGED
|
@@ -15441,6 +15441,51 @@ var SourceInfoSchema = object({
|
|
|
15441
15441
|
raw: record(string(), unknown()).optional()
|
|
15442
15442
|
});
|
|
15443
15443
|
/**
|
|
15444
|
+
* Coerce common unit-of-measurement string variants to a canonical form
|
|
15445
|
+
* so the UI's display templates can rely on stable strings. Pure
|
|
15446
|
+
* passthrough for unknown units — kept conservative on purpose, the goal
|
|
15447
|
+
* is to normalise the long tail of HA `unit_of_measurement` values that
|
|
15448
|
+
* have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
|
|
15449
|
+
*
|
|
15450
|
+
* Extend the map as we encounter real-world data. Returning the raw
|
|
15451
|
+
* input for unknown values is intentional: providers should never
|
|
15452
|
+
* lose information silently here.
|
|
15453
|
+
*/
|
|
15454
|
+
function normalizeUnit(raw) {
|
|
15455
|
+
if (raw == null) return void 0;
|
|
15456
|
+
const trimmed = raw.trim();
|
|
15457
|
+
if (trimmed === "") return void 0;
|
|
15458
|
+
const lower = trimmed.toLowerCase();
|
|
15459
|
+
if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
|
|
15460
|
+
if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
|
|
15461
|
+
if (lower === "k" || lower === "kelvin") return "K";
|
|
15462
|
+
if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
|
|
15463
|
+
if (lower === "lx" || lower === "lux") return "lx";
|
|
15464
|
+
if (lower === "hpa" || lower === "hectopascal") return "hPa";
|
|
15465
|
+
if (lower === "kpa") return "kPa";
|
|
15466
|
+
if (lower === "pa") return "Pa";
|
|
15467
|
+
if (lower === "mbar" || lower === "millibar") return "mbar";
|
|
15468
|
+
if (lower === "w" || lower === "watt") return "W";
|
|
15469
|
+
if (lower === "kw" || lower === "kilowatt") return "kW";
|
|
15470
|
+
if (lower === "wh" || lower === "watt-hour") return "Wh";
|
|
15471
|
+
if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
|
|
15472
|
+
if (lower === "v" || lower === "volt") return "V";
|
|
15473
|
+
if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
|
|
15474
|
+
if (lower === "ma" || lower === "milliampere") return "mA";
|
|
15475
|
+
if (lower === "va") return "VA";
|
|
15476
|
+
if (lower === "ppm") return "ppm";
|
|
15477
|
+
if (lower === "ppb") return "ppb";
|
|
15478
|
+
if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
|
|
15479
|
+
if (lower === "km/h" || lower === "kph") return "km/h";
|
|
15480
|
+
if (lower === "mph") return "mph";
|
|
15481
|
+
if (lower === "m/s") return "m/s";
|
|
15482
|
+
if (lower === "m") return "m";
|
|
15483
|
+
if (lower === "km") return "km";
|
|
15484
|
+
if (lower === "kg") return "kg";
|
|
15485
|
+
if (lower === "g") return "g";
|
|
15486
|
+
return trimmed;
|
|
15487
|
+
}
|
|
15488
|
+
/**
|
|
15444
15489
|
* device-discovery — device-scoped capability for parents that host /
|
|
15445
15490
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
15446
15491
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -39292,28 +39337,50 @@ setOverlay: method(object({
|
|
|
39292
39337
|
*
|
|
39293
39338
|
* This cap owns the other half: a per-(camera, slot) BINDING that says
|
|
39294
39339
|
* which value feeds the slot, how it is formatted, and under which
|
|
39295
|
-
* conditions it is shown at all.
|
|
39296
|
-
*
|
|
39297
|
-
|
|
39298
|
-
|
|
39299
|
-
|
|
39300
|
-
|
|
39301
|
-
|
|
39302
|
-
|
|
39303
|
-
|
|
39304
|
-
|
|
39305
|
-
*
|
|
39306
|
-
*
|
|
39307
|
-
*
|
|
39308
|
-
|
|
39309
|
-
|
|
39310
|
-
|
|
39311
|
-
|
|
39312
|
-
|
|
39313
|
-
|
|
39314
|
-
*
|
|
39315
|
-
*
|
|
39340
|
+
* conditions it is shown at all. Named TEMPLATES combine many device
|
|
39341
|
+
* fields into one overlay string (Scrypted OSD Manager parity).
|
|
39342
|
+
*/
|
|
39343
|
+
/** What `{alias}` expands to for one template binding. Exactly one choice. */
|
|
39344
|
+
var OsdTemplateFormatSchema = _enum([
|
|
39345
|
+
"value+unit",
|
|
39346
|
+
"value",
|
|
39347
|
+
"unit"
|
|
39348
|
+
]);
|
|
39349
|
+
/**
|
|
39350
|
+
* How the operator reached the leaf device. `deviceId` is ALWAYS the
|
|
39351
|
+
* OSD-supported leaf that is read; `containerId` is set when the pick
|
|
39352
|
+
* went through a container (gateway) first.
|
|
39353
|
+
*/
|
|
39354
|
+
var OsdTemplatePickSchema = object({
|
|
39355
|
+
containerId: number().int().optional(),
|
|
39356
|
+
deviceId: number().int()
|
|
39357
|
+
});
|
|
39358
|
+
/**
|
|
39359
|
+
* One named binding inside a multi-device template. The parser only
|
|
39360
|
+
* sees `{alias}`; format / unit / decimals live here structurally.
|
|
39361
|
+
*/
|
|
39362
|
+
var OsdTemplateBindingSchema = object({
|
|
39363
|
+
/** Token in the parser, e.g. `t` → `{t}`. Unique within the template. */
|
|
39364
|
+
alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
|
|
39365
|
+
pick: OsdTemplatePickSchema,
|
|
39366
|
+
capName: string().min(1).max(64),
|
|
39367
|
+
valuePath: string().min(1).max(64),
|
|
39368
|
+
format: OsdTemplateFormatSchema.default("value+unit"),
|
|
39369
|
+
/** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
|
|
39370
|
+
displayUnit: string().max(16).optional(),
|
|
39371
|
+
maxDecimals: number().int().min(0).max(4).default(1)
|
|
39372
|
+
});
|
|
39373
|
+
/**
|
|
39374
|
+
* A named template: bindings (alias → leaf field) + a parser that only
|
|
39375
|
+
* references `{alias}` tokens.
|
|
39316
39376
|
*/
|
|
39377
|
+
var OsdNamedTemplateSchema = object({
|
|
39378
|
+
id: string().min(1).max(64),
|
|
39379
|
+
name: string().min(1).max(64),
|
|
39380
|
+
bindings: array(OsdTemplateBindingSchema).max(32).default([]),
|
|
39381
|
+
/** Parser with alias placeholders, e.g. `{t} / {h}`. */
|
|
39382
|
+
parser: string().max(256).default("")
|
|
39383
|
+
});
|
|
39317
39384
|
/** Where a slot's value comes from. */
|
|
39318
39385
|
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
39319
39386
|
object({
|
|
@@ -39332,11 +39399,17 @@ var OsdSourceSchema = discriminatedUnion("kind", [
|
|
|
39332
39399
|
deviceId: number().int().optional(),
|
|
39333
39400
|
capName: string().min(1).max(64),
|
|
39334
39401
|
/** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
|
|
39335
|
-
valuePath: string().min(1).max(64)
|
|
39402
|
+
valuePath: string().min(1).max(64),
|
|
39403
|
+
/** Convert TO this unit at render. Absent = no conversion. */
|
|
39404
|
+
displayUnit: string().max(16).optional()
|
|
39336
39405
|
}),
|
|
39337
39406
|
object({
|
|
39338
39407
|
kind: literal("latest-recognition"),
|
|
39339
39408
|
recognition: _enum(["person", "plate"])
|
|
39409
|
+
}),
|
|
39410
|
+
object({
|
|
39411
|
+
kind: literal("template"),
|
|
39412
|
+
templateId: string().min(1).max(64)
|
|
39340
39413
|
})
|
|
39341
39414
|
]);
|
|
39342
39415
|
var OsdSlotBindingSchema = object({
|
|
@@ -39352,7 +39425,10 @@ var OsdSlotBindingSchema = object({
|
|
|
39352
39425
|
* documented workaround for firmwares that reject `.` in overlay text.
|
|
39353
39426
|
*/
|
|
39354
39427
|
maxDecimals: number().int().min(0).max(4).default(1),
|
|
39355
|
-
/**
|
|
39428
|
+
/**
|
|
39429
|
+
* Appended via `${unit}` when no unit conversion produces a label.
|
|
39430
|
+
* Prefer `source.displayUnit` for converted device-state values.
|
|
39431
|
+
*/
|
|
39356
39432
|
unitLabel: string().max(8).optional(),
|
|
39357
39433
|
/** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
|
|
39358
39434
|
valueMap: record(string(), string()).optional(),
|
|
@@ -39506,6 +39582,21 @@ var osdManagerCapability = {
|
|
|
39506
39582
|
renderDevice: method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
|
|
39507
39583
|
kind: "mutation",
|
|
39508
39584
|
auth: "admin"
|
|
39585
|
+
}),
|
|
39586
|
+
/** Every named multi-device template. */
|
|
39587
|
+
listTemplates: method(object({}), object({ templates: array(OsdNamedTemplateSchema) }), { auth: "admin" }),
|
|
39588
|
+
/**
|
|
39589
|
+
* Create or replace a named template by `id`. Returns the stored record
|
|
39590
|
+
* after Zod normalisation.
|
|
39591
|
+
*/
|
|
39592
|
+
upsertTemplate: method(object({ template: OsdNamedTemplateSchema }), object({ template: OsdNamedTemplateSchema }), {
|
|
39593
|
+
kind: "mutation",
|
|
39594
|
+
auth: "admin"
|
|
39595
|
+
}),
|
|
39596
|
+
/** Drop a named template. Bindings that still reference it render unreadable. */
|
|
39597
|
+
deleteTemplate: method(object({ templateId: string().min(1).max(64) }), object({ success: literal(true) }), {
|
|
39598
|
+
kind: "mutation",
|
|
39599
|
+
auth: "admin"
|
|
39509
39600
|
})
|
|
39510
39601
|
}
|
|
39511
39602
|
};
|
|
@@ -45256,6 +45347,265 @@ var zoneRulesCapability = {
|
|
|
45256
45347
|
*/
|
|
45257
45348
|
durability: "restored"
|
|
45258
45349
|
};
|
|
45350
|
+
/**
|
|
45351
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
45352
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
45353
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
45354
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
45355
|
+
*/
|
|
45356
|
+
var UNIT_TABLE = {
|
|
45357
|
+
"°C": {
|
|
45358
|
+
dimension: "temperature",
|
|
45359
|
+
factor: 1,
|
|
45360
|
+
offset: 0
|
|
45361
|
+
},
|
|
45362
|
+
"°F": {
|
|
45363
|
+
dimension: "temperature",
|
|
45364
|
+
factor: 5 / 9,
|
|
45365
|
+
offset: -160 / 9
|
|
45366
|
+
},
|
|
45367
|
+
K: {
|
|
45368
|
+
dimension: "temperature",
|
|
45369
|
+
factor: 1,
|
|
45370
|
+
offset: -273.15
|
|
45371
|
+
},
|
|
45372
|
+
hPa: {
|
|
45373
|
+
dimension: "pressure",
|
|
45374
|
+
factor: 1,
|
|
45375
|
+
offset: 0
|
|
45376
|
+
},
|
|
45377
|
+
kPa: {
|
|
45378
|
+
dimension: "pressure",
|
|
45379
|
+
factor: 10,
|
|
45380
|
+
offset: 0
|
|
45381
|
+
},
|
|
45382
|
+
Pa: {
|
|
45383
|
+
dimension: "pressure",
|
|
45384
|
+
factor: .01,
|
|
45385
|
+
offset: 0
|
|
45386
|
+
},
|
|
45387
|
+
mbar: {
|
|
45388
|
+
dimension: "pressure",
|
|
45389
|
+
factor: 1,
|
|
45390
|
+
offset: 0
|
|
45391
|
+
},
|
|
45392
|
+
bar: {
|
|
45393
|
+
dimension: "pressure",
|
|
45394
|
+
factor: 1e3,
|
|
45395
|
+
offset: 0
|
|
45396
|
+
},
|
|
45397
|
+
inHg: {
|
|
45398
|
+
dimension: "pressure",
|
|
45399
|
+
factor: 33.8639,
|
|
45400
|
+
offset: 0
|
|
45401
|
+
},
|
|
45402
|
+
mmHg: {
|
|
45403
|
+
dimension: "pressure",
|
|
45404
|
+
factor: 1.33322,
|
|
45405
|
+
offset: 0
|
|
45406
|
+
},
|
|
45407
|
+
psi: {
|
|
45408
|
+
dimension: "pressure",
|
|
45409
|
+
factor: 68.9476,
|
|
45410
|
+
offset: 0
|
|
45411
|
+
},
|
|
45412
|
+
"m/s": {
|
|
45413
|
+
dimension: "speed",
|
|
45414
|
+
factor: 1,
|
|
45415
|
+
offset: 0
|
|
45416
|
+
},
|
|
45417
|
+
"km/h": {
|
|
45418
|
+
dimension: "speed",
|
|
45419
|
+
factor: 1 / 3.6,
|
|
45420
|
+
offset: 0
|
|
45421
|
+
},
|
|
45422
|
+
mph: {
|
|
45423
|
+
dimension: "speed",
|
|
45424
|
+
factor: .44704,
|
|
45425
|
+
offset: 0
|
|
45426
|
+
},
|
|
45427
|
+
kn: {
|
|
45428
|
+
dimension: "speed",
|
|
45429
|
+
factor: .514444,
|
|
45430
|
+
offset: 0
|
|
45431
|
+
},
|
|
45432
|
+
"mm/h": {
|
|
45433
|
+
dimension: "precipitation-rate",
|
|
45434
|
+
factor: 1,
|
|
45435
|
+
offset: 0
|
|
45436
|
+
},
|
|
45437
|
+
"in/h": {
|
|
45438
|
+
dimension: "precipitation-rate",
|
|
45439
|
+
factor: 25.4,
|
|
45440
|
+
offset: 0
|
|
45441
|
+
},
|
|
45442
|
+
m: {
|
|
45443
|
+
dimension: "length",
|
|
45444
|
+
factor: 1,
|
|
45445
|
+
offset: 0
|
|
45446
|
+
},
|
|
45447
|
+
mm: {
|
|
45448
|
+
dimension: "length",
|
|
45449
|
+
factor: .001,
|
|
45450
|
+
offset: 0
|
|
45451
|
+
},
|
|
45452
|
+
cm: {
|
|
45453
|
+
dimension: "length",
|
|
45454
|
+
factor: .01,
|
|
45455
|
+
offset: 0
|
|
45456
|
+
},
|
|
45457
|
+
km: {
|
|
45458
|
+
dimension: "length",
|
|
45459
|
+
factor: 1e3,
|
|
45460
|
+
offset: 0
|
|
45461
|
+
},
|
|
45462
|
+
in: {
|
|
45463
|
+
dimension: "length",
|
|
45464
|
+
factor: .0254,
|
|
45465
|
+
offset: 0
|
|
45466
|
+
},
|
|
45467
|
+
ft: {
|
|
45468
|
+
dimension: "length",
|
|
45469
|
+
factor: .3048,
|
|
45470
|
+
offset: 0
|
|
45471
|
+
},
|
|
45472
|
+
mi: {
|
|
45473
|
+
dimension: "length",
|
|
45474
|
+
factor: 1609.344,
|
|
45475
|
+
offset: 0
|
|
45476
|
+
},
|
|
45477
|
+
lx: {
|
|
45478
|
+
dimension: "illuminance",
|
|
45479
|
+
factor: 1,
|
|
45480
|
+
offset: 0
|
|
45481
|
+
},
|
|
45482
|
+
"W/m²": {
|
|
45483
|
+
dimension: "irradiance",
|
|
45484
|
+
factor: 1,
|
|
45485
|
+
offset: 0
|
|
45486
|
+
},
|
|
45487
|
+
W: {
|
|
45488
|
+
dimension: "power",
|
|
45489
|
+
factor: 1,
|
|
45490
|
+
offset: 0
|
|
45491
|
+
},
|
|
45492
|
+
kW: {
|
|
45493
|
+
dimension: "power",
|
|
45494
|
+
factor: 1e3,
|
|
45495
|
+
offset: 0
|
|
45496
|
+
},
|
|
45497
|
+
VA: {
|
|
45498
|
+
dimension: "apparent-power",
|
|
45499
|
+
factor: 1,
|
|
45500
|
+
offset: 0
|
|
45501
|
+
},
|
|
45502
|
+
Wh: {
|
|
45503
|
+
dimension: "energy",
|
|
45504
|
+
factor: 1,
|
|
45505
|
+
offset: 0
|
|
45506
|
+
},
|
|
45507
|
+
kWh: {
|
|
45508
|
+
dimension: "energy",
|
|
45509
|
+
factor: 1e3,
|
|
45510
|
+
offset: 0
|
|
45511
|
+
},
|
|
45512
|
+
MWh: {
|
|
45513
|
+
dimension: "energy",
|
|
45514
|
+
factor: 1e6,
|
|
45515
|
+
offset: 0
|
|
45516
|
+
},
|
|
45517
|
+
V: {
|
|
45518
|
+
dimension: "voltage",
|
|
45519
|
+
factor: 1,
|
|
45520
|
+
offset: 0
|
|
45521
|
+
},
|
|
45522
|
+
mV: {
|
|
45523
|
+
dimension: "voltage",
|
|
45524
|
+
factor: .001,
|
|
45525
|
+
offset: 0
|
|
45526
|
+
},
|
|
45527
|
+
A: {
|
|
45528
|
+
dimension: "current",
|
|
45529
|
+
factor: 1,
|
|
45530
|
+
offset: 0
|
|
45531
|
+
},
|
|
45532
|
+
mA: {
|
|
45533
|
+
dimension: "current",
|
|
45534
|
+
factor: .001,
|
|
45535
|
+
offset: 0
|
|
45536
|
+
},
|
|
45537
|
+
ppm: {
|
|
45538
|
+
dimension: "concentration-volume",
|
|
45539
|
+
factor: 1,
|
|
45540
|
+
offset: 0
|
|
45541
|
+
},
|
|
45542
|
+
ppb: {
|
|
45543
|
+
dimension: "concentration-volume",
|
|
45544
|
+
factor: .001,
|
|
45545
|
+
offset: 0
|
|
45546
|
+
},
|
|
45547
|
+
"µg/m³": {
|
|
45548
|
+
dimension: "concentration-mass",
|
|
45549
|
+
factor: 1,
|
|
45550
|
+
offset: 0
|
|
45551
|
+
},
|
|
45552
|
+
"mg/m³": {
|
|
45553
|
+
dimension: "concentration-mass",
|
|
45554
|
+
factor: 1e3,
|
|
45555
|
+
offset: 0
|
|
45556
|
+
},
|
|
45557
|
+
kg: {
|
|
45558
|
+
dimension: "mass",
|
|
45559
|
+
factor: 1,
|
|
45560
|
+
offset: 0
|
|
45561
|
+
},
|
|
45562
|
+
g: {
|
|
45563
|
+
dimension: "mass",
|
|
45564
|
+
factor: .001,
|
|
45565
|
+
offset: 0
|
|
45566
|
+
},
|
|
45567
|
+
lb: {
|
|
45568
|
+
dimension: "mass",
|
|
45569
|
+
factor: .453592,
|
|
45570
|
+
offset: 0
|
|
45571
|
+
},
|
|
45572
|
+
oz: {
|
|
45573
|
+
dimension: "mass",
|
|
45574
|
+
factor: .0283495,
|
|
45575
|
+
offset: 0
|
|
45576
|
+
},
|
|
45577
|
+
"%": {
|
|
45578
|
+
dimension: "percentage",
|
|
45579
|
+
factor: 1,
|
|
45580
|
+
offset: 0
|
|
45581
|
+
}
|
|
45582
|
+
};
|
|
45583
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
45584
|
+
function specFor(unit) {
|
|
45585
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
45586
|
+
}
|
|
45587
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
45588
|
+
function toCanonical(value, spec) {
|
|
45589
|
+
return value * spec.factor + spec.offset;
|
|
45590
|
+
}
|
|
45591
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
45592
|
+
function fromCanonical(canonical, spec) {
|
|
45593
|
+
return (canonical - spec.offset) / spec.factor;
|
|
45594
|
+
}
|
|
45595
|
+
/**
|
|
45596
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
45597
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
45598
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
45599
|
+
*/
|
|
45600
|
+
function tryConvertUnit(value, from, to) {
|
|
45601
|
+
if (from === to) return value;
|
|
45602
|
+
const fromSpec = specFor(from);
|
|
45603
|
+
if (!fromSpec) return void 0;
|
|
45604
|
+
const toSpec = specFor(to);
|
|
45605
|
+
if (!toSpec) return void 0;
|
|
45606
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
45607
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
45608
|
+
}
|
|
45259
45609
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
45260
45610
|
/**
|
|
45261
45611
|
* Most specific first. Extending this list is how a new device kind becomes
|
|
@@ -50624,6 +50974,12 @@ Object.freeze({
|
|
|
50624
50974
|
addonId: null,
|
|
50625
50975
|
access: "create"
|
|
50626
50976
|
},
|
|
50977
|
+
"osdManager.deleteTemplate": {
|
|
50978
|
+
capName: "osd-manager",
|
|
50979
|
+
capScope: "system",
|
|
50980
|
+
addonId: null,
|
|
50981
|
+
access: "delete"
|
|
50982
|
+
},
|
|
50627
50983
|
"osdManager.getConditionSupport": {
|
|
50628
50984
|
capName: "osd-manager",
|
|
50629
50985
|
capScope: "system",
|
|
@@ -50642,6 +50998,12 @@ Object.freeze({
|
|
|
50642
50998
|
addonId: null,
|
|
50643
50999
|
access: "view"
|
|
50644
51000
|
},
|
|
51001
|
+
"osdManager.listTemplates": {
|
|
51002
|
+
capName: "osd-manager",
|
|
51003
|
+
capScope: "system",
|
|
51004
|
+
addonId: null,
|
|
51005
|
+
access: "view"
|
|
51006
|
+
},
|
|
50645
51007
|
"osdManager.previewSlot": {
|
|
50646
51008
|
capName: "osd-manager",
|
|
50647
51009
|
capScope: "system",
|
|
@@ -50660,6 +51022,12 @@ Object.freeze({
|
|
|
50660
51022
|
addonId: null,
|
|
50661
51023
|
access: "create"
|
|
50662
51024
|
},
|
|
51025
|
+
"osdManager.upsertTemplate": {
|
|
51026
|
+
capName: "osd-manager",
|
|
51027
|
+
capScope: "system",
|
|
51028
|
+
addonId: null,
|
|
51029
|
+
access: "create"
|
|
51030
|
+
},
|
|
50663
51031
|
"petFeeder.callPet": {
|
|
50664
51032
|
capName: "pet-feeder",
|
|
50665
51033
|
capScope: "device",
|
|
@@ -57908,10 +58276,10 @@ function limitText(text, max) {
|
|
|
57908
58276
|
return `${text.slice(0, max - 3)}...`;
|
|
57909
58277
|
}
|
|
57910
58278
|
/** Turn a raw state value into the string that goes into `${value}`. */
|
|
57911
|
-
function displayValue(raw, binding) {
|
|
58279
|
+
function displayValue(raw, binding, maxDecimals = binding.maxDecimals) {
|
|
57912
58280
|
const mapped = binding.valueMap?.[String(raw)];
|
|
57913
58281
|
if (mapped !== void 0) return mapped;
|
|
57914
|
-
if (typeof raw === "number") return truncateDecimals(raw,
|
|
58282
|
+
if (typeof raw === "number") return truncateDecimals(raw, maxDecimals);
|
|
57915
58283
|
if (typeof raw === "boolean") return raw ? "ON" : "OFF";
|
|
57916
58284
|
if (raw === null || raw === void 0) return "";
|
|
57917
58285
|
if (typeof raw === "string") return raw;
|
|
@@ -57925,9 +58293,10 @@ function applyTemplate(template, value, unit) {
|
|
|
57925
58293
|
return template.replaceAll("${value}", value).replaceAll("${unit}", unit);
|
|
57926
58294
|
}
|
|
57927
58295
|
/** The whole formatting chain: raw value → the text to burn into the slot. */
|
|
57928
|
-
function formatSlotText(raw, binding) {
|
|
57929
|
-
const value = displayValue(raw, binding);
|
|
57930
|
-
|
|
58296
|
+
function formatSlotText(raw, binding, options = {}) {
|
|
58297
|
+
const value = displayValue(options.displayRaw ?? raw, binding);
|
|
58298
|
+
const unit = options.unit ?? binding.unitLabel ?? "";
|
|
58299
|
+
return limitText(applyTemplate(binding.template, value, unit).trim(), binding.maxCharacters);
|
|
57931
58300
|
}
|
|
57932
58301
|
//#endregion
|
|
57933
58302
|
//#region src/state-leaves.ts
|
|
@@ -58015,6 +58384,144 @@ function readStatePath(slice, path) {
|
|
|
58015
58384
|
return current;
|
|
58016
58385
|
}
|
|
58017
58386
|
//#endregion
|
|
58387
|
+
//#region src/template-tokens.ts
|
|
58388
|
+
/** Build the placeholder token for one binding alias. */
|
|
58389
|
+
function aliasToken(alias) {
|
|
58390
|
+
return `{${alias}}`;
|
|
58391
|
+
}
|
|
58392
|
+
/** Chip / parser token for a binding. */
|
|
58393
|
+
function bindingToken(binding) {
|
|
58394
|
+
return aliasToken(binding.alias);
|
|
58395
|
+
}
|
|
58396
|
+
/** Parse `{alias}` tokens from a parser string (letters, digits, `_`, `-`). */
|
|
58397
|
+
function parseAliasTokens(parser) {
|
|
58398
|
+
const out = [];
|
|
58399
|
+
for (const match of parser.matchAll(/\{([A-Za-z][A-Za-z0-9_-]*)\}/g)) {
|
|
58400
|
+
const alias = match[1];
|
|
58401
|
+
if (alias === void 0 || alias === "") continue;
|
|
58402
|
+
if (!out.includes(alias)) out.push(alias);
|
|
58403
|
+
}
|
|
58404
|
+
return out;
|
|
58405
|
+
}
|
|
58406
|
+
/** Replace every occurrence of `token` in `parser` with `value`. */
|
|
58407
|
+
function replaceToken(parser, token, value) {
|
|
58408
|
+
return parser.replaceAll(token, value);
|
|
58409
|
+
}
|
|
58410
|
+
/**
|
|
58411
|
+
* Suggest a short unique alias from a device/field name.
|
|
58412
|
+
* Falls back to `b1`, `b2`, … when the slug collides.
|
|
58413
|
+
*/
|
|
58414
|
+
function suggestAlias(seed, taken, fallbackPrefix = "b") {
|
|
58415
|
+
const slug = seed.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^(\d)/, "a$1").slice(0, 24);
|
|
58416
|
+
const base = slug.length > 0 ? slug : fallbackPrefix;
|
|
58417
|
+
if (!taken.has(base)) return base;
|
|
58418
|
+
for (let i = 2; i < 1e3; i += 1) {
|
|
58419
|
+
const candidate = `${base}${i}`.slice(0, 32);
|
|
58420
|
+
if (!taken.has(candidate)) return candidate;
|
|
58421
|
+
}
|
|
58422
|
+
return `${fallbackPrefix}${Date.now().toString(36)}`;
|
|
58423
|
+
}
|
|
58424
|
+
//#endregion
|
|
58425
|
+
//#region src/unit-display.ts
|
|
58426
|
+
/**
|
|
58427
|
+
* Convert a numeric state value for OSD display using `@camstack/types`
|
|
58428
|
+
* UNIT_TABLE / tryConvertUnit (same set as D699).
|
|
58429
|
+
*/
|
|
58430
|
+
/**
|
|
58431
|
+
* Convert `raw` from `sourceUnit` to `displayUnit` when both are known and
|
|
58432
|
+
* same-dimension. On refusal, keep the raw value and the source unit so the
|
|
58433
|
+
* label never lies about the number.
|
|
58434
|
+
*/
|
|
58435
|
+
function convertForDisplay(raw, sourceUnit, displayUnit) {
|
|
58436
|
+
const from = sourceUnit !== void 0 ? normalizeUnit(sourceUnit) : void 0;
|
|
58437
|
+
const to = displayUnit !== void 0 ? normalizeUnit(displayUnit) : void 0;
|
|
58438
|
+
if (from === void 0 || to === void 0 || from === to) return {
|
|
58439
|
+
value: raw,
|
|
58440
|
+
unit: to ?? from ?? ""
|
|
58441
|
+
};
|
|
58442
|
+
const converted = tryConvertUnit(raw, from, to);
|
|
58443
|
+
if (converted === void 0) return {
|
|
58444
|
+
value: raw,
|
|
58445
|
+
unit: from
|
|
58446
|
+
};
|
|
58447
|
+
return {
|
|
58448
|
+
value: converted,
|
|
58449
|
+
unit: to
|
|
58450
|
+
};
|
|
58451
|
+
}
|
|
58452
|
+
/**
|
|
58453
|
+
* Best-effort unit from a state slice: sibling `unit` at the same depth as
|
|
58454
|
+
* `valuePath`, else top-level `unit`.
|
|
58455
|
+
*/
|
|
58456
|
+
function inferSourceUnit(slice, valuePath) {
|
|
58457
|
+
if (typeof slice !== "object" || slice === null || Array.isArray(slice)) return void 0;
|
|
58458
|
+
const root = { ...slice };
|
|
58459
|
+
const segments = valuePath.split(".");
|
|
58460
|
+
if (segments.length >= 1) {
|
|
58461
|
+
let parent = root;
|
|
58462
|
+
for (const segment of segments.slice(0, -1)) {
|
|
58463
|
+
if (typeof parent !== "object" || parent === null || Array.isArray(parent)) {
|
|
58464
|
+
parent = void 0;
|
|
58465
|
+
break;
|
|
58466
|
+
}
|
|
58467
|
+
parent = { ...parent }[segment];
|
|
58468
|
+
}
|
|
58469
|
+
if (typeof parent === "object" && parent !== null && !Array.isArray(parent)) {
|
|
58470
|
+
const unit = { ...parent }.unit;
|
|
58471
|
+
if (typeof unit === "string" && unit.length > 0) return unit;
|
|
58472
|
+
}
|
|
58473
|
+
}
|
|
58474
|
+
const top = root.unit;
|
|
58475
|
+
return typeof top === "string" && top.length > 0 ? top : void 0;
|
|
58476
|
+
}
|
|
58477
|
+
//#endregion
|
|
58478
|
+
//#region src/named-template-render.ts
|
|
58479
|
+
/** Combine value/unit according to the binding's single format choice. */
|
|
58480
|
+
function applyTemplateFormat(value, unit, format) {
|
|
58481
|
+
if (format === "value") return value;
|
|
58482
|
+
if (format === "unit") return unit;
|
|
58483
|
+
const v = value.trim();
|
|
58484
|
+
const u = unit.trim();
|
|
58485
|
+
if (v === "") return u;
|
|
58486
|
+
if (u === "") return v;
|
|
58487
|
+
return `${v} ${u}`;
|
|
58488
|
+
}
|
|
58489
|
+
/** Resolve one binding to live value/unit/formatted parts (or a reason). */
|
|
58490
|
+
function resolveBindingLive(binding, facts) {
|
|
58491
|
+
const deviceId = binding.pick.deviceId;
|
|
58492
|
+
const deviceState = facts.stateByDevice.get(deviceId);
|
|
58493
|
+
if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
|
|
58494
|
+
const slice = deviceState[binding.capName];
|
|
58495
|
+
if (slice === void 0) return { reason: `device ${deviceId} publishes no "${binding.capName}" state` };
|
|
58496
|
+
const raw = readStatePath(slice, binding.valuePath);
|
|
58497
|
+
if (raw === void 0) return { reason: `"${binding.capName}.${binding.valuePath}" is absent on device ${deviceId}` };
|
|
58498
|
+
let value;
|
|
58499
|
+
let unit = "";
|
|
58500
|
+
if (typeof raw === "number") {
|
|
58501
|
+
const converted = convertForDisplay(raw, inferSourceUnit(slice, binding.valuePath), binding.displayUnit);
|
|
58502
|
+
value = truncateDecimals(converted.value, binding.maxDecimals);
|
|
58503
|
+
unit = converted.unit;
|
|
58504
|
+
} else if (typeof raw === "boolean") value = raw ? "ON" : "OFF";
|
|
58505
|
+
else if (raw === null) value = "";
|
|
58506
|
+
else value = String(raw);
|
|
58507
|
+
return {
|
|
58508
|
+
value,
|
|
58509
|
+
unit,
|
|
58510
|
+
formatted: applyTemplateFormat(value, unit, binding.format)
|
|
58511
|
+
};
|
|
58512
|
+
}
|
|
58513
|
+
/** Render one named template's parser from live states. */
|
|
58514
|
+
function renderNamedTemplate(template, facts) {
|
|
58515
|
+
let text = template.parser;
|
|
58516
|
+
if (template.bindings.length === 0) return text.trim() === "" ? { reason: `template "${template.id}" has no bindings` } : { text };
|
|
58517
|
+
for (const binding of template.bindings) {
|
|
58518
|
+
const live = resolveBindingLive(binding, facts);
|
|
58519
|
+
if ("reason" in live) return live;
|
|
58520
|
+
text = replaceToken(text, aliasToken(binding.alias), live.formatted);
|
|
58521
|
+
}
|
|
58522
|
+
return { text };
|
|
58523
|
+
}
|
|
58524
|
+
//#endregion
|
|
58018
58525
|
//#region src/render.ts
|
|
58019
58526
|
/**
|
|
58020
58527
|
* Read the source's value. `null` means "unreadable" — a missing device, a
|
|
@@ -58029,6 +58536,16 @@ function resolveSource(binding, cameraDeviceId, facts) {
|
|
|
58029
58536
|
const value = facts.recognitionByDevice.get(cameraDeviceId)?.[source.recognition];
|
|
58030
58537
|
return value === void 0 ? { reason: `no recognized ${source.recognition} has been persisted for device ${cameraDeviceId}` } : { value: { raw: value } };
|
|
58031
58538
|
}
|
|
58539
|
+
if (source.kind === "template") {
|
|
58540
|
+
const named = facts.templatesById.get(source.templateId);
|
|
58541
|
+
if (named === void 0) return { reason: `named template "${source.templateId}" is missing` };
|
|
58542
|
+
const rendered = renderNamedTemplate(named, { stateByDevice: facts.stateByDevice });
|
|
58543
|
+
if ("reason" in rendered) return rendered;
|
|
58544
|
+
return { value: {
|
|
58545
|
+
raw: rendered.text,
|
|
58546
|
+
preformatted: rendered.text
|
|
58547
|
+
} };
|
|
58548
|
+
}
|
|
58032
58549
|
const deviceId = source.deviceId ?? cameraDeviceId;
|
|
58033
58550
|
const deviceState = facts.stateByDevice.get(deviceId);
|
|
58034
58551
|
if (deviceState === void 0) return { reason: `no state mirrored for device ${deviceId}` };
|
|
@@ -58036,6 +58553,14 @@ function resolveSource(binding, cameraDeviceId, facts) {
|
|
|
58036
58553
|
if (slice === void 0) return { reason: `device ${deviceId} publishes no "${source.capName}" state` };
|
|
58037
58554
|
const raw = readStatePath(slice, source.valuePath);
|
|
58038
58555
|
if (raw === void 0) return { reason: `"${source.capName}.${source.valuePath}" is absent on device ${deviceId}` };
|
|
58556
|
+
if (typeof raw === "number") {
|
|
58557
|
+
const converted = convertForDisplay(raw, inferSourceUnit(slice, source.valuePath), source.displayUnit);
|
|
58558
|
+
return { value: {
|
|
58559
|
+
raw,
|
|
58560
|
+
displayRaw: converted.value,
|
|
58561
|
+
unit: converted.unit || binding.unitLabel
|
|
58562
|
+
} };
|
|
58563
|
+
}
|
|
58039
58564
|
return { value: { raw } };
|
|
58040
58565
|
}
|
|
58041
58566
|
/**
|
|
@@ -58068,7 +58593,12 @@ function renderSlot(slotId, binding, cameraDeviceId, facts, previousText) {
|
|
|
58068
58593
|
if (!gate.open) return settle(slotId, fallback, previousText, "gated", `condition "${gate.failedCondition}"`);
|
|
58069
58594
|
const resolved = resolveSource(binding, cameraDeviceId, facts);
|
|
58070
58595
|
if ("reason" in resolved) return settle(slotId, fallback, previousText, "unreadable", resolved.reason);
|
|
58071
|
-
return settle(slotId,
|
|
58596
|
+
if (resolved.value.preformatted !== void 0 && binding.source.kind === "template") return settle(slotId, limitText(resolved.value.preformatted.trim(), binding.maxCharacters), previousText, "written");
|
|
58597
|
+
const unit = resolved.value.unit ?? binding.unitLabel;
|
|
58598
|
+
return settle(slotId, formatSlotText(resolved.value.raw, binding, {
|
|
58599
|
+
...resolved.value.displayRaw !== void 0 ? { displayRaw: resolved.value.displayRaw } : {},
|
|
58600
|
+
...unit !== void 0 ? { unit } : {}
|
|
58601
|
+
}), previousText, "written");
|
|
58072
58602
|
}
|
|
58073
58603
|
function settle(slotId, text, previousText, outcome, reason) {
|
|
58074
58604
|
if (previousText !== void 0 && previousText === text) return {
|
|
@@ -58089,20 +58619,98 @@ function settle(slotId, text, previousText, outcome, reason) {
|
|
|
58089
58619
|
};
|
|
58090
58620
|
}
|
|
58091
58621
|
//#endregion
|
|
58622
|
+
//#region src/osd-source-fields.ts
|
|
58623
|
+
/** Caps the OSD source picker may bind. */
|
|
58624
|
+
var OSD_SOURCE_CAP_NAMES = new Set([
|
|
58625
|
+
"temperature-sensor",
|
|
58626
|
+
"humidity-sensor",
|
|
58627
|
+
"climate-control",
|
|
58628
|
+
"numeric-sensor",
|
|
58629
|
+
"weather",
|
|
58630
|
+
"air-quality-sensor",
|
|
58631
|
+
"lock-control",
|
|
58632
|
+
"contact",
|
|
58633
|
+
"binary",
|
|
58634
|
+
"battery"
|
|
58635
|
+
]);
|
|
58636
|
+
/**
|
|
58637
|
+
* Per-cap leaf allowlist. Absent entry ⇒ every leaf from `stateLeavesOf`
|
|
58638
|
+
* that survives bookkeeping (single-metric sensors). Present ⇒ only these
|
|
58639
|
+
* paths (climate / weather / battery).
|
|
58640
|
+
*/
|
|
58641
|
+
var OSD_SOURCE_PATHS = new Map([
|
|
58642
|
+
["climate-control", new Set([
|
|
58643
|
+
"currentTemp",
|
|
58644
|
+
"currentHumidity",
|
|
58645
|
+
"target",
|
|
58646
|
+
"targetHumidity",
|
|
58647
|
+
"mode"
|
|
58648
|
+
])],
|
|
58649
|
+
["weather", new Set([
|
|
58650
|
+
"condition",
|
|
58651
|
+
"temperature",
|
|
58652
|
+
"humidity",
|
|
58653
|
+
"pressure",
|
|
58654
|
+
"windSpeed",
|
|
58655
|
+
"windBearing"
|
|
58656
|
+
])],
|
|
58657
|
+
["battery", new Set(["percentage"])],
|
|
58658
|
+
["temperature-sensor", new Set(["celsius"])],
|
|
58659
|
+
["humidity-sensor", new Set(["percent"])],
|
|
58660
|
+
["numeric-sensor", new Set(["value"])],
|
|
58661
|
+
["air-quality-sensor", new Set([
|
|
58662
|
+
"co2Ppm",
|
|
58663
|
+
"vocPpb",
|
|
58664
|
+
"pm25",
|
|
58665
|
+
"pm10",
|
|
58666
|
+
"aqi"
|
|
58667
|
+
])],
|
|
58668
|
+
["binary", new Set(["on"])],
|
|
58669
|
+
["contact", new Set(["entryOpen"])],
|
|
58670
|
+
["lock-control", new Set(["state"])]
|
|
58671
|
+
]);
|
|
58672
|
+
function isOsdSourceCap(capName) {
|
|
58673
|
+
return OSD_SOURCE_CAP_NAMES.has(capName);
|
|
58674
|
+
}
|
|
58675
|
+
/** Bindable OSD leaves for one cap — empty when the cap is not OSD-relevant. */
|
|
58676
|
+
function osdStateLeavesOf(cap) {
|
|
58677
|
+
if (!isOsdSourceCap(cap.name)) return [];
|
|
58678
|
+
const leaves = stateLeavesOf(cap);
|
|
58679
|
+
const allowed = OSD_SOURCE_PATHS.get(cap.name);
|
|
58680
|
+
if (allowed === void 0) return leaves;
|
|
58681
|
+
return leaves.filter((leaf) => allowed.has(leaf.path));
|
|
58682
|
+
}
|
|
58683
|
+
/** Device types that never contribute their OWN catalog leaves. */
|
|
58684
|
+
var OSD_NON_LEAF_TYPES = new Set(["container"]);
|
|
58685
|
+
/** True when this device type may expose OSD leaves (not a container gateway). */
|
|
58686
|
+
function isOsdLeafDeviceType(type) {
|
|
58687
|
+
if (type === null || type === void 0) return true;
|
|
58688
|
+
return !OSD_NON_LEAF_TYPES.has(type);
|
|
58689
|
+
}
|
|
58690
|
+
/** @deprecated Prefer {@link isOsdLeafDeviceType}. */
|
|
58691
|
+
var OSD_PICKER_EXCLUDED_TYPES = OSD_NON_LEAF_TYPES;
|
|
58692
|
+
/** @deprecated Prefer {@link isOsdLeafDeviceType}. */
|
|
58693
|
+
function isOsdPickerDeviceType(type) {
|
|
58694
|
+
return isOsdLeafDeviceType(type);
|
|
58695
|
+
}
|
|
58696
|
+
function isContainerDeviceType(type) {
|
|
58697
|
+
return type === "container";
|
|
58698
|
+
}
|
|
58699
|
+
//#endregion
|
|
58092
58700
|
//#region src/source-catalog.ts
|
|
58093
58701
|
/**
|
|
58094
|
-
* Build the editor's source picker from
|
|
58095
|
-
*
|
|
58096
|
-
*
|
|
58702
|
+
* Build the editor's source picker from OSD-relevant cap definitions.
|
|
58703
|
+
* Gateway / infrastructure caps (broker, mqtt, deviceExport, …) never
|
|
58704
|
+
* appear — see `osd-source-fields.ts`.
|
|
58097
58705
|
*/
|
|
58098
58706
|
/** Turn `air-quality-sensor` + `pm25` into `Air Quality Sensor · pm25`. */
|
|
58099
58707
|
function humanizeCapName(capName) {
|
|
58100
58708
|
return capName.split("-").map((part) => part.length === 0 ? part : part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
58101
58709
|
}
|
|
58102
58710
|
/**
|
|
58103
|
-
* Every bindable value across the cluster, with the camera's own
|
|
58104
|
-
* first — that is what an operator reaches for, and a picker that
|
|
58105
|
-
* it under 200 other devices is a picker nobody uses.
|
|
58711
|
+
* Every OSD-bindable value across the cluster, with the camera's own
|
|
58712
|
+
* state first — that is what an operator reaches for, and a picker that
|
|
58713
|
+
* buries it under 200 other devices is a picker nobody uses.
|
|
58106
58714
|
*/
|
|
58107
58715
|
function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
|
|
58108
58716
|
const capsByName = new Map(ALL_CAPABILITY_DEFINITIONS.map((cap) => [cap.name, cap]));
|
|
@@ -58112,22 +58720,44 @@ function sourceOptionsFor(cameraDeviceId, devices, capsByDevice) {
|
|
|
58112
58720
|
return a.id - b.id;
|
|
58113
58721
|
});
|
|
58114
58722
|
const options = [];
|
|
58115
|
-
for (const device of ordered)
|
|
58116
|
-
|
|
58117
|
-
|
|
58118
|
-
|
|
58119
|
-
|
|
58120
|
-
|
|
58121
|
-
|
|
58122
|
-
|
|
58123
|
-
|
|
58124
|
-
|
|
58125
|
-
|
|
58126
|
-
|
|
58723
|
+
for (const device of ordered) {
|
|
58724
|
+
if (!isOsdLeafDeviceType(device.type ?? null)) continue;
|
|
58725
|
+
for (const capName of capsByDevice.get(device.id) ?? []) {
|
|
58726
|
+
if (!isOsdSourceCap(capName)) continue;
|
|
58727
|
+
const cap = capsByName.get(capName);
|
|
58728
|
+
if (cap === void 0) continue;
|
|
58729
|
+
for (const leaf of osdStateLeavesOf(cap)) options.push({
|
|
58730
|
+
deviceId: device.id,
|
|
58731
|
+
deviceName: device.name,
|
|
58732
|
+
capName,
|
|
58733
|
+
valuePath: leaf.path,
|
|
58734
|
+
label: `${humanizeCapName(capName)} · ${leaf.path}`,
|
|
58735
|
+
valueType: leaf.valueType,
|
|
58736
|
+
...leaf.enumValues !== void 0 ? { enumValues: leaf.enumValues } : {}
|
|
58737
|
+
});
|
|
58738
|
+
}
|
|
58127
58739
|
}
|
|
58128
58740
|
return options;
|
|
58129
58741
|
}
|
|
58130
58742
|
//#endregion
|
|
58743
|
+
//#region src/templates-store.ts
|
|
58744
|
+
/**
|
|
58745
|
+
* Named OSD templates — durable in the addon's own (global) store.
|
|
58746
|
+
*/
|
|
58747
|
+
var OSD_TEMPLATES_KEY = "namedTemplates";
|
|
58748
|
+
var OsdNamedTemplatesSchema = array(OsdNamedTemplateSchema);
|
|
58749
|
+
var EMPTY_TEMPLATES = [];
|
|
58750
|
+
function upsertNamedTemplate(prev, template) {
|
|
58751
|
+
const parsed = OsdNamedTemplateSchema.parse(template);
|
|
58752
|
+
return [...prev.filter((t) => t.id !== parsed.id), parsed].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
58753
|
+
}
|
|
58754
|
+
function deleteNamedTemplate(prev, templateId) {
|
|
58755
|
+
return prev.filter((t) => t.id !== templateId);
|
|
58756
|
+
}
|
|
58757
|
+
function findNamedTemplate(templates, templateId) {
|
|
58758
|
+
return templates.find((t) => t.id === templateId);
|
|
58759
|
+
}
|
|
58760
|
+
//#endregion
|
|
58131
58761
|
//#region src/osd-manager.ts
|
|
58132
58762
|
/**
|
|
58133
58763
|
* The `osd-manager` provider — the render loop and the operator surface.
|
|
@@ -58491,6 +59121,26 @@ var OsdManager = class {
|
|
|
58491
59121
|
renderDevice = async ({ deviceId }) => {
|
|
58492
59122
|
return { results: [...await this.renderDeviceInternal(deviceId, null)] };
|
|
58493
59123
|
};
|
|
59124
|
+
listTemplates = async () => {
|
|
59125
|
+
return { templates: [...await this.deps.templates.get()] };
|
|
59126
|
+
};
|
|
59127
|
+
upsertTemplate = async ({ template }) => {
|
|
59128
|
+
const next = upsertNamedTemplate(await this.deps.templates.get(), template);
|
|
59129
|
+
await this.deps.templates.set(next);
|
|
59130
|
+
const stored = findNamedTemplate(next, template.id);
|
|
59131
|
+
if (stored === void 0) throw new Error(`osd-manager: template "${template.id}" missing after upsert`);
|
|
59132
|
+
this.logger.info("osd-manager: named template upserted", { meta: {
|
|
59133
|
+
templateId: stored.id,
|
|
59134
|
+
bindingCount: stored.bindings.length
|
|
59135
|
+
} });
|
|
59136
|
+
return { template: stored };
|
|
59137
|
+
};
|
|
59138
|
+
deleteTemplate = async ({ templateId }) => {
|
|
59139
|
+
const prev = await this.deps.templates.get();
|
|
59140
|
+
await this.deps.templates.set(deleteNamedTemplate(prev, templateId));
|
|
59141
|
+
this.logger.info("osd-manager: named template deleted", { meta: { templateId } });
|
|
59142
|
+
return { success: true };
|
|
59143
|
+
};
|
|
58494
59144
|
async renderAllBound() {
|
|
58495
59145
|
for (const deviceId of [...this.boundDevices]) try {
|
|
58496
59146
|
await this.renderDeviceInternal(deviceId, null);
|
|
@@ -58558,11 +59208,19 @@ var OsdManager = class {
|
|
|
58558
59208
|
async collectFacts(cameraDeviceId, bindings) {
|
|
58559
59209
|
const wanted = new Set([cameraDeviceId]);
|
|
58560
59210
|
const needsRecognition = Object.values(bindings).some((binding) => binding.source.kind === "latest-recognition");
|
|
59211
|
+
const templateIds = /* @__PURE__ */ new Set();
|
|
58561
59212
|
for (const binding of Object.values(bindings)) {
|
|
58562
59213
|
if (binding.source.kind === "device-state" && binding.source.deviceId !== void 0) wanted.add(binding.source.deviceId);
|
|
59214
|
+
if (binding.source.kind === "template") templateIds.add(binding.source.templateId);
|
|
58563
59215
|
const gated = binding.conditions?.deviceState?.deviceId;
|
|
58564
59216
|
if (gated !== void 0) wanted.add(gated);
|
|
58565
59217
|
}
|
|
59218
|
+
const allTemplates = await this.deps.templates.get();
|
|
59219
|
+
const templatesById = /* @__PURE__ */ new Map();
|
|
59220
|
+
for (const named of allTemplates) {
|
|
59221
|
+
templatesById.set(named.id, named);
|
|
59222
|
+
if (templateIds.has(named.id)) for (const binding of named.bindings) wanted.add(binding.pick.deviceId);
|
|
59223
|
+
}
|
|
58566
59224
|
const stateByDevice = /* @__PURE__ */ new Map();
|
|
58567
59225
|
const gateState = /* @__PURE__ */ new Map();
|
|
58568
59226
|
for (const id of wanted) try {
|
|
@@ -58587,7 +59245,8 @@ var OsdManager = class {
|
|
|
58587
59245
|
atMs: this.now(),
|
|
58588
59246
|
stateByDevice,
|
|
58589
59247
|
recognitionByDevice,
|
|
58590
|
-
deviceState: (id) => gateState.get(id)
|
|
59248
|
+
deviceState: (id) => gateState.get(id),
|
|
59249
|
+
templatesById
|
|
58591
59250
|
};
|
|
58592
59251
|
}
|
|
58593
59252
|
async hydrateRecognitions(deviceId) {
|
|
@@ -58728,6 +59387,7 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58728
59387
|
const ctx = this.ctx;
|
|
58729
59388
|
const store = new OsdBindingsStore((deviceId, key, schema, fallback) => this.deviceState(deviceId, key, schema, fallback));
|
|
58730
59389
|
const boundIndex = this.state(OSD_BOUND_INDEX_KEY, OsdBoundIndexSchema, null);
|
|
59390
|
+
const templates = this.state(OSD_TEMPLATES_KEY, OsdNamedTemplatesSchema, EMPTY_TEMPLATES);
|
|
58731
59391
|
const manager = new OsdManager({
|
|
58732
59392
|
logger: ctx.logger,
|
|
58733
59393
|
readOsdStatus: async (deviceId) => {
|
|
@@ -58761,7 +59421,8 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58761
59421
|
listDevices: async () => {
|
|
58762
59422
|
return (await ctx.api.deviceManager.listAll.query({ projection: "slim" })).map((d) => ({
|
|
58763
59423
|
id: d.id,
|
|
58764
|
-
name: d.name
|
|
59424
|
+
name: d.name,
|
|
59425
|
+
type: d.type ?? null
|
|
58765
59426
|
}));
|
|
58766
59427
|
},
|
|
58767
59428
|
listCameras: async () => {
|
|
@@ -58801,7 +59462,8 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58801
59462
|
};
|
|
58802
59463
|
},
|
|
58803
59464
|
bindings: (deviceId) => store.forDevice(deviceId),
|
|
58804
|
-
boundIndex
|
|
59465
|
+
boundIndex,
|
|
59466
|
+
templates
|
|
58805
59467
|
});
|
|
58806
59468
|
this.manager = manager;
|
|
58807
59469
|
this.subscribe({ category: EventCategory.PipelineAnalyticsTrackLifecycle }, (event) => {
|
|
@@ -58858,4 +59520,41 @@ var OsdManagerAddon = class extends BaseAddon {
|
|
|
58858
59520
|
}
|
|
58859
59521
|
};
|
|
58860
59522
|
//#endregion
|
|
58861
|
-
|
|
59523
|
+
//#region src/osd-picker-devices.ts
|
|
59524
|
+
/**
|
|
59525
|
+
* Devices offered to the OSD DevicePicker — containers INCLUDED so the
|
|
59526
|
+
* operator can pick a gateway, then an OSD-supported child.
|
|
59527
|
+
*/
|
|
59528
|
+
function toOsdPickerDevices(devices) {
|
|
59529
|
+
return devices.map((d) => ({
|
|
59530
|
+
id: d.id,
|
|
59531
|
+
name: d.name,
|
|
59532
|
+
type: d.type ?? null,
|
|
59533
|
+
location: d.location ?? null,
|
|
59534
|
+
parentDeviceId: d.parentDeviceId ?? null
|
|
59535
|
+
}));
|
|
59536
|
+
}
|
|
59537
|
+
/** Children of a container that have at least one OSD catalog field. */
|
|
59538
|
+
function osdSupportedChildrenOf(containerId, devices, deviceIdsWithOsdFields) {
|
|
59539
|
+
return devices.filter((d) => d.parentDeviceId === containerId && isOsdLeafDeviceType(d.type) && deviceIdsWithOsdFields.has(d.id)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
59540
|
+
}
|
|
59541
|
+
function deviceById(devices, id) {
|
|
59542
|
+
return devices.find((d) => d.id === id);
|
|
59543
|
+
}
|
|
59544
|
+
//#endregion
|
|
59545
|
+
//#region src/template-id.ts
|
|
59546
|
+
/**
|
|
59547
|
+
* Named-template ids are system-generated — never an operator text field.
|
|
59548
|
+
* Create uses a uuid; the editable surface is the template name.
|
|
59549
|
+
*/
|
|
59550
|
+
function generateTemplateId() {
|
|
59551
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
59552
|
+
return `tpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
59553
|
+
}
|
|
59554
|
+
/** Optional slug helper when a stable short id from the name is preferred. */
|
|
59555
|
+
function templateIdFromName(name) {
|
|
59556
|
+
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
59557
|
+
return `${slug.length > 0 ? slug : "template"}-${Date.now().toString(36)}`;
|
|
59558
|
+
}
|
|
59559
|
+
//#endregion
|
|
59560
|
+
export { DEFAULT_TICK_MS, EMPTY_BINDINGS, OSD_BINDINGS_KEY, OSD_NON_LEAF_TYPES, OSD_PICKER_EXCLUDED_TYPES, OSD_SOURCE_CAP_NAMES, OSD_SUPPORTED_CONDITIONS, OsdBindingsStore, OsdDeviceBindingsSchema, OsdManager, OsdManagerAddon, OsdManagerAddon as default, aliasToken, applyTemplate, applyTemplateFormat, assertSupportedConditions, bindingToken, deviceById, displayValue, evaluateGate, formatClock, formatSlotText, generateTemplateId, humanizeCapName, isContainerDeviceType, isOsdLeafDeviceType, isOsdPickerDeviceType, isOsdSourceCap, limitText, osdStateLeavesOf, osdSupportedChildrenOf, parseAliasTokens, readStatePath, renderNamedTemplate, renderSlot, replaceToken, resolveBindingLive, resolveSource, sourceOptionsFor, stateLeavesOf, suggestAlias, templateIdFromName, toOsdPickerDevices, truncateDecimals, unsupportedConditionKeys, withBinding, withoutBinding };
|