@camstack/types 1.2.446 → 1.2.447
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/CHANGELOG.md +6 -0
- package/dist/automation/action.d.ts +2 -0
- package/dist/automation/index.d.ts +3 -1
- package/dist/automation/normalize-units.d.ts +59 -0
- package/dist/automation/recipe.d.ts +15 -1
- package/dist/capabilities/action-source.cap.d.ts +1 -0
- package/dist/capabilities/actions.cap.d.ts +32 -1
- package/dist/capabilities/core-blocks.cap.d.ts +17 -0
- package/dist/capabilities/remote.cap.d.ts +2 -0
- package/dist/device/cap-vocabulary/types.d.ts +9 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1087 -728
- package/dist/index.mjs +1075 -729
- package/dist/remote/remote-config.d.ts +3 -0
- package/dist/units/index.d.ts +3 -0
- package/dist/units/quantity-input.d.ts +58 -0
- package/dist/units/quantity-units.d.ts +8 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8060,6 +8060,791 @@ var ExpressionSourceSchema = zod.z.object({
|
|
|
8060
8060
|
});
|
|
8061
8061
|
});
|
|
8062
8062
|
//#endregion
|
|
8063
|
+
//#region src/device/schema-fields.ts
|
|
8064
|
+
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
8065
|
+
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
8066
|
+
function unwrapSchema(schema) {
|
|
8067
|
+
let s = schema;
|
|
8068
|
+
while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
|
|
8069
|
+
return s;
|
|
8070
|
+
}
|
|
8071
|
+
function isMetaBag(value) {
|
|
8072
|
+
return typeof value === "object" && value !== null;
|
|
8073
|
+
}
|
|
8074
|
+
function metaQuantity(schema) {
|
|
8075
|
+
const bag = zod.z.globalRegistry.get(schema);
|
|
8076
|
+
if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
|
|
8077
|
+
}
|
|
8078
|
+
/** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
|
|
8079
|
+
function quantityOfField(schema) {
|
|
8080
|
+
return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
|
|
8081
|
+
}
|
|
8082
|
+
function leafKind(schema) {
|
|
8083
|
+
const s = unwrapSchema(schema);
|
|
8084
|
+
if (s instanceof zod.z.ZodEnum) return {
|
|
8085
|
+
kind: "enum",
|
|
8086
|
+
enumValues: s.options
|
|
8087
|
+
};
|
|
8088
|
+
if (s instanceof zod.z.ZodNumber) return { kind: "number" };
|
|
8089
|
+
if (s instanceof zod.z.ZodString) return { kind: "string" };
|
|
8090
|
+
if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
|
|
8091
|
+
return null;
|
|
8092
|
+
}
|
|
8093
|
+
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
8094
|
+
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
8095
|
+
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
8096
|
+
* are skipped — never throws. */
|
|
8097
|
+
function enumerateSchemaFields(schema, prefix = "") {
|
|
8098
|
+
const root = unwrapSchema(schema);
|
|
8099
|
+
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
8100
|
+
const out = [];
|
|
8101
|
+
const shape = root.shape;
|
|
8102
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
8103
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
8104
|
+
const inner = unwrapSchema(field);
|
|
8105
|
+
if (inner instanceof zod.z.ZodObject) {
|
|
8106
|
+
out.push(...enumerateSchemaFields(inner, path));
|
|
8107
|
+
continue;
|
|
8108
|
+
}
|
|
8109
|
+
const leaf = leafKind(field);
|
|
8110
|
+
if (leaf) {
|
|
8111
|
+
const quantity = quantityOfField(field);
|
|
8112
|
+
out.push({
|
|
8113
|
+
path,
|
|
8114
|
+
kind: leaf.kind,
|
|
8115
|
+
...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
|
|
8116
|
+
...quantity !== void 0 ? { quantity } : {}
|
|
8117
|
+
});
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
return out;
|
|
8121
|
+
}
|
|
8122
|
+
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
8123
|
+
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
8124
|
+
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
8125
|
+
* source), each tagged `item: true` so the authoring UI collects an
|
|
8126
|
+
* `itemKey` alongside the field. Never throws. */
|
|
8127
|
+
function enumerateItemArrayFields(itemArray) {
|
|
8128
|
+
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
8129
|
+
...f,
|
|
8130
|
+
item: true
|
|
8131
|
+
}));
|
|
8132
|
+
}
|
|
8133
|
+
//#endregion
|
|
8134
|
+
//#region src/device/source-info.ts
|
|
8135
|
+
/**
|
|
8136
|
+
* Identity envelope for a device's upstream-system metadata.
|
|
8137
|
+
*
|
|
8138
|
+
* Two jobs:
|
|
8139
|
+
*
|
|
8140
|
+
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
8141
|
+
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
8142
|
+
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
8143
|
+
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
8144
|
+
* without round-tripping through `stableId` / `id` mappings.
|
|
8145
|
+
*
|
|
8146
|
+
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
8147
|
+
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
8148
|
+
* for vendors where MAC is the natural key). For systems where the
|
|
8149
|
+
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
8150
|
+
* providers detect renames by matching `uniqueId` and patch `id`
|
|
8151
|
+
* in-place so dispatch keeps working.
|
|
8152
|
+
*
|
|
8153
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
8154
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
8155
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
8156
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
8157
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
8158
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
8159
|
+
*
|
|
8160
|
+
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
8161
|
+
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
8162
|
+
* details, …). Providers control what lands here.
|
|
8163
|
+
*/
|
|
8164
|
+
var SourceInfoSchema = zod.z.object({
|
|
8165
|
+
/** Live dispatch key — mutable when the source system allows rename. */
|
|
8166
|
+
id: zod.z.string(),
|
|
8167
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
8168
|
+
system: zod.z.string(),
|
|
8169
|
+
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
8170
|
+
* Used to detect rename when `id` changes. */
|
|
8171
|
+
uniqueId: zod.z.string().optional(),
|
|
8172
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
8173
|
+
quantity: require_sleep.ReadingQuantitySchema.optional(),
|
|
8174
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
8175
|
+
dateTimeShape: zod.z.enum([
|
|
8176
|
+
"date",
|
|
8177
|
+
"time",
|
|
8178
|
+
"datetime"
|
|
8179
|
+
]).optional(),
|
|
8180
|
+
/** Provider-specific extras that don't fit the structured slots.
|
|
8181
|
+
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
8182
|
+
* attribute blobs (would explode the meta JSON column). */
|
|
8183
|
+
raw: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
8184
|
+
});
|
|
8185
|
+
/**
|
|
8186
|
+
* Build the synthetic SourceInfo every existing provider falls back to
|
|
8187
|
+
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
8188
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
8189
|
+
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
8190
|
+
* id. Real upstream identity replaces this once a provider migrates and
|
|
8191
|
+
* calls `updateSourceInfo()` with concrete values.
|
|
8192
|
+
*/
|
|
8193
|
+
function synthesizeSourceInfo(input) {
|
|
8194
|
+
return {
|
|
8195
|
+
id: input.stableId,
|
|
8196
|
+
system: input.addonId
|
|
8197
|
+
};
|
|
8198
|
+
}
|
|
8199
|
+
/**
|
|
8200
|
+
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
8201
|
+
* patch values are ignored (no field clobber); to clear a field
|
|
8202
|
+
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
8203
|
+
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
8204
|
+
* bother with a `null`-means-delete sentinel.
|
|
8205
|
+
*
|
|
8206
|
+
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
8207
|
+
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
8208
|
+
*/
|
|
8209
|
+
function mergeSourceInfo(prev, patch) {
|
|
8210
|
+
const next = { ...prev };
|
|
8211
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
8212
|
+
if (v === void 0) continue;
|
|
8213
|
+
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
8214
|
+
next.raw = {
|
|
8215
|
+
...prev.raw,
|
|
8216
|
+
...v
|
|
8217
|
+
};
|
|
8218
|
+
continue;
|
|
8219
|
+
}
|
|
8220
|
+
next[k] = v;
|
|
8221
|
+
}
|
|
8222
|
+
return next;
|
|
8223
|
+
}
|
|
8224
|
+
/**
|
|
8225
|
+
* Coerce common unit-of-measurement string variants to a canonical form
|
|
8226
|
+
* so the UI's display templates can rely on stable strings. Pure
|
|
8227
|
+
* passthrough for unknown units — kept conservative on purpose, the goal
|
|
8228
|
+
* is to normalise the long tail of HA `unit_of_measurement` values that
|
|
8229
|
+
* have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
|
|
8230
|
+
*
|
|
8231
|
+
* Extend the map as we encounter real-world data. Returning the raw
|
|
8232
|
+
* input for unknown values is intentional: providers should never
|
|
8233
|
+
* lose information silently here.
|
|
8234
|
+
*/
|
|
8235
|
+
function normalizeUnit(raw) {
|
|
8236
|
+
if (raw == null) return void 0;
|
|
8237
|
+
const trimmed = raw.trim();
|
|
8238
|
+
if (trimmed === "") return void 0;
|
|
8239
|
+
const lower = trimmed.toLowerCase();
|
|
8240
|
+
if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
|
|
8241
|
+
if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
|
|
8242
|
+
if (lower === "k" || lower === "kelvin") return "K";
|
|
8243
|
+
if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
|
|
8244
|
+
if (lower === "lx" || lower === "lux") return "lx";
|
|
8245
|
+
if (lower === "hpa" || lower === "hectopascal") return "hPa";
|
|
8246
|
+
if (lower === "kpa") return "kPa";
|
|
8247
|
+
if (lower === "pa") return "Pa";
|
|
8248
|
+
if (lower === "mbar" || lower === "millibar") return "mbar";
|
|
8249
|
+
if (lower === "w" || lower === "watt") return "W";
|
|
8250
|
+
if (lower === "kw" || lower === "kilowatt") return "kW";
|
|
8251
|
+
if (lower === "wh" || lower === "watt-hour") return "Wh";
|
|
8252
|
+
if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
|
|
8253
|
+
if (lower === "v" || lower === "volt") return "V";
|
|
8254
|
+
if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
|
|
8255
|
+
if (lower === "ma" || lower === "milliampere") return "mA";
|
|
8256
|
+
if (lower === "va") return "VA";
|
|
8257
|
+
if (lower === "ppm") return "ppm";
|
|
8258
|
+
if (lower === "ppb") return "ppb";
|
|
8259
|
+
if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
|
|
8260
|
+
if (lower === "km/h" || lower === "kph") return "km/h";
|
|
8261
|
+
if (lower === "mph") return "mph";
|
|
8262
|
+
if (lower === "m/s") return "m/s";
|
|
8263
|
+
if (lower === "m") return "m";
|
|
8264
|
+
if (lower === "m²" || lower === "m2") return "m²";
|
|
8265
|
+
if (lower === "mm") return "mm";
|
|
8266
|
+
if (lower === "cm") return "cm";
|
|
8267
|
+
if (lower === "km") return "km";
|
|
8268
|
+
if (lower === "in" || lower === "inch" || lower === "inches") return "in";
|
|
8269
|
+
if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
|
|
8270
|
+
if (lower === "kg") return "kg";
|
|
8271
|
+
if (lower === "g") return "g";
|
|
8272
|
+
if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
|
|
8273
|
+
if (lower === "ml") return "mL";
|
|
8274
|
+
if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
|
|
8275
|
+
if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
|
|
8276
|
+
if (lower === "ms") return "ms";
|
|
8277
|
+
if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
|
|
8278
|
+
if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
|
|
8279
|
+
if (lower === "d" || lower === "day" || lower === "days") return "d";
|
|
8280
|
+
if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
|
|
8281
|
+
if (lower === "kib") return "KiB";
|
|
8282
|
+
if (lower === "mib") return "MiB";
|
|
8283
|
+
if (lower === "gib") return "GiB";
|
|
8284
|
+
if (lower === "tib") return "TiB";
|
|
8285
|
+
if (lower === "kb") return "kB";
|
|
8286
|
+
if (lower === "mb") return "MB";
|
|
8287
|
+
if (lower === "gb") return "GB";
|
|
8288
|
+
if (lower === "dbm") return "dBm";
|
|
8289
|
+
if (lower === "dbfs") return "dBFS";
|
|
8290
|
+
if (lower === "inhg") return "inHg";
|
|
8291
|
+
if (lower === "mm/h") return "mm/h";
|
|
8292
|
+
if (lower === "w/m²" || lower === "w/m2") return "W/m²";
|
|
8293
|
+
if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
|
|
8294
|
+
if (lower === "kbps") return "kbps";
|
|
8295
|
+
if (lower === "fps") return "fps";
|
|
8296
|
+
return trimmed;
|
|
8297
|
+
}
|
|
8298
|
+
/**
|
|
8299
|
+
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
8300
|
+
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
8301
|
+
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
8302
|
+
* lives under a single nested key to keep it composable with those.
|
|
8303
|
+
*/
|
|
8304
|
+
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
8305
|
+
/**
|
|
8306
|
+
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
8307
|
+
* with Zod. Returns `null` when the blob is missing or the nested
|
|
8308
|
+
* `sourceInfo` key fails validation — callers fall back to the
|
|
8309
|
+
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
8310
|
+
*/
|
|
8311
|
+
function extractSourceInfoFromMetadata(metadata) {
|
|
8312
|
+
if (!metadata) return null;
|
|
8313
|
+
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
8314
|
+
if (!raw) return null;
|
|
8315
|
+
const parsed = SourceInfoSchema.safeParse(raw);
|
|
8316
|
+
if (!parsed.success) return null;
|
|
8317
|
+
return parsed.data;
|
|
8318
|
+
}
|
|
8319
|
+
//#endregion
|
|
8320
|
+
//#region src/units/unit-table.ts
|
|
8321
|
+
/**
|
|
8322
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
8323
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
8324
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
8325
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
8326
|
+
*/
|
|
8327
|
+
var UNIT_TABLE = {
|
|
8328
|
+
"°C": {
|
|
8329
|
+
dimension: "temperature",
|
|
8330
|
+
factor: 1,
|
|
8331
|
+
offset: 0
|
|
8332
|
+
},
|
|
8333
|
+
"°F": {
|
|
8334
|
+
dimension: "temperature",
|
|
8335
|
+
factor: 5 / 9,
|
|
8336
|
+
offset: -160 / 9
|
|
8337
|
+
},
|
|
8338
|
+
K: {
|
|
8339
|
+
dimension: "temperature",
|
|
8340
|
+
factor: 1,
|
|
8341
|
+
offset: -273.15
|
|
8342
|
+
},
|
|
8343
|
+
hPa: {
|
|
8344
|
+
dimension: "pressure",
|
|
8345
|
+
factor: 1,
|
|
8346
|
+
offset: 0
|
|
8347
|
+
},
|
|
8348
|
+
kPa: {
|
|
8349
|
+
dimension: "pressure",
|
|
8350
|
+
factor: 10,
|
|
8351
|
+
offset: 0
|
|
8352
|
+
},
|
|
8353
|
+
Pa: {
|
|
8354
|
+
dimension: "pressure",
|
|
8355
|
+
factor: .01,
|
|
8356
|
+
offset: 0
|
|
8357
|
+
},
|
|
8358
|
+
mbar: {
|
|
8359
|
+
dimension: "pressure",
|
|
8360
|
+
factor: 1,
|
|
8361
|
+
offset: 0
|
|
8362
|
+
},
|
|
8363
|
+
bar: {
|
|
8364
|
+
dimension: "pressure",
|
|
8365
|
+
factor: 1e3,
|
|
8366
|
+
offset: 0
|
|
8367
|
+
},
|
|
8368
|
+
inHg: {
|
|
8369
|
+
dimension: "pressure",
|
|
8370
|
+
factor: 33.8639,
|
|
8371
|
+
offset: 0
|
|
8372
|
+
},
|
|
8373
|
+
mmHg: {
|
|
8374
|
+
dimension: "pressure",
|
|
8375
|
+
factor: 1.33322,
|
|
8376
|
+
offset: 0
|
|
8377
|
+
},
|
|
8378
|
+
psi: {
|
|
8379
|
+
dimension: "pressure",
|
|
8380
|
+
factor: 68.9476,
|
|
8381
|
+
offset: 0
|
|
8382
|
+
},
|
|
8383
|
+
"m/s": {
|
|
8384
|
+
dimension: "speed",
|
|
8385
|
+
factor: 1,
|
|
8386
|
+
offset: 0
|
|
8387
|
+
},
|
|
8388
|
+
"km/h": {
|
|
8389
|
+
dimension: "speed",
|
|
8390
|
+
factor: 1 / 3.6,
|
|
8391
|
+
offset: 0
|
|
8392
|
+
},
|
|
8393
|
+
mph: {
|
|
8394
|
+
dimension: "speed",
|
|
8395
|
+
factor: .44704,
|
|
8396
|
+
offset: 0
|
|
8397
|
+
},
|
|
8398
|
+
kn: {
|
|
8399
|
+
dimension: "speed",
|
|
8400
|
+
factor: .514444,
|
|
8401
|
+
offset: 0
|
|
8402
|
+
},
|
|
8403
|
+
"mm/h": {
|
|
8404
|
+
dimension: "precipitation-rate",
|
|
8405
|
+
factor: 1,
|
|
8406
|
+
offset: 0
|
|
8407
|
+
},
|
|
8408
|
+
"in/h": {
|
|
8409
|
+
dimension: "precipitation-rate",
|
|
8410
|
+
factor: 25.4,
|
|
8411
|
+
offset: 0
|
|
8412
|
+
},
|
|
8413
|
+
m: {
|
|
8414
|
+
dimension: "length",
|
|
8415
|
+
factor: 1,
|
|
8416
|
+
offset: 0
|
|
8417
|
+
},
|
|
8418
|
+
mm: {
|
|
8419
|
+
dimension: "length",
|
|
8420
|
+
factor: .001,
|
|
8421
|
+
offset: 0
|
|
8422
|
+
},
|
|
8423
|
+
cm: {
|
|
8424
|
+
dimension: "length",
|
|
8425
|
+
factor: .01,
|
|
8426
|
+
offset: 0
|
|
8427
|
+
},
|
|
8428
|
+
km: {
|
|
8429
|
+
dimension: "length",
|
|
8430
|
+
factor: 1e3,
|
|
8431
|
+
offset: 0
|
|
8432
|
+
},
|
|
8433
|
+
in: {
|
|
8434
|
+
dimension: "length",
|
|
8435
|
+
factor: .0254,
|
|
8436
|
+
offset: 0
|
|
8437
|
+
},
|
|
8438
|
+
ft: {
|
|
8439
|
+
dimension: "length",
|
|
8440
|
+
factor: .3048,
|
|
8441
|
+
offset: 0
|
|
8442
|
+
},
|
|
8443
|
+
mi: {
|
|
8444
|
+
dimension: "length",
|
|
8445
|
+
factor: 1609.344,
|
|
8446
|
+
offset: 0
|
|
8447
|
+
},
|
|
8448
|
+
"m²": {
|
|
8449
|
+
dimension: "area",
|
|
8450
|
+
factor: 1,
|
|
8451
|
+
offset: 0
|
|
8452
|
+
},
|
|
8453
|
+
lx: {
|
|
8454
|
+
dimension: "illuminance",
|
|
8455
|
+
factor: 1,
|
|
8456
|
+
offset: 0
|
|
8457
|
+
},
|
|
8458
|
+
"W/m²": {
|
|
8459
|
+
dimension: "irradiance",
|
|
8460
|
+
factor: 1,
|
|
8461
|
+
offset: 0
|
|
8462
|
+
},
|
|
8463
|
+
W: {
|
|
8464
|
+
dimension: "power",
|
|
8465
|
+
factor: 1,
|
|
8466
|
+
offset: 0
|
|
8467
|
+
},
|
|
8468
|
+
kW: {
|
|
8469
|
+
dimension: "power",
|
|
8470
|
+
factor: 1e3,
|
|
8471
|
+
offset: 0
|
|
8472
|
+
},
|
|
8473
|
+
VA: {
|
|
8474
|
+
dimension: "apparent-power",
|
|
8475
|
+
factor: 1,
|
|
8476
|
+
offset: 0
|
|
8477
|
+
},
|
|
8478
|
+
Wh: {
|
|
8479
|
+
dimension: "energy",
|
|
8480
|
+
factor: 1,
|
|
8481
|
+
offset: 0
|
|
8482
|
+
},
|
|
8483
|
+
kWh: {
|
|
8484
|
+
dimension: "energy",
|
|
8485
|
+
factor: 1e3,
|
|
8486
|
+
offset: 0
|
|
8487
|
+
},
|
|
8488
|
+
MWh: {
|
|
8489
|
+
dimension: "energy",
|
|
8490
|
+
factor: 1e6,
|
|
8491
|
+
offset: 0
|
|
8492
|
+
},
|
|
8493
|
+
V: {
|
|
8494
|
+
dimension: "voltage",
|
|
8495
|
+
factor: 1,
|
|
8496
|
+
offset: 0
|
|
8497
|
+
},
|
|
8498
|
+
mV: {
|
|
8499
|
+
dimension: "voltage",
|
|
8500
|
+
factor: .001,
|
|
8501
|
+
offset: 0
|
|
8502
|
+
},
|
|
8503
|
+
A: {
|
|
8504
|
+
dimension: "current",
|
|
8505
|
+
factor: 1,
|
|
8506
|
+
offset: 0
|
|
8507
|
+
},
|
|
8508
|
+
mA: {
|
|
8509
|
+
dimension: "current",
|
|
8510
|
+
factor: .001,
|
|
8511
|
+
offset: 0
|
|
8512
|
+
},
|
|
8513
|
+
ppm: {
|
|
8514
|
+
dimension: "concentration-volume",
|
|
8515
|
+
factor: 1,
|
|
8516
|
+
offset: 0
|
|
8517
|
+
},
|
|
8518
|
+
ppb: {
|
|
8519
|
+
dimension: "concentration-volume",
|
|
8520
|
+
factor: .001,
|
|
8521
|
+
offset: 0
|
|
8522
|
+
},
|
|
8523
|
+
"µg/m³": {
|
|
8524
|
+
dimension: "concentration-mass",
|
|
8525
|
+
factor: 1,
|
|
8526
|
+
offset: 0
|
|
8527
|
+
},
|
|
8528
|
+
"mg/m³": {
|
|
8529
|
+
dimension: "concentration-mass",
|
|
8530
|
+
factor: 1e3,
|
|
8531
|
+
offset: 0
|
|
8532
|
+
},
|
|
8533
|
+
kg: {
|
|
8534
|
+
dimension: "mass",
|
|
8535
|
+
factor: 1,
|
|
8536
|
+
offset: 0
|
|
8537
|
+
},
|
|
8538
|
+
g: {
|
|
8539
|
+
dimension: "mass",
|
|
8540
|
+
factor: .001,
|
|
8541
|
+
offset: 0
|
|
8542
|
+
},
|
|
8543
|
+
lb: {
|
|
8544
|
+
dimension: "mass",
|
|
8545
|
+
factor: .453592,
|
|
8546
|
+
offset: 0
|
|
8547
|
+
},
|
|
8548
|
+
oz: {
|
|
8549
|
+
dimension: "mass",
|
|
8550
|
+
factor: .0283495,
|
|
8551
|
+
offset: 0
|
|
8552
|
+
},
|
|
8553
|
+
"%": {
|
|
8554
|
+
dimension: "percentage",
|
|
8555
|
+
factor: 1,
|
|
8556
|
+
offset: 0
|
|
8557
|
+
},
|
|
8558
|
+
L: {
|
|
8559
|
+
dimension: "volume",
|
|
8560
|
+
factor: 1,
|
|
8561
|
+
offset: 0
|
|
8562
|
+
},
|
|
8563
|
+
mL: {
|
|
8564
|
+
dimension: "volume",
|
|
8565
|
+
factor: .001,
|
|
8566
|
+
offset: 0
|
|
8567
|
+
},
|
|
8568
|
+
gal: {
|
|
8569
|
+
dimension: "volume",
|
|
8570
|
+
factor: 3.785411784,
|
|
8571
|
+
offset: 0
|
|
8572
|
+
},
|
|
8573
|
+
s: {
|
|
8574
|
+
dimension: "duration",
|
|
8575
|
+
factor: 1,
|
|
8576
|
+
offset: 0
|
|
8577
|
+
},
|
|
8578
|
+
ms: {
|
|
8579
|
+
dimension: "duration",
|
|
8580
|
+
factor: .001,
|
|
8581
|
+
offset: 0
|
|
8582
|
+
},
|
|
8583
|
+
min: {
|
|
8584
|
+
dimension: "duration",
|
|
8585
|
+
factor: 60,
|
|
8586
|
+
offset: 0
|
|
8587
|
+
},
|
|
8588
|
+
h: {
|
|
8589
|
+
dimension: "duration",
|
|
8590
|
+
factor: 3600,
|
|
8591
|
+
offset: 0
|
|
8592
|
+
},
|
|
8593
|
+
d: {
|
|
8594
|
+
dimension: "duration",
|
|
8595
|
+
factor: 86400,
|
|
8596
|
+
offset: 0
|
|
8597
|
+
},
|
|
8598
|
+
B: {
|
|
8599
|
+
dimension: "information",
|
|
8600
|
+
factor: 1,
|
|
8601
|
+
offset: 0
|
|
8602
|
+
},
|
|
8603
|
+
KiB: {
|
|
8604
|
+
dimension: "information",
|
|
8605
|
+
factor: 1024,
|
|
8606
|
+
offset: 0
|
|
8607
|
+
},
|
|
8608
|
+
MiB: {
|
|
8609
|
+
dimension: "information",
|
|
8610
|
+
factor: 1024 ** 2,
|
|
8611
|
+
offset: 0
|
|
8612
|
+
},
|
|
8613
|
+
GiB: {
|
|
8614
|
+
dimension: "information",
|
|
8615
|
+
factor: 1024 ** 3,
|
|
8616
|
+
offset: 0
|
|
8617
|
+
},
|
|
8618
|
+
TiB: {
|
|
8619
|
+
dimension: "information",
|
|
8620
|
+
factor: 1024 ** 4,
|
|
8621
|
+
offset: 0
|
|
8622
|
+
},
|
|
8623
|
+
kB: {
|
|
8624
|
+
dimension: "information",
|
|
8625
|
+
factor: 1e3,
|
|
8626
|
+
offset: 0
|
|
8627
|
+
},
|
|
8628
|
+
MB: {
|
|
8629
|
+
dimension: "information",
|
|
8630
|
+
factor: 1e6,
|
|
8631
|
+
offset: 0
|
|
8632
|
+
},
|
|
8633
|
+
GB: {
|
|
8634
|
+
dimension: "information",
|
|
8635
|
+
factor: 1e9,
|
|
8636
|
+
offset: 0
|
|
8637
|
+
},
|
|
8638
|
+
"°": {
|
|
8639
|
+
dimension: "angle",
|
|
8640
|
+
factor: 1,
|
|
8641
|
+
offset: 0
|
|
8642
|
+
},
|
|
8643
|
+
dBFS: {
|
|
8644
|
+
dimension: "sound-level",
|
|
8645
|
+
factor: 1,
|
|
8646
|
+
offset: 0
|
|
8647
|
+
},
|
|
8648
|
+
dBm: {
|
|
8649
|
+
dimension: "signal-level",
|
|
8650
|
+
factor: 1,
|
|
8651
|
+
offset: 0
|
|
8652
|
+
},
|
|
8653
|
+
mired: {
|
|
8654
|
+
dimension: "colour-temperature",
|
|
8655
|
+
factor: 1,
|
|
8656
|
+
offset: 0
|
|
8657
|
+
},
|
|
8658
|
+
kbps: {
|
|
8659
|
+
dimension: "bitrate",
|
|
8660
|
+
factor: 1,
|
|
8661
|
+
offset: 0
|
|
8662
|
+
},
|
|
8663
|
+
Mbps: {
|
|
8664
|
+
dimension: "bitrate",
|
|
8665
|
+
factor: 1e3,
|
|
8666
|
+
offset: 0
|
|
8667
|
+
},
|
|
8668
|
+
fps: {
|
|
8669
|
+
dimension: "rate",
|
|
8670
|
+
factor: 1,
|
|
8671
|
+
offset: 0
|
|
8672
|
+
}
|
|
8673
|
+
};
|
|
8674
|
+
//#endregion
|
|
8675
|
+
//#region src/units/convert.ts
|
|
8676
|
+
/**
|
|
8677
|
+
* Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
|
|
8678
|
+
*
|
|
8679
|
+
* Conversion is refused (throws `UnitConversionError`, or returns `undefined`
|
|
8680
|
+
* from the `try*` variant) whenever a unit is unknown or the two units belong
|
|
8681
|
+
* to different dimensions — cross-dimension conversion is deliberately
|
|
8682
|
+
* impossible so a render override can never make the value and its unit label
|
|
8683
|
+
* disagree.
|
|
8684
|
+
*
|
|
8685
|
+
* Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
|
|
8686
|
+
* calling here — the table keys only canonical spellings.
|
|
8687
|
+
*/
|
|
8688
|
+
/** Thrown by `convertUnit` when a conversion is refused. Carries the offending
|
|
8689
|
+
* units + a machine-readable `reason` for callers that branch on the cause. */
|
|
8690
|
+
var UnitConversionError = class extends Error {
|
|
8691
|
+
from;
|
|
8692
|
+
to;
|
|
8693
|
+
reason;
|
|
8694
|
+
constructor(from, to, reason) {
|
|
8695
|
+
super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
|
|
8696
|
+
this.name = "UnitConversionError";
|
|
8697
|
+
this.from = from;
|
|
8698
|
+
this.to = to;
|
|
8699
|
+
this.reason = reason;
|
|
8700
|
+
}
|
|
8701
|
+
};
|
|
8702
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
8703
|
+
function specFor(unit) {
|
|
8704
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
8705
|
+
}
|
|
8706
|
+
/** The dimension a unit belongs to, or `undefined` for an unknown unit. */
|
|
8707
|
+
function unitDimension(unit) {
|
|
8708
|
+
return specFor(unit)?.dimension;
|
|
8709
|
+
}
|
|
8710
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
8711
|
+
function toCanonical(value, spec) {
|
|
8712
|
+
return value * spec.factor + spec.offset;
|
|
8713
|
+
}
|
|
8714
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
8715
|
+
function fromCanonical(canonical, spec) {
|
|
8716
|
+
return (canonical - spec.offset) / spec.factor;
|
|
8717
|
+
}
|
|
8718
|
+
/**
|
|
8719
|
+
* Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
|
|
8720
|
+
* Throws `UnitConversionError` when either unit is unknown or the units belong
|
|
8721
|
+
* to different dimensions.
|
|
8722
|
+
*/
|
|
8723
|
+
function convertUnit(value, from, to) {
|
|
8724
|
+
if (from === to) return value;
|
|
8725
|
+
const fromSpec = specFor(from);
|
|
8726
|
+
if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
|
|
8727
|
+
const toSpec = specFor(to);
|
|
8728
|
+
if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
|
|
8729
|
+
if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
|
|
8730
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
8731
|
+
}
|
|
8732
|
+
/**
|
|
8733
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
8734
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
8735
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
8736
|
+
*/
|
|
8737
|
+
function tryConvertUnit(value, from, to) {
|
|
8738
|
+
if (from === to) return value;
|
|
8739
|
+
const fromSpec = specFor(from);
|
|
8740
|
+
if (!fromSpec) return void 0;
|
|
8741
|
+
const toSpec = specFor(to);
|
|
8742
|
+
if (!toSpec) return void 0;
|
|
8743
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
8744
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
8745
|
+
}
|
|
8746
|
+
/**
|
|
8747
|
+
* Whether `value` can be converted between `from` and `to` (same dimension,
|
|
8748
|
+
* both known). Cheap boolean probe for the UI's same-dimension unit picker and
|
|
8749
|
+
* the resolver's convertibility branch — compute ONCE so a unit label and its
|
|
8750
|
+
* value math can never disagree.
|
|
8751
|
+
*/
|
|
8752
|
+
function canConvertUnit(from, to) {
|
|
8753
|
+
if (from === to) return true;
|
|
8754
|
+
const fromDim = unitDimension(from);
|
|
8755
|
+
if (fromDim === void 0) return false;
|
|
8756
|
+
return fromDim === unitDimension(to);
|
|
8757
|
+
}
|
|
8758
|
+
/**
|
|
8759
|
+
* Every known unit spelling in a dimension (table insertion order — canonical
|
|
8760
|
+
* unit first). Powers the UI's same-dimension unit picker.
|
|
8761
|
+
*/
|
|
8762
|
+
/**
|
|
8763
|
+
* Convert a difference, not a reading. Affine units (°F) shift an absolute
|
|
8764
|
+
* value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
|
|
8765
|
+
*/
|
|
8766
|
+
function tryConvertDelta(value, from, to) {
|
|
8767
|
+
if (from === to) return value;
|
|
8768
|
+
const origin = tryConvertUnit(0, from, to);
|
|
8769
|
+
const shifted = tryConvertUnit(value, from, to);
|
|
8770
|
+
if (origin === void 0 || shifted === void 0) return void 0;
|
|
8771
|
+
return shifted - origin;
|
|
8772
|
+
}
|
|
8773
|
+
function unitsForDimension(dimension) {
|
|
8774
|
+
const out = [];
|
|
8775
|
+
for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
|
|
8776
|
+
return out;
|
|
8777
|
+
}
|
|
8778
|
+
//#endregion
|
|
8779
|
+
//#region src/units/quantity-input.ts
|
|
8780
|
+
/**
|
|
8781
|
+
* A number a person types for a physical quantity — the input side of the
|
|
8782
|
+
* value + unit contract (D972).
|
|
8783
|
+
*
|
|
8784
|
+
* Readings are stored in the base unit of their quantity (D944); a cap method
|
|
8785
|
+
* param is stored in the unit its input schema DECLARES (`paramMeta`). The
|
|
8786
|
+
* wire pair {@link QuantityValue} says "this number is expressed in this
|
|
8787
|
+
* unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
|
|
8788
|
+
* with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
|
|
8789
|
+
*/
|
|
8790
|
+
/** A unit that cannot be applied to the number it was given with. */
|
|
8791
|
+
var QuantityUnitError = class extends Error {
|
|
8792
|
+
code;
|
|
8793
|
+
constructor(code, message) {
|
|
8794
|
+
super(`[${code}] ${message}`);
|
|
8795
|
+
this.name = "QuantityUnitError";
|
|
8796
|
+
this.code = code;
|
|
8797
|
+
}
|
|
8798
|
+
};
|
|
8799
|
+
/** The wire pair: `value` is expressed in `unit`. */
|
|
8800
|
+
var QuantityValueSchema = zod.z.object({
|
|
8801
|
+
value: zod.z.number(),
|
|
8802
|
+
unit: zod.z.string().min(1)
|
|
8803
|
+
});
|
|
8804
|
+
function isQuantityValue(candidate) {
|
|
8805
|
+
return QuantityValueSchema.safeParse(candidate).success;
|
|
8806
|
+
}
|
|
8807
|
+
/**
|
|
8808
|
+
* `value` expressed in `unit`, brought to `target`. Throws
|
|
8809
|
+
* {@link QuantityUnitError} for a non-finite value, an unknown unit or a unit
|
|
8810
|
+
* of another dimension — never guesses.
|
|
8811
|
+
*/
|
|
8812
|
+
function convertQuantityInput(input) {
|
|
8813
|
+
if (!Number.isFinite(input.value)) throw new QuantityUnitError("non-finite", `${String(input.value)} is not a finite number`);
|
|
8814
|
+
const unit = normalizeUnit(input.unit);
|
|
8815
|
+
if (unit === void 0 || unitDimension(unit) === void 0) throw new QuantityUnitError("unknown-unit", `unknown unit "${input.unit}"`);
|
|
8816
|
+
const converted = tryConvertUnit(input.value, unit, input.target);
|
|
8817
|
+
if (converted === void 0) throw new QuantityUnitError("cross-dimension", `"${input.unit}" is not a unit of ${input.target}'s quantity`);
|
|
8818
|
+
return Number(converted.toPrecision(12));
|
|
8819
|
+
}
|
|
8820
|
+
/**
|
|
8821
|
+
* Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
|
|
8822
|
+
* input-side twin of `readingMeta`. The value is carried in `unit` (a gram
|
|
8823
|
+
* portion stays in grams); the unit must belong to the quantity, checked once
|
|
8824
|
+
* when the cap is declared.
|
|
8825
|
+
*/
|
|
8826
|
+
function paramMeta(declared) {
|
|
8827
|
+
const base = require_sleep.baseUnitOf(declared.quantity);
|
|
8828
|
+
if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
|
|
8829
|
+
if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
|
|
8830
|
+
return {
|
|
8831
|
+
quantity: declared.quantity,
|
|
8832
|
+
unit: declared.unit
|
|
8833
|
+
};
|
|
8834
|
+
}
|
|
8835
|
+
var ParamMetaBagSchema = zod.z.object({
|
|
8836
|
+
quantity: require_sleep.ReadingQuantitySchema,
|
|
8837
|
+
unit: zod.z.string().min(1)
|
|
8838
|
+
});
|
|
8839
|
+
function metaOf(schema) {
|
|
8840
|
+
const parsed = ParamMetaBagSchema.safeParse(zod.z.globalRegistry.get(schema));
|
|
8841
|
+
return parsed.success ? parsed.data : void 0;
|
|
8842
|
+
}
|
|
8843
|
+
/** The `paramMeta` on a method input field (or its unwrapped leaf), if any. */
|
|
8844
|
+
function paramMetaOf(field) {
|
|
8845
|
+
return metaOf(field) ?? metaOf(unwrapSchema(field));
|
|
8846
|
+
}
|
|
8847
|
+
//#endregion
|
|
8063
8848
|
//#region src/automation/recipe.ts
|
|
8064
8849
|
/**
|
|
8065
8850
|
* The automation RECIPE — what a visually-authored automation is, before it
|
|
@@ -8121,8 +8906,10 @@ var AutomationConditionLeafSchema = zod.z.object({
|
|
|
8121
8906
|
zod.z.string(),
|
|
8122
8907
|
zod.z.number(),
|
|
8123
8908
|
zod.z.boolean(),
|
|
8124
|
-
zod.z.array(zod.z.union([zod.z.string(), zod.z.number()]))
|
|
8125
|
-
|
|
8909
|
+
zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
|
|
8910
|
+
QuantityValueSchema
|
|
8911
|
+
]),
|
|
8912
|
+
unit: zod.z.string().min(1).optional()
|
|
8126
8913
|
});
|
|
8127
8914
|
var AutomationConditionStationaryPresentSchema = zod.z.object({
|
|
8128
8915
|
kind: zod.z.literal("stationary-present"),
|
|
@@ -8275,7 +9062,13 @@ var AutomationCapStepSchema = zod.z.object({
|
|
|
8275
9062
|
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
8276
9063
|
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
8277
9064
|
* the expression leaf. */
|
|
8278
|
-
args: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
9065
|
+
args: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
9066
|
+
/**
|
|
9067
|
+
* The unit the person picked per arg (display only). `args` always hold the
|
|
9068
|
+
* value in the unit the method's input DECLARES (`paramMeta`); on a write an
|
|
9069
|
+
* arg may be a {@link QuantityValue}, which the save boundary converts.
|
|
9070
|
+
*/
|
|
9071
|
+
argUnits: zod.z.record(zod.z.string(), zod.z.string().min(1)).optional()
|
|
8279
9072
|
});
|
|
8280
9073
|
/**
|
|
8281
9074
|
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
@@ -9855,6 +10648,14 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
9855
10648
|
expected: toTraceValue(cond.value),
|
|
9856
10649
|
observed: toTraceValue(observed)
|
|
9857
10650
|
};
|
|
10651
|
+
if (isQuantityValue(cond.value)) {
|
|
10652
|
+
out.push({
|
|
10653
|
+
...base,
|
|
10654
|
+
outcome: "fail",
|
|
10655
|
+
reason: `${cond.fieldPath} expected value's unit was never converted → fail`
|
|
10656
|
+
});
|
|
10657
|
+
return false;
|
|
10658
|
+
}
|
|
9858
10659
|
if (observed === void 0) {
|
|
9859
10660
|
out.push({
|
|
9860
10661
|
...base,
|
|
@@ -10705,6 +11506,242 @@ function buildActionSteps(steps, vars, deps) {
|
|
|
10705
11506
|
});
|
|
10706
11507
|
}
|
|
10707
11508
|
//#endregion
|
|
11509
|
+
//#region src/units/quantity-units.ts
|
|
11510
|
+
/**
|
|
11511
|
+
* Which units a person may pick for a number of a given quantity. The
|
|
11512
|
+
* quantity's stored base unit comes first (D944), then every other unit of the
|
|
11513
|
+
* same dimension in table order. A quantity with no unit (`none`, `count`,
|
|
11514
|
+
* `index`, `ratio`, `device`) offers nothing: the number is just a number.
|
|
11515
|
+
*/
|
|
11516
|
+
/** The unit a reading of `quantity` is stored in, or `undefined` when it has none. */
|
|
11517
|
+
function baseUnitForQuantity(quantity) {
|
|
11518
|
+
return require_sleep.baseUnitOf(quantity);
|
|
11519
|
+
}
|
|
11520
|
+
/**
|
|
11521
|
+
* The units a person can enter a number of `quantity` in — base unit first.
|
|
11522
|
+
* Empty when the quantity carries no unit.
|
|
11523
|
+
*/
|
|
11524
|
+
function unitsForQuantity(quantity) {
|
|
11525
|
+
const base = baseUnitForQuantity(quantity);
|
|
11526
|
+
if (base === void 0) return [];
|
|
11527
|
+
const dimension = unitDimension(base);
|
|
11528
|
+
if (dimension === void 0) return [base];
|
|
11529
|
+
return [base, ...unitsForDimension(dimension).filter((unit) => unit !== base)];
|
|
11530
|
+
}
|
|
11531
|
+
//#endregion
|
|
11532
|
+
//#region src/automation/normalize-units.ts
|
|
11533
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
11534
|
+
var UnitWarningSchema = zod.z.object({
|
|
11535
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
11536
|
+
path: zod.z.string(),
|
|
11537
|
+
deviceId: zod.z.number().int().optional(),
|
|
11538
|
+
field: zod.z.string(),
|
|
11539
|
+
value: zod.z.number(),
|
|
11540
|
+
assumedUnit: zod.z.string(),
|
|
11541
|
+
message: zod.z.string()
|
|
11542
|
+
});
|
|
11543
|
+
function objectInput(schema) {
|
|
11544
|
+
const inner = unwrapSchema(schema);
|
|
11545
|
+
return inner instanceof zod.z.ZodObject ? inner : void 0;
|
|
11546
|
+
}
|
|
11547
|
+
/** A resolver over capability definitions (the generated registry, or a fake). */
|
|
11548
|
+
function capUnitResolver(defs) {
|
|
11549
|
+
const byName = new Map(defs.map((def) => [def.name, def]));
|
|
11550
|
+
return {
|
|
11551
|
+
fieldQuantity(cap, fieldPath) {
|
|
11552
|
+
const status = byName.get(cap)?.status;
|
|
11553
|
+
if (status === void 0) return void 0;
|
|
11554
|
+
return [...enumerateSchemaFields(status.schema), ...status.itemArray === void 0 ? [] : enumerateItemArrayFields(status.itemArray)].find((f) => f.path === fieldPath)?.quantity;
|
|
11555
|
+
},
|
|
11556
|
+
paramMeta(cap, method, arg) {
|
|
11557
|
+
const input = byName.get(cap)?.methods[method]?.input;
|
|
11558
|
+
if (input === void 0) return void 0;
|
|
11559
|
+
const field = objectInput(input)?.shape[arg];
|
|
11560
|
+
return field === void 0 ? void 0 : paramMetaOf(field);
|
|
11561
|
+
}
|
|
11562
|
+
};
|
|
11563
|
+
}
|
|
11564
|
+
function pickedUnit(raw) {
|
|
11565
|
+
return normalizeUnit(raw) ?? raw;
|
|
11566
|
+
}
|
|
11567
|
+
function hasUnitChoice(quantity) {
|
|
11568
|
+
return unitsForQuantity(quantity).length > 1;
|
|
11569
|
+
}
|
|
11570
|
+
function bareWarning(input) {
|
|
11571
|
+
return {
|
|
11572
|
+
...input,
|
|
11573
|
+
message: `${input.field}: the bare number ${input.value} was taken as ${input.assumedUnit} — send { value, unit } to state the unit`
|
|
11574
|
+
};
|
|
11575
|
+
}
|
|
11576
|
+
function checkPickedUnit(unit, target) {
|
|
11577
|
+
convertQuantityInput({
|
|
11578
|
+
value: 1,
|
|
11579
|
+
unit,
|
|
11580
|
+
target
|
|
11581
|
+
});
|
|
11582
|
+
}
|
|
11583
|
+
function normalizeLeaf(leaf, resolver, path) {
|
|
11584
|
+
const quantity = resolver.fieldQuantity(leaf.cap, leaf.fieldPath);
|
|
11585
|
+
const base = quantity === void 0 ? void 0 : baseUnitForQuantity(quantity);
|
|
11586
|
+
const wantsUnit = isQuantityValue(leaf.value) || leaf.unit !== void 0;
|
|
11587
|
+
if (base === void 0 || quantity === void 0) {
|
|
11588
|
+
if (wantsUnit) throw new QuantityUnitError("no-quantity", `${leaf.cap}.${leaf.fieldPath} is not a quantity — it takes no unit`);
|
|
11589
|
+
return {
|
|
11590
|
+
leaf,
|
|
11591
|
+
warnings: []
|
|
11592
|
+
};
|
|
11593
|
+
}
|
|
11594
|
+
if (isQuantityValue(leaf.value)) {
|
|
11595
|
+
const converted = convertQuantityInput({
|
|
11596
|
+
value: leaf.value.value,
|
|
11597
|
+
unit: leaf.value.unit,
|
|
11598
|
+
target: base
|
|
11599
|
+
});
|
|
11600
|
+
return {
|
|
11601
|
+
leaf: {
|
|
11602
|
+
...leaf,
|
|
11603
|
+
value: converted,
|
|
11604
|
+
unit: pickedUnit(leaf.value.unit)
|
|
11605
|
+
},
|
|
11606
|
+
warnings: []
|
|
11607
|
+
};
|
|
11608
|
+
}
|
|
11609
|
+
if (typeof leaf.value !== "number") return {
|
|
11610
|
+
leaf,
|
|
11611
|
+
warnings: []
|
|
11612
|
+
};
|
|
11613
|
+
if (leaf.unit !== void 0) {
|
|
11614
|
+
checkPickedUnit(leaf.unit, base);
|
|
11615
|
+
return {
|
|
11616
|
+
leaf,
|
|
11617
|
+
warnings: []
|
|
11618
|
+
};
|
|
11619
|
+
}
|
|
11620
|
+
if (!hasUnitChoice(quantity)) return {
|
|
11621
|
+
leaf,
|
|
11622
|
+
warnings: []
|
|
11623
|
+
};
|
|
11624
|
+
return {
|
|
11625
|
+
leaf,
|
|
11626
|
+
warnings: [bareWarning({
|
|
11627
|
+
path,
|
|
11628
|
+
deviceId: leaf.deviceId,
|
|
11629
|
+
field: leaf.fieldPath,
|
|
11630
|
+
value: leaf.value,
|
|
11631
|
+
assumedUnit: base
|
|
11632
|
+
})]
|
|
11633
|
+
};
|
|
11634
|
+
}
|
|
11635
|
+
/** Normalise every leaf of a condition tree. Throws {@link QuantityUnitError}. */
|
|
11636
|
+
function normalizeConditionUnits(condition, resolver, path = "conditions") {
|
|
11637
|
+
switch (condition.kind) {
|
|
11638
|
+
case "condition": {
|
|
11639
|
+
const { leaf, warnings } = normalizeLeaf(condition, resolver, path);
|
|
11640
|
+
return {
|
|
11641
|
+
value: leaf,
|
|
11642
|
+
warnings
|
|
11643
|
+
};
|
|
11644
|
+
}
|
|
11645
|
+
case "not": {
|
|
11646
|
+
const inner = normalizeConditionUnits(condition.child, resolver, `${path}.child`);
|
|
11647
|
+
return {
|
|
11648
|
+
value: {
|
|
11649
|
+
...condition,
|
|
11650
|
+
child: inner.value
|
|
11651
|
+
},
|
|
11652
|
+
warnings: inner.warnings
|
|
11653
|
+
};
|
|
11654
|
+
}
|
|
11655
|
+
case "all":
|
|
11656
|
+
case "any": {
|
|
11657
|
+
const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${path}.children.${i}`));
|
|
11658
|
+
return {
|
|
11659
|
+
value: {
|
|
11660
|
+
...condition,
|
|
11661
|
+
children: children.map((c) => c.value)
|
|
11662
|
+
},
|
|
11663
|
+
warnings: children.flatMap((c) => c.warnings)
|
|
11664
|
+
};
|
|
11665
|
+
}
|
|
11666
|
+
default: return {
|
|
11667
|
+
value: condition,
|
|
11668
|
+
warnings: []
|
|
11669
|
+
};
|
|
11670
|
+
}
|
|
11671
|
+
}
|
|
11672
|
+
function normalizeCapStep(step, resolver, path) {
|
|
11673
|
+
const deviceId = typeof step.deviceId === "number" ? step.deviceId : void 0;
|
|
11674
|
+
const args = { ...step.args };
|
|
11675
|
+
const argUnits = { ...step.argUnits };
|
|
11676
|
+
const warnings = [];
|
|
11677
|
+
for (const [arg, picked] of Object.entries(step.argUnits ?? {})) {
|
|
11678
|
+
const meta = resolver.paramMeta(step.cap, step.method, arg);
|
|
11679
|
+
if (meta === void 0) throw new QuantityUnitError("no-quantity", `${step.cap}.${step.method}(${arg}) is not a quantity — it takes no unit`);
|
|
11680
|
+
checkPickedUnit(picked, meta.unit);
|
|
11681
|
+
}
|
|
11682
|
+
for (const [arg, given] of Object.entries(step.args ?? {})) {
|
|
11683
|
+
const meta = resolver.paramMeta(step.cap, step.method, arg);
|
|
11684
|
+
if (meta === void 0) continue;
|
|
11685
|
+
if (isQuantityValue(given)) {
|
|
11686
|
+
args[arg] = convertQuantityInput({
|
|
11687
|
+
value: given.value,
|
|
11688
|
+
unit: given.unit,
|
|
11689
|
+
target: meta.unit
|
|
11690
|
+
});
|
|
11691
|
+
argUnits[arg] = pickedUnit(given.unit);
|
|
11692
|
+
} else if (typeof given === "number" && argUnits[arg] === void 0 && hasUnitChoice(meta.quantity)) warnings.push(bareWarning({
|
|
11693
|
+
path: `${path}.args.${arg}`,
|
|
11694
|
+
...deviceId === void 0 ? {} : { deviceId },
|
|
11695
|
+
field: arg,
|
|
11696
|
+
value: given,
|
|
11697
|
+
assumedUnit: meta.unit
|
|
11698
|
+
}));
|
|
11699
|
+
}
|
|
11700
|
+
const { argUnits: _dropped, ...rest } = step;
|
|
11701
|
+
return {
|
|
11702
|
+
value: {
|
|
11703
|
+
...rest,
|
|
11704
|
+
...step.args === void 0 ? {} : { args },
|
|
11705
|
+
...Object.keys(argUnits).length > 0 ? { argUnits } : {}
|
|
11706
|
+
},
|
|
11707
|
+
warnings
|
|
11708
|
+
};
|
|
11709
|
+
}
|
|
11710
|
+
/**
|
|
11711
|
+
* Normalise the cap steps of an action list (an internal Action's steps, or a
|
|
11712
|
+
* recipe's actions). Other step kinds pass through. Throws {@link QuantityUnitError}.
|
|
11713
|
+
*/
|
|
11714
|
+
function normalizeActionUnits(actions, resolver, path = "steps") {
|
|
11715
|
+
const out = [];
|
|
11716
|
+
const warnings = [];
|
|
11717
|
+
actions.forEach((action, i) => {
|
|
11718
|
+
if (action.kind !== "cap") {
|
|
11719
|
+
out.push(action);
|
|
11720
|
+
return;
|
|
11721
|
+
}
|
|
11722
|
+
const normalized = normalizeCapStep(action, resolver, `${path}.${i}`);
|
|
11723
|
+
out.push(normalized.value);
|
|
11724
|
+
warnings.push(...normalized.warnings);
|
|
11725
|
+
});
|
|
11726
|
+
return {
|
|
11727
|
+
value: out,
|
|
11728
|
+
warnings
|
|
11729
|
+
};
|
|
11730
|
+
}
|
|
11731
|
+
/** Normalise a whole recipe: its condition tree and its actions. */
|
|
11732
|
+
function normalizeRecipeUnits(recipe, resolver) {
|
|
11733
|
+
const conditions = recipe.conditions === void 0 ? void 0 : normalizeConditionUnits(recipe.conditions, resolver, "conditions");
|
|
11734
|
+
const actions = normalizeActionUnits(recipe.actions, resolver, "actions");
|
|
11735
|
+
return {
|
|
11736
|
+
value: {
|
|
11737
|
+
...recipe,
|
|
11738
|
+
...conditions === void 0 ? {} : { conditions: conditions.value },
|
|
11739
|
+
actions: actions.value
|
|
11740
|
+
},
|
|
11741
|
+
warnings: [...conditions?.warnings ?? [], ...actions.warnings]
|
|
11742
|
+
};
|
|
11743
|
+
}
|
|
11744
|
+
//#endregion
|
|
10708
11745
|
//#region src/capabilities/notification-output.cap.ts
|
|
10709
11746
|
/**
|
|
10710
11747
|
* notification-output — canonical, capability-gated notification delivery.
|
|
@@ -15199,77 +16236,6 @@ function kebabToCamel(s) {
|
|
|
15199
16236
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
15200
16237
|
}
|
|
15201
16238
|
//#endregion
|
|
15202
|
-
//#region src/device/schema-fields.ts
|
|
15203
|
-
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
15204
|
-
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
15205
|
-
function unwrapSchema(schema) {
|
|
15206
|
-
let s = schema;
|
|
15207
|
-
while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
|
|
15208
|
-
return s;
|
|
15209
|
-
}
|
|
15210
|
-
function isMetaBag(value) {
|
|
15211
|
-
return typeof value === "object" && value !== null;
|
|
15212
|
-
}
|
|
15213
|
-
function metaQuantity(schema) {
|
|
15214
|
-
const bag = zod.z.globalRegistry.get(schema);
|
|
15215
|
-
if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
|
|
15216
|
-
}
|
|
15217
|
-
/** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
|
|
15218
|
-
function quantityOfField(schema) {
|
|
15219
|
-
return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
|
|
15220
|
-
}
|
|
15221
|
-
function leafKind(schema) {
|
|
15222
|
-
const s = unwrapSchema(schema);
|
|
15223
|
-
if (s instanceof zod.z.ZodEnum) return {
|
|
15224
|
-
kind: "enum",
|
|
15225
|
-
enumValues: s.options
|
|
15226
|
-
};
|
|
15227
|
-
if (s instanceof zod.z.ZodNumber) return { kind: "number" };
|
|
15228
|
-
if (s instanceof zod.z.ZodString) return { kind: "string" };
|
|
15229
|
-
if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
|
|
15230
|
-
return null;
|
|
15231
|
-
}
|
|
15232
|
-
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
15233
|
-
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
15234
|
-
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
15235
|
-
* are skipped — never throws. */
|
|
15236
|
-
function enumerateSchemaFields(schema, prefix = "") {
|
|
15237
|
-
const root = unwrapSchema(schema);
|
|
15238
|
-
if (!(root instanceof zod.z.ZodObject)) return [];
|
|
15239
|
-
const out = [];
|
|
15240
|
-
const shape = root.shape;
|
|
15241
|
-
for (const [key, field] of Object.entries(shape)) {
|
|
15242
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
15243
|
-
const inner = unwrapSchema(field);
|
|
15244
|
-
if (inner instanceof zod.z.ZodObject) {
|
|
15245
|
-
out.push(...enumerateSchemaFields(inner, path));
|
|
15246
|
-
continue;
|
|
15247
|
-
}
|
|
15248
|
-
const leaf = leafKind(field);
|
|
15249
|
-
if (leaf) {
|
|
15250
|
-
const quantity = quantityOfField(field);
|
|
15251
|
-
out.push({
|
|
15252
|
-
path,
|
|
15253
|
-
kind: leaf.kind,
|
|
15254
|
-
...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
|
|
15255
|
-
...quantity !== void 0 ? { quantity } : {}
|
|
15256
|
-
});
|
|
15257
|
-
}
|
|
15258
|
-
}
|
|
15259
|
-
return out;
|
|
15260
|
-
}
|
|
15261
|
-
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
15262
|
-
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
15263
|
-
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
15264
|
-
* source), each tagged `item: true` so the authoring UI collects an
|
|
15265
|
-
* `itemKey` alongside the field. Never throws. */
|
|
15266
|
-
function enumerateItemArrayFields(itemArray) {
|
|
15267
|
-
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
15268
|
-
...f,
|
|
15269
|
-
item: true
|
|
15270
|
-
}));
|
|
15271
|
-
}
|
|
15272
|
-
//#endregion
|
|
15273
16239
|
//#region src/composition/state-fields.ts
|
|
15274
16240
|
/**
|
|
15275
16241
|
* What a capability's state field IS: kind, nullability, whether required. The
|
|
@@ -21163,192 +22129,6 @@ function parseAdminBase(baseUrl) {
|
|
|
21163
22129
|
};
|
|
21164
22130
|
}
|
|
21165
22131
|
//#endregion
|
|
21166
|
-
//#region src/device/source-info.ts
|
|
21167
|
-
/**
|
|
21168
|
-
* Identity envelope for a device's upstream-system metadata.
|
|
21169
|
-
*
|
|
21170
|
-
* Two jobs:
|
|
21171
|
-
*
|
|
21172
|
-
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
21173
|
-
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
21174
|
-
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
21175
|
-
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
21176
|
-
* without round-tripping through `stableId` / `id` mappings.
|
|
21177
|
-
*
|
|
21178
|
-
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
21179
|
-
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
21180
|
-
* for vendors where MAC is the natural key). For systems where the
|
|
21181
|
-
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
21182
|
-
* providers detect renames by matching `uniqueId` and patch `id`
|
|
21183
|
-
* in-place so dispatch keeps working.
|
|
21184
|
-
*
|
|
21185
|
-
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
21186
|
-
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
21187
|
-
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
21188
|
-
* neither is repeated on each sample. The `device_class` tag is an
|
|
21189
|
-
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
21190
|
-
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
21191
|
-
*
|
|
21192
|
-
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
21193
|
-
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
21194
|
-
* details, …). Providers control what lands here.
|
|
21195
|
-
*/
|
|
21196
|
-
var SourceInfoSchema = zod.z.object({
|
|
21197
|
-
/** Live dispatch key — mutable when the source system allows rename. */
|
|
21198
|
-
id: zod.z.string(),
|
|
21199
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
21200
|
-
system: zod.z.string(),
|
|
21201
|
-
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
21202
|
-
* Used to detect rename when `id` changes. */
|
|
21203
|
-
uniqueId: zod.z.string().optional(),
|
|
21204
|
-
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
21205
|
-
quantity: require_sleep.ReadingQuantitySchema.optional(),
|
|
21206
|
-
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
21207
|
-
dateTimeShape: zod.z.enum([
|
|
21208
|
-
"date",
|
|
21209
|
-
"time",
|
|
21210
|
-
"datetime"
|
|
21211
|
-
]).optional(),
|
|
21212
|
-
/** Provider-specific extras that don't fit the structured slots.
|
|
21213
|
-
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
21214
|
-
* attribute blobs (would explode the meta JSON column). */
|
|
21215
|
-
raw: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
21216
|
-
});
|
|
21217
|
-
/**
|
|
21218
|
-
* Build the synthetic SourceInfo every existing provider falls back to
|
|
21219
|
-
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
21220
|
-
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
21221
|
-
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
21222
|
-
* id. Real upstream identity replaces this once a provider migrates and
|
|
21223
|
-
* calls `updateSourceInfo()` with concrete values.
|
|
21224
|
-
*/
|
|
21225
|
-
function synthesizeSourceInfo(input) {
|
|
21226
|
-
return {
|
|
21227
|
-
id: input.stableId,
|
|
21228
|
-
system: input.addonId
|
|
21229
|
-
};
|
|
21230
|
-
}
|
|
21231
|
-
/**
|
|
21232
|
-
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
21233
|
-
* patch values are ignored (no field clobber); to clear a field
|
|
21234
|
-
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
21235
|
-
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
21236
|
-
* bother with a `null`-means-delete sentinel.
|
|
21237
|
-
*
|
|
21238
|
-
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
21239
|
-
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
21240
|
-
*/
|
|
21241
|
-
function mergeSourceInfo(prev, patch) {
|
|
21242
|
-
const next = { ...prev };
|
|
21243
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
21244
|
-
if (v === void 0) continue;
|
|
21245
|
-
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
21246
|
-
next.raw = {
|
|
21247
|
-
...prev.raw,
|
|
21248
|
-
...v
|
|
21249
|
-
};
|
|
21250
|
-
continue;
|
|
21251
|
-
}
|
|
21252
|
-
next[k] = v;
|
|
21253
|
-
}
|
|
21254
|
-
return next;
|
|
21255
|
-
}
|
|
21256
|
-
/**
|
|
21257
|
-
* Coerce common unit-of-measurement string variants to a canonical form
|
|
21258
|
-
* so the UI's display templates can rely on stable strings. Pure
|
|
21259
|
-
* passthrough for unknown units — kept conservative on purpose, the goal
|
|
21260
|
-
* is to normalise the long tail of HA `unit_of_measurement` values that
|
|
21261
|
-
* have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
|
|
21262
|
-
*
|
|
21263
|
-
* Extend the map as we encounter real-world data. Returning the raw
|
|
21264
|
-
* input for unknown values is intentional: providers should never
|
|
21265
|
-
* lose information silently here.
|
|
21266
|
-
*/
|
|
21267
|
-
function normalizeUnit(raw) {
|
|
21268
|
-
if (raw == null) return void 0;
|
|
21269
|
-
const trimmed = raw.trim();
|
|
21270
|
-
if (trimmed === "") return void 0;
|
|
21271
|
-
const lower = trimmed.toLowerCase();
|
|
21272
|
-
if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
|
|
21273
|
-
if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
|
|
21274
|
-
if (lower === "k" || lower === "kelvin") return "K";
|
|
21275
|
-
if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
|
|
21276
|
-
if (lower === "lx" || lower === "lux") return "lx";
|
|
21277
|
-
if (lower === "hpa" || lower === "hectopascal") return "hPa";
|
|
21278
|
-
if (lower === "kpa") return "kPa";
|
|
21279
|
-
if (lower === "pa") return "Pa";
|
|
21280
|
-
if (lower === "mbar" || lower === "millibar") return "mbar";
|
|
21281
|
-
if (lower === "w" || lower === "watt") return "W";
|
|
21282
|
-
if (lower === "kw" || lower === "kilowatt") return "kW";
|
|
21283
|
-
if (lower === "wh" || lower === "watt-hour") return "Wh";
|
|
21284
|
-
if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
|
|
21285
|
-
if (lower === "v" || lower === "volt") return "V";
|
|
21286
|
-
if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
|
|
21287
|
-
if (lower === "ma" || lower === "milliampere") return "mA";
|
|
21288
|
-
if (lower === "va") return "VA";
|
|
21289
|
-
if (lower === "ppm") return "ppm";
|
|
21290
|
-
if (lower === "ppb") return "ppb";
|
|
21291
|
-
if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
|
|
21292
|
-
if (lower === "km/h" || lower === "kph") return "km/h";
|
|
21293
|
-
if (lower === "mph") return "mph";
|
|
21294
|
-
if (lower === "m/s") return "m/s";
|
|
21295
|
-
if (lower === "m") return "m";
|
|
21296
|
-
if (lower === "m²" || lower === "m2") return "m²";
|
|
21297
|
-
if (lower === "mm") return "mm";
|
|
21298
|
-
if (lower === "cm") return "cm";
|
|
21299
|
-
if (lower === "km") return "km";
|
|
21300
|
-
if (lower === "in" || lower === "inch" || lower === "inches") return "in";
|
|
21301
|
-
if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
|
|
21302
|
-
if (lower === "kg") return "kg";
|
|
21303
|
-
if (lower === "g") return "g";
|
|
21304
|
-
if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
|
|
21305
|
-
if (lower === "ml") return "mL";
|
|
21306
|
-
if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
|
|
21307
|
-
if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
|
|
21308
|
-
if (lower === "ms") return "ms";
|
|
21309
|
-
if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
|
|
21310
|
-
if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
|
|
21311
|
-
if (lower === "d" || lower === "day" || lower === "days") return "d";
|
|
21312
|
-
if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
|
|
21313
|
-
if (lower === "kib") return "KiB";
|
|
21314
|
-
if (lower === "mib") return "MiB";
|
|
21315
|
-
if (lower === "gib") return "GiB";
|
|
21316
|
-
if (lower === "tib") return "TiB";
|
|
21317
|
-
if (lower === "kb") return "kB";
|
|
21318
|
-
if (lower === "mb") return "MB";
|
|
21319
|
-
if (lower === "gb") return "GB";
|
|
21320
|
-
if (lower === "dbm") return "dBm";
|
|
21321
|
-
if (lower === "dbfs") return "dBFS";
|
|
21322
|
-
if (lower === "inhg") return "inHg";
|
|
21323
|
-
if (lower === "mm/h") return "mm/h";
|
|
21324
|
-
if (lower === "w/m²" || lower === "w/m2") return "W/m²";
|
|
21325
|
-
if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
|
|
21326
|
-
if (lower === "kbps") return "kbps";
|
|
21327
|
-
if (lower === "fps") return "fps";
|
|
21328
|
-
return trimmed;
|
|
21329
|
-
}
|
|
21330
|
-
/**
|
|
21331
|
-
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
21332
|
-
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
21333
|
-
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
21334
|
-
* lives under a single nested key to keep it composable with those.
|
|
21335
|
-
*/
|
|
21336
|
-
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
21337
|
-
/**
|
|
21338
|
-
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
21339
|
-
* with Zod. Returns `null` when the blob is missing or the nested
|
|
21340
|
-
* `sourceInfo` key fails validation — callers fall back to the
|
|
21341
|
-
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
21342
|
-
*/
|
|
21343
|
-
function extractSourceInfoFromMetadata(metadata) {
|
|
21344
|
-
if (!metadata) return null;
|
|
21345
|
-
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
21346
|
-
if (!raw) return null;
|
|
21347
|
-
const parsed = SourceInfoSchema.safeParse(raw);
|
|
21348
|
-
if (!parsed.success) return null;
|
|
21349
|
-
return parsed.data;
|
|
21350
|
-
}
|
|
21351
|
-
//#endregion
|
|
21352
22132
|
//#region src/capabilities/device-discovery.cap.ts
|
|
21353
22133
|
/**
|
|
21354
22134
|
* device-discovery — device-scoped capability for parents that host /
|
|
@@ -65158,15 +65938,29 @@ var actionsCapability = {
|
|
|
65158
65938
|
auth: "admin"
|
|
65159
65939
|
}),
|
|
65160
65940
|
getTraces: require_sleep.method(ActionIdInput, zod.z.object({ traces: zod.z.array(AutomationRunTraceSchema) }), { auth: "admin" }),
|
|
65161
|
-
/**
|
|
65162
|
-
|
|
65941
|
+
/**
|
|
65942
|
+
* Internal actions only; a read-only (addon) entry is refused with `[read-only]`.
|
|
65943
|
+
* Create an internal action. A cap step arg that is a physical quantity may
|
|
65944
|
+
* be sent as `{ value, unit }` ("value expressed in unit"); the server
|
|
65945
|
+
* converts it to the unit the method's input declares, stores the bare
|
|
65946
|
+
* number plus the picked unit in `argUnits`, and refuses a unit of another
|
|
65947
|
+
* dimension (`[invalid-steps]`). A bare number is taken as the declared
|
|
65948
|
+
* unit and answered as a `warnings` entry (D972).
|
|
65949
|
+
*/
|
|
65950
|
+
create: require_sleep.method(zod.z.object({ action: ActionInputSchema }), zod.z.object({
|
|
65951
|
+
action: ActionEntrySchema,
|
|
65952
|
+
warnings: zod.z.array(UnitWarningSchema).optional()
|
|
65953
|
+
}), {
|
|
65163
65954
|
kind: "mutation",
|
|
65164
65955
|
auth: "admin"
|
|
65165
65956
|
}),
|
|
65166
65957
|
update: require_sleep.method(zod.z.object({
|
|
65167
65958
|
actionId: zod.z.string().min(1),
|
|
65168
65959
|
action: ActionInputSchema.partial()
|
|
65169
|
-
}), zod.z.object({
|
|
65960
|
+
}), zod.z.object({
|
|
65961
|
+
action: ActionEntrySchema,
|
|
65962
|
+
warnings: zod.z.array(UnitWarningSchema).optional()
|
|
65963
|
+
}), {
|
|
65170
65964
|
kind: "mutation",
|
|
65171
65965
|
auth: "admin"
|
|
65172
65966
|
}),
|
|
@@ -73343,6 +74137,14 @@ var PetFeederDeviceStatusSchema = zod.z.enum([
|
|
|
73343
74137
|
"on_batteries"
|
|
73344
74138
|
]);
|
|
73345
74139
|
var gramsPortion = zod.z.number().int().min(4).max(200);
|
|
74140
|
+
/** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
|
|
74141
|
+
* hardware's own unit). The bounds are grams; a caller entering another unit
|
|
74142
|
+
* converts to grams first (`paramMeta`, D972). The STATUS fields above stay
|
|
74143
|
+
* plain grams — they are not readings in the kilogram base. */
|
|
74144
|
+
var gramsParam = gramsPortion.meta(paramMeta({
|
|
74145
|
+
quantity: "mass",
|
|
74146
|
+
unit: "g"
|
|
74147
|
+
}));
|
|
73346
74148
|
var PetFeederStatusSchema = zod.z.object({
|
|
73347
74149
|
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
73348
74150
|
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
@@ -73431,9 +74233,9 @@ var petFeederCapability = {
|
|
|
73431
74233
|
*/
|
|
73432
74234
|
feed: require_sleep.method(zod.z.object({
|
|
73433
74235
|
deviceId: zod.z.number().int().nonnegative(),
|
|
73434
|
-
grams:
|
|
73435
|
-
hopper1:
|
|
73436
|
-
hopper2:
|
|
74236
|
+
grams: gramsParam.optional(),
|
|
74237
|
+
hopper1: gramsParam.optional(),
|
|
74238
|
+
hopper2: gramsParam.optional()
|
|
73437
74239
|
}), zod.z.void(), {
|
|
73438
74240
|
kind: "mutation",
|
|
73439
74241
|
auth: "protected"
|
|
@@ -82891,465 +83693,6 @@ function numericAccept() {
|
|
|
82891
83693
|
return historyAccept("statistic").filter((field) => field.valueKind === "number");
|
|
82892
83694
|
}
|
|
82893
83695
|
//#endregion
|
|
82894
|
-
//#region src/units/unit-table.ts
|
|
82895
|
-
/**
|
|
82896
|
-
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
82897
|
-
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
82898
|
-
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
82899
|
-
* decimals). K derives from `°C = K − 273.15`.
|
|
82900
|
-
*/
|
|
82901
|
-
var UNIT_TABLE = {
|
|
82902
|
-
"°C": {
|
|
82903
|
-
dimension: "temperature",
|
|
82904
|
-
factor: 1,
|
|
82905
|
-
offset: 0
|
|
82906
|
-
},
|
|
82907
|
-
"°F": {
|
|
82908
|
-
dimension: "temperature",
|
|
82909
|
-
factor: 5 / 9,
|
|
82910
|
-
offset: -160 / 9
|
|
82911
|
-
},
|
|
82912
|
-
K: {
|
|
82913
|
-
dimension: "temperature",
|
|
82914
|
-
factor: 1,
|
|
82915
|
-
offset: -273.15
|
|
82916
|
-
},
|
|
82917
|
-
hPa: {
|
|
82918
|
-
dimension: "pressure",
|
|
82919
|
-
factor: 1,
|
|
82920
|
-
offset: 0
|
|
82921
|
-
},
|
|
82922
|
-
kPa: {
|
|
82923
|
-
dimension: "pressure",
|
|
82924
|
-
factor: 10,
|
|
82925
|
-
offset: 0
|
|
82926
|
-
},
|
|
82927
|
-
Pa: {
|
|
82928
|
-
dimension: "pressure",
|
|
82929
|
-
factor: .01,
|
|
82930
|
-
offset: 0
|
|
82931
|
-
},
|
|
82932
|
-
mbar: {
|
|
82933
|
-
dimension: "pressure",
|
|
82934
|
-
factor: 1,
|
|
82935
|
-
offset: 0
|
|
82936
|
-
},
|
|
82937
|
-
bar: {
|
|
82938
|
-
dimension: "pressure",
|
|
82939
|
-
factor: 1e3,
|
|
82940
|
-
offset: 0
|
|
82941
|
-
},
|
|
82942
|
-
inHg: {
|
|
82943
|
-
dimension: "pressure",
|
|
82944
|
-
factor: 33.8639,
|
|
82945
|
-
offset: 0
|
|
82946
|
-
},
|
|
82947
|
-
mmHg: {
|
|
82948
|
-
dimension: "pressure",
|
|
82949
|
-
factor: 1.33322,
|
|
82950
|
-
offset: 0
|
|
82951
|
-
},
|
|
82952
|
-
psi: {
|
|
82953
|
-
dimension: "pressure",
|
|
82954
|
-
factor: 68.9476,
|
|
82955
|
-
offset: 0
|
|
82956
|
-
},
|
|
82957
|
-
"m/s": {
|
|
82958
|
-
dimension: "speed",
|
|
82959
|
-
factor: 1,
|
|
82960
|
-
offset: 0
|
|
82961
|
-
},
|
|
82962
|
-
"km/h": {
|
|
82963
|
-
dimension: "speed",
|
|
82964
|
-
factor: 1 / 3.6,
|
|
82965
|
-
offset: 0
|
|
82966
|
-
},
|
|
82967
|
-
mph: {
|
|
82968
|
-
dimension: "speed",
|
|
82969
|
-
factor: .44704,
|
|
82970
|
-
offset: 0
|
|
82971
|
-
},
|
|
82972
|
-
kn: {
|
|
82973
|
-
dimension: "speed",
|
|
82974
|
-
factor: .514444,
|
|
82975
|
-
offset: 0
|
|
82976
|
-
},
|
|
82977
|
-
"mm/h": {
|
|
82978
|
-
dimension: "precipitation-rate",
|
|
82979
|
-
factor: 1,
|
|
82980
|
-
offset: 0
|
|
82981
|
-
},
|
|
82982
|
-
"in/h": {
|
|
82983
|
-
dimension: "precipitation-rate",
|
|
82984
|
-
factor: 25.4,
|
|
82985
|
-
offset: 0
|
|
82986
|
-
},
|
|
82987
|
-
m: {
|
|
82988
|
-
dimension: "length",
|
|
82989
|
-
factor: 1,
|
|
82990
|
-
offset: 0
|
|
82991
|
-
},
|
|
82992
|
-
mm: {
|
|
82993
|
-
dimension: "length",
|
|
82994
|
-
factor: .001,
|
|
82995
|
-
offset: 0
|
|
82996
|
-
},
|
|
82997
|
-
cm: {
|
|
82998
|
-
dimension: "length",
|
|
82999
|
-
factor: .01,
|
|
83000
|
-
offset: 0
|
|
83001
|
-
},
|
|
83002
|
-
km: {
|
|
83003
|
-
dimension: "length",
|
|
83004
|
-
factor: 1e3,
|
|
83005
|
-
offset: 0
|
|
83006
|
-
},
|
|
83007
|
-
in: {
|
|
83008
|
-
dimension: "length",
|
|
83009
|
-
factor: .0254,
|
|
83010
|
-
offset: 0
|
|
83011
|
-
},
|
|
83012
|
-
ft: {
|
|
83013
|
-
dimension: "length",
|
|
83014
|
-
factor: .3048,
|
|
83015
|
-
offset: 0
|
|
83016
|
-
},
|
|
83017
|
-
mi: {
|
|
83018
|
-
dimension: "length",
|
|
83019
|
-
factor: 1609.344,
|
|
83020
|
-
offset: 0
|
|
83021
|
-
},
|
|
83022
|
-
"m²": {
|
|
83023
|
-
dimension: "area",
|
|
83024
|
-
factor: 1,
|
|
83025
|
-
offset: 0
|
|
83026
|
-
},
|
|
83027
|
-
lx: {
|
|
83028
|
-
dimension: "illuminance",
|
|
83029
|
-
factor: 1,
|
|
83030
|
-
offset: 0
|
|
83031
|
-
},
|
|
83032
|
-
"W/m²": {
|
|
83033
|
-
dimension: "irradiance",
|
|
83034
|
-
factor: 1,
|
|
83035
|
-
offset: 0
|
|
83036
|
-
},
|
|
83037
|
-
W: {
|
|
83038
|
-
dimension: "power",
|
|
83039
|
-
factor: 1,
|
|
83040
|
-
offset: 0
|
|
83041
|
-
},
|
|
83042
|
-
kW: {
|
|
83043
|
-
dimension: "power",
|
|
83044
|
-
factor: 1e3,
|
|
83045
|
-
offset: 0
|
|
83046
|
-
},
|
|
83047
|
-
VA: {
|
|
83048
|
-
dimension: "apparent-power",
|
|
83049
|
-
factor: 1,
|
|
83050
|
-
offset: 0
|
|
83051
|
-
},
|
|
83052
|
-
Wh: {
|
|
83053
|
-
dimension: "energy",
|
|
83054
|
-
factor: 1,
|
|
83055
|
-
offset: 0
|
|
83056
|
-
},
|
|
83057
|
-
kWh: {
|
|
83058
|
-
dimension: "energy",
|
|
83059
|
-
factor: 1e3,
|
|
83060
|
-
offset: 0
|
|
83061
|
-
},
|
|
83062
|
-
MWh: {
|
|
83063
|
-
dimension: "energy",
|
|
83064
|
-
factor: 1e6,
|
|
83065
|
-
offset: 0
|
|
83066
|
-
},
|
|
83067
|
-
V: {
|
|
83068
|
-
dimension: "voltage",
|
|
83069
|
-
factor: 1,
|
|
83070
|
-
offset: 0
|
|
83071
|
-
},
|
|
83072
|
-
mV: {
|
|
83073
|
-
dimension: "voltage",
|
|
83074
|
-
factor: .001,
|
|
83075
|
-
offset: 0
|
|
83076
|
-
},
|
|
83077
|
-
A: {
|
|
83078
|
-
dimension: "current",
|
|
83079
|
-
factor: 1,
|
|
83080
|
-
offset: 0
|
|
83081
|
-
},
|
|
83082
|
-
mA: {
|
|
83083
|
-
dimension: "current",
|
|
83084
|
-
factor: .001,
|
|
83085
|
-
offset: 0
|
|
83086
|
-
},
|
|
83087
|
-
ppm: {
|
|
83088
|
-
dimension: "concentration-volume",
|
|
83089
|
-
factor: 1,
|
|
83090
|
-
offset: 0
|
|
83091
|
-
},
|
|
83092
|
-
ppb: {
|
|
83093
|
-
dimension: "concentration-volume",
|
|
83094
|
-
factor: .001,
|
|
83095
|
-
offset: 0
|
|
83096
|
-
},
|
|
83097
|
-
"µg/m³": {
|
|
83098
|
-
dimension: "concentration-mass",
|
|
83099
|
-
factor: 1,
|
|
83100
|
-
offset: 0
|
|
83101
|
-
},
|
|
83102
|
-
"mg/m³": {
|
|
83103
|
-
dimension: "concentration-mass",
|
|
83104
|
-
factor: 1e3,
|
|
83105
|
-
offset: 0
|
|
83106
|
-
},
|
|
83107
|
-
kg: {
|
|
83108
|
-
dimension: "mass",
|
|
83109
|
-
factor: 1,
|
|
83110
|
-
offset: 0
|
|
83111
|
-
},
|
|
83112
|
-
g: {
|
|
83113
|
-
dimension: "mass",
|
|
83114
|
-
factor: .001,
|
|
83115
|
-
offset: 0
|
|
83116
|
-
},
|
|
83117
|
-
lb: {
|
|
83118
|
-
dimension: "mass",
|
|
83119
|
-
factor: .453592,
|
|
83120
|
-
offset: 0
|
|
83121
|
-
},
|
|
83122
|
-
oz: {
|
|
83123
|
-
dimension: "mass",
|
|
83124
|
-
factor: .0283495,
|
|
83125
|
-
offset: 0
|
|
83126
|
-
},
|
|
83127
|
-
"%": {
|
|
83128
|
-
dimension: "percentage",
|
|
83129
|
-
factor: 1,
|
|
83130
|
-
offset: 0
|
|
83131
|
-
},
|
|
83132
|
-
L: {
|
|
83133
|
-
dimension: "volume",
|
|
83134
|
-
factor: 1,
|
|
83135
|
-
offset: 0
|
|
83136
|
-
},
|
|
83137
|
-
mL: {
|
|
83138
|
-
dimension: "volume",
|
|
83139
|
-
factor: .001,
|
|
83140
|
-
offset: 0
|
|
83141
|
-
},
|
|
83142
|
-
gal: {
|
|
83143
|
-
dimension: "volume",
|
|
83144
|
-
factor: 3.785411784,
|
|
83145
|
-
offset: 0
|
|
83146
|
-
},
|
|
83147
|
-
s: {
|
|
83148
|
-
dimension: "duration",
|
|
83149
|
-
factor: 1,
|
|
83150
|
-
offset: 0
|
|
83151
|
-
},
|
|
83152
|
-
ms: {
|
|
83153
|
-
dimension: "duration",
|
|
83154
|
-
factor: .001,
|
|
83155
|
-
offset: 0
|
|
83156
|
-
},
|
|
83157
|
-
min: {
|
|
83158
|
-
dimension: "duration",
|
|
83159
|
-
factor: 60,
|
|
83160
|
-
offset: 0
|
|
83161
|
-
},
|
|
83162
|
-
h: {
|
|
83163
|
-
dimension: "duration",
|
|
83164
|
-
factor: 3600,
|
|
83165
|
-
offset: 0
|
|
83166
|
-
},
|
|
83167
|
-
d: {
|
|
83168
|
-
dimension: "duration",
|
|
83169
|
-
factor: 86400,
|
|
83170
|
-
offset: 0
|
|
83171
|
-
},
|
|
83172
|
-
B: {
|
|
83173
|
-
dimension: "information",
|
|
83174
|
-
factor: 1,
|
|
83175
|
-
offset: 0
|
|
83176
|
-
},
|
|
83177
|
-
KiB: {
|
|
83178
|
-
dimension: "information",
|
|
83179
|
-
factor: 1024,
|
|
83180
|
-
offset: 0
|
|
83181
|
-
},
|
|
83182
|
-
MiB: {
|
|
83183
|
-
dimension: "information",
|
|
83184
|
-
factor: 1024 ** 2,
|
|
83185
|
-
offset: 0
|
|
83186
|
-
},
|
|
83187
|
-
GiB: {
|
|
83188
|
-
dimension: "information",
|
|
83189
|
-
factor: 1024 ** 3,
|
|
83190
|
-
offset: 0
|
|
83191
|
-
},
|
|
83192
|
-
TiB: {
|
|
83193
|
-
dimension: "information",
|
|
83194
|
-
factor: 1024 ** 4,
|
|
83195
|
-
offset: 0
|
|
83196
|
-
},
|
|
83197
|
-
kB: {
|
|
83198
|
-
dimension: "information",
|
|
83199
|
-
factor: 1e3,
|
|
83200
|
-
offset: 0
|
|
83201
|
-
},
|
|
83202
|
-
MB: {
|
|
83203
|
-
dimension: "information",
|
|
83204
|
-
factor: 1e6,
|
|
83205
|
-
offset: 0
|
|
83206
|
-
},
|
|
83207
|
-
GB: {
|
|
83208
|
-
dimension: "information",
|
|
83209
|
-
factor: 1e9,
|
|
83210
|
-
offset: 0
|
|
83211
|
-
},
|
|
83212
|
-
"°": {
|
|
83213
|
-
dimension: "angle",
|
|
83214
|
-
factor: 1,
|
|
83215
|
-
offset: 0
|
|
83216
|
-
},
|
|
83217
|
-
dBFS: {
|
|
83218
|
-
dimension: "sound-level",
|
|
83219
|
-
factor: 1,
|
|
83220
|
-
offset: 0
|
|
83221
|
-
},
|
|
83222
|
-
dBm: {
|
|
83223
|
-
dimension: "signal-level",
|
|
83224
|
-
factor: 1,
|
|
83225
|
-
offset: 0
|
|
83226
|
-
},
|
|
83227
|
-
mired: {
|
|
83228
|
-
dimension: "colour-temperature",
|
|
83229
|
-
factor: 1,
|
|
83230
|
-
offset: 0
|
|
83231
|
-
},
|
|
83232
|
-
kbps: {
|
|
83233
|
-
dimension: "bitrate",
|
|
83234
|
-
factor: 1,
|
|
83235
|
-
offset: 0
|
|
83236
|
-
},
|
|
83237
|
-
Mbps: {
|
|
83238
|
-
dimension: "bitrate",
|
|
83239
|
-
factor: 1e3,
|
|
83240
|
-
offset: 0
|
|
83241
|
-
},
|
|
83242
|
-
fps: {
|
|
83243
|
-
dimension: "rate",
|
|
83244
|
-
factor: 1,
|
|
83245
|
-
offset: 0
|
|
83246
|
-
}
|
|
83247
|
-
};
|
|
83248
|
-
//#endregion
|
|
83249
|
-
//#region src/units/convert.ts
|
|
83250
|
-
/**
|
|
83251
|
-
* Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
|
|
83252
|
-
*
|
|
83253
|
-
* Conversion is refused (throws `UnitConversionError`, or returns `undefined`
|
|
83254
|
-
* from the `try*` variant) whenever a unit is unknown or the two units belong
|
|
83255
|
-
* to different dimensions — cross-dimension conversion is deliberately
|
|
83256
|
-
* impossible so a render override can never make the value and its unit label
|
|
83257
|
-
* disagree.
|
|
83258
|
-
*
|
|
83259
|
-
* Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
|
|
83260
|
-
* calling here — the table keys only canonical spellings.
|
|
83261
|
-
*/
|
|
83262
|
-
/** Thrown by `convertUnit` when a conversion is refused. Carries the offending
|
|
83263
|
-
* units + a machine-readable `reason` for callers that branch on the cause. */
|
|
83264
|
-
var UnitConversionError = class extends Error {
|
|
83265
|
-
from;
|
|
83266
|
-
to;
|
|
83267
|
-
reason;
|
|
83268
|
-
constructor(from, to, reason) {
|
|
83269
|
-
super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
|
|
83270
|
-
this.name = "UnitConversionError";
|
|
83271
|
-
this.from = from;
|
|
83272
|
-
this.to = to;
|
|
83273
|
-
this.reason = reason;
|
|
83274
|
-
}
|
|
83275
|
-
};
|
|
83276
|
-
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
83277
|
-
function specFor(unit) {
|
|
83278
|
-
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
83279
|
-
}
|
|
83280
|
-
/** The dimension a unit belongs to, or `undefined` for an unknown unit. */
|
|
83281
|
-
function unitDimension(unit) {
|
|
83282
|
-
return specFor(unit)?.dimension;
|
|
83283
|
-
}
|
|
83284
|
-
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
83285
|
-
function toCanonical(value, spec) {
|
|
83286
|
-
return value * spec.factor + spec.offset;
|
|
83287
|
-
}
|
|
83288
|
-
/** Canonical-unit value → the unit described by `spec`. */
|
|
83289
|
-
function fromCanonical(canonical, spec) {
|
|
83290
|
-
return (canonical - spec.offset) / spec.factor;
|
|
83291
|
-
}
|
|
83292
|
-
/**
|
|
83293
|
-
* Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
|
|
83294
|
-
* Throws `UnitConversionError` when either unit is unknown or the units belong
|
|
83295
|
-
* to different dimensions.
|
|
83296
|
-
*/
|
|
83297
|
-
function convertUnit(value, from, to) {
|
|
83298
|
-
if (from === to) return value;
|
|
83299
|
-
const fromSpec = specFor(from);
|
|
83300
|
-
if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
|
|
83301
|
-
const toSpec = specFor(to);
|
|
83302
|
-
if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
|
|
83303
|
-
if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
|
|
83304
|
-
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
83305
|
-
}
|
|
83306
|
-
/**
|
|
83307
|
-
* Graceful variant for the render path: returns the converted value, or
|
|
83308
|
-
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
83309
|
-
* Never throws — a render caller falls back to the raw value + source unit.
|
|
83310
|
-
*/
|
|
83311
|
-
function tryConvertUnit(value, from, to) {
|
|
83312
|
-
if (from === to) return value;
|
|
83313
|
-
const fromSpec = specFor(from);
|
|
83314
|
-
if (!fromSpec) return void 0;
|
|
83315
|
-
const toSpec = specFor(to);
|
|
83316
|
-
if (!toSpec) return void 0;
|
|
83317
|
-
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
83318
|
-
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
83319
|
-
}
|
|
83320
|
-
/**
|
|
83321
|
-
* Whether `value` can be converted between `from` and `to` (same dimension,
|
|
83322
|
-
* both known). Cheap boolean probe for the UI's same-dimension unit picker and
|
|
83323
|
-
* the resolver's convertibility branch — compute ONCE so a unit label and its
|
|
83324
|
-
* value math can never disagree.
|
|
83325
|
-
*/
|
|
83326
|
-
function canConvertUnit(from, to) {
|
|
83327
|
-
if (from === to) return true;
|
|
83328
|
-
const fromDim = unitDimension(from);
|
|
83329
|
-
if (fromDim === void 0) return false;
|
|
83330
|
-
return fromDim === unitDimension(to);
|
|
83331
|
-
}
|
|
83332
|
-
/**
|
|
83333
|
-
* Every known unit spelling in a dimension (table insertion order — canonical
|
|
83334
|
-
* unit first). Powers the UI's same-dimension unit picker.
|
|
83335
|
-
*/
|
|
83336
|
-
/**
|
|
83337
|
-
* Convert a difference, not a reading. Affine units (°F) shift an absolute
|
|
83338
|
-
* value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
|
|
83339
|
-
*/
|
|
83340
|
-
function tryConvertDelta(value, from, to) {
|
|
83341
|
-
if (from === to) return value;
|
|
83342
|
-
const origin = tryConvertUnit(0, from, to);
|
|
83343
|
-
const shifted = tryConvertUnit(value, from, to);
|
|
83344
|
-
if (origin === void 0 || shifted === void 0) return void 0;
|
|
83345
|
-
return shifted - origin;
|
|
83346
|
-
}
|
|
83347
|
-
function unitsForDimension(dimension) {
|
|
83348
|
-
const out = [];
|
|
83349
|
-
for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
|
|
83350
|
-
return out;
|
|
83351
|
-
}
|
|
83352
|
-
//#endregion
|
|
83353
83696
|
//#region src/composition/evaluate-field.ts
|
|
83354
83697
|
/**
|
|
83355
83698
|
* One composed field, evaluated from what its sources say NOW (D659).
|
|
@@ -101058,7 +101401,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
101058
101401
|
min: 4,
|
|
101059
101402
|
max: 200,
|
|
101060
101403
|
step: 1,
|
|
101061
|
-
unit: "g"
|
|
101404
|
+
unit: "g",
|
|
101405
|
+
quantity: "mass"
|
|
101062
101406
|
},
|
|
101063
101407
|
optional: true
|
|
101064
101408
|
}]
|
|
@@ -101077,7 +101421,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
101077
101421
|
min: 4,
|
|
101078
101422
|
max: 200,
|
|
101079
101423
|
step: 1,
|
|
101080
|
-
unit: "g"
|
|
101424
|
+
unit: "g",
|
|
101425
|
+
quantity: "mass"
|
|
101081
101426
|
},
|
|
101082
101427
|
optional: true
|
|
101083
101428
|
}, {
|
|
@@ -101088,7 +101433,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
101088
101433
|
min: 4,
|
|
101089
101434
|
max: 200,
|
|
101090
101435
|
step: 1,
|
|
101091
|
-
unit: "g"
|
|
101436
|
+
unit: "g",
|
|
101437
|
+
quantity: "mass"
|
|
101092
101438
|
},
|
|
101093
101439
|
optional: true
|
|
101094
101440
|
}]
|
|
@@ -123395,6 +123741,8 @@ exports.PtzPresetFunctionSchema = PtzPresetFunctionSchema;
|
|
|
123395
123741
|
exports.PtzPresetSchema = PtzPresetSchema;
|
|
123396
123742
|
exports.PtzStatusSchema = PtzStatusSchema;
|
|
123397
123743
|
exports.QUANTITY_BASE = require_sleep.QUANTITY_BASE;
|
|
123744
|
+
exports.QuantityUnitError = QuantityUnitError;
|
|
123745
|
+
exports.QuantityValueSchema = QuantityValueSchema;
|
|
123398
123746
|
exports.QueryFilterSchema = QueryFilterSchema;
|
|
123399
123747
|
exports.RATE_CONTROL_RELAXED = RATE_CONTROL_RELAXED;
|
|
123400
123748
|
exports.RATE_CONTROL_TIGHT = RATE_CONTROL_TIGHT;
|
|
@@ -123937,6 +124285,7 @@ exports.UninstallNodeOutcomeSchema = UninstallNodeOutcomeSchema;
|
|
|
123937
124285
|
exports.UninstallNodeResultSchema = UninstallNodeResultSchema;
|
|
123938
124286
|
exports.UninstallPackageResultSchema = UninstallPackageResultSchema;
|
|
123939
124287
|
exports.UnitConversionError = UnitConversionError;
|
|
124288
|
+
exports.UnitWarningSchema = UnitWarningSchema;
|
|
123940
124289
|
exports.UnknownRecordKindError = require_sleep.UnknownRecordKindError;
|
|
123941
124290
|
exports.UnreleasedClaimSchema = UnreleasedClaimSchema;
|
|
123942
124291
|
exports.UnstampedEventMediaCountSchema = UnstampedEventMediaCountSchema;
|
|
@@ -124100,6 +124449,7 @@ exports.autoAssignProfiles = autoAssignProfiles;
|
|
|
124100
124449
|
exports.automationControlCapability = automationControlCapability;
|
|
124101
124450
|
exports.backupCapability = backupCapability;
|
|
124102
124451
|
exports.bareAddonId = bareAddonId;
|
|
124452
|
+
exports.baseUnitForQuantity = baseUnitForQuantity;
|
|
124103
124453
|
exports.baseUnitOf = require_sleep.baseUnitOf;
|
|
124104
124454
|
exports.batteryCapability = batteryCapability;
|
|
124105
124455
|
exports.bestLocationMatch = bestLocationMatch;
|
|
@@ -124148,6 +124498,7 @@ exports.canConvertUnit = canConvertUnit;
|
|
|
124148
124498
|
exports.canonicalEgressPlan = canonicalEgressPlan;
|
|
124149
124499
|
exports.canonicalIconKey = canonicalIconKey;
|
|
124150
124500
|
exports.canonicalizeAppCallback = canonicalizeAppCallback;
|
|
124501
|
+
exports.capUnitResolver = capUnitResolver;
|
|
124151
124502
|
exports.capabilityScopesOf = capabilityScopesOf;
|
|
124152
124503
|
exports.capacityForModel = capacityForModel;
|
|
124153
124504
|
exports.capacitySessionsOf = capacitySessionsOf;
|
|
@@ -124230,6 +124581,7 @@ exports.contactCapability = contactCapability;
|
|
|
124230
124581
|
exports.containingParentIndex = containingParentIndex;
|
|
124231
124582
|
exports.controlCapability = controlCapability;
|
|
124232
124583
|
exports.convertPresetFeature = convertPresetFeature;
|
|
124584
|
+
exports.convertQuantityInput = convertQuantityInput;
|
|
124233
124585
|
exports.convertUnit = convertUnit;
|
|
124234
124586
|
exports.convertUnitTargets = convertUnitTargets;
|
|
124235
124587
|
exports.coralUsableUnits = coralUsableUnits;
|
|
@@ -124545,6 +124897,7 @@ exports.isObjectInput = isObjectInput;
|
|
|
124545
124897
|
exports.isOccupancyRule = isOccupancyRule;
|
|
124546
124898
|
exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
|
|
124547
124899
|
exports.isPlugApplianceType = isPlugApplianceType;
|
|
124900
|
+
exports.isQuantityValue = isQuantityValue;
|
|
124548
124901
|
exports.isReadingQuantity = require_sleep.isReadingQuantity;
|
|
124549
124902
|
exports.isRemoteCustomKey = isRemoteCustomKey;
|
|
124550
124903
|
exports.isRemoteKnownRole = isRemoteKnownRole;
|
|
@@ -124675,12 +125028,15 @@ exports.nodePickerItems = nodePickerItems;
|
|
|
124675
125028
|
exports.nodePin = require_sleep.nodePin;
|
|
124676
125029
|
exports.nodeRemovalHookCapability = nodeRemovalHookCapability;
|
|
124677
125030
|
exports.nodesCapability = nodesCapability;
|
|
125031
|
+
exports.normalizeActionUnits = normalizeActionUnits;
|
|
124678
125032
|
exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
|
|
124679
125033
|
exports.normalizeAudioLabel = normalizeAudioLabel;
|
|
125034
|
+
exports.normalizeConditionUnits = normalizeConditionUnits;
|
|
124680
125035
|
exports.normalizeCoreBlockWrite = normalizeCoreBlockWrite;
|
|
124681
125036
|
exports.normalizeNcMediaPolicy = normalizeNcMediaPolicy;
|
|
124682
125037
|
exports.normalizeNodeId = require_sleep.normalizeNodeId;
|
|
124683
125038
|
exports.normalizeOrigin = normalizeOrigin;
|
|
125039
|
+
exports.normalizeRecipeUnits = normalizeRecipeUnits;
|
|
124684
125040
|
exports.normalizeSecretOwner = require_sleep.normalizeSecretOwner;
|
|
124685
125041
|
exports.normalizeTokenScopes = normalizeTokenScopes;
|
|
124686
125042
|
exports.normalizeUnit = normalizeUnit;
|
|
@@ -124712,6 +125068,8 @@ exports.paletteFromStored = paletteFromStored;
|
|
|
124712
125068
|
exports.paletteLegend = paletteLegend;
|
|
124713
125069
|
exports.paletteResultSchema = paletteResultSchema;
|
|
124714
125070
|
exports.paletteSwatchSchema = paletteSwatchSchema;
|
|
125071
|
+
exports.paramMeta = paramMeta;
|
|
125072
|
+
exports.paramMetaOf = paramMetaOf;
|
|
124715
125073
|
exports.parseAcceleratorFactsJson = parseAcceleratorFactsJson;
|
|
124716
125074
|
exports.parseAdminBase = parseAdminBase;
|
|
124717
125075
|
exports.parseAlertEpisodePayload = parseAlertEpisodePayload;
|
|
@@ -125097,6 +125455,7 @@ exports.turnProviderCapability = turnProviderCapability;
|
|
|
125097
125455
|
exports.unitCompanionField = unitCompanionField;
|
|
125098
125456
|
exports.unitDimension = unitDimension;
|
|
125099
125457
|
exports.unitsForDimension = unitsForDimension;
|
|
125458
|
+
exports.unitsForQuantity = unitsForQuantity;
|
|
125100
125459
|
exports.unreachableExtensionSlotRow = unreachableExtensionSlotRow;
|
|
125101
125460
|
exports.unwrapSchema = unwrapSchema;
|
|
125102
125461
|
exports.updateCapability = updateCapability;
|