@camstack/addon-provider-homeassistant 1.2.643 → 1.2.645
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 +12 -0
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{ha-broker-source-zbaephU5.js → ha-broker-source-DAoZm6wm.js} +2121 -1254
- package/dist/{ha-broker-source-DxSvfCed.mjs → ha-broker-source-DLhSaXYb.mjs} +2122 -1255
- package/dist/ha-export/ha-export.addon.js +6 -2
- package/dist/ha-export/ha-export.addon.mjs +6 -2
- package/package.json +4 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require("node:crypto");
|
|
2
|
-
//#region ../types/dist/event-category-
|
|
2
|
+
//#region ../types/dist/event-category-BmPm98n0.mjs
|
|
3
3
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4
4
|
EventCategory["SystemBoot"] = "system.boot";
|
|
5
5
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -483,6 +483,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
483
483
|
EventCategory["RunningStarted"] = "running.started";
|
|
484
484
|
/** Mirrors `runningStateCapability.events.onFinished`. */
|
|
485
485
|
EventCategory["RunningFinished"] = "running.finished";
|
|
486
|
+
/** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
|
|
487
|
+
EventCategory["DeviceRunOpened"] = "device-run.opened";
|
|
488
|
+
/** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
|
|
489
|
+
EventCategory["DeviceRunClosed"] = "device-run.closed";
|
|
486
490
|
/**
|
|
487
491
|
* Cap event fired by every device that registers the `event-emitter`
|
|
488
492
|
* capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
|
|
@@ -715,6 +719,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
715
719
|
EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
|
|
716
720
|
/** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
|
|
717
721
|
EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
|
|
722
|
+
/** A migration failed or was blocked; the Alert Center re-reads the ledger. */
|
|
723
|
+
EventCategory["MigrationFailed"] = "migration.failed";
|
|
724
|
+
/** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
|
|
725
|
+
EventCategory["MigrationApplied"] = "migration.applied";
|
|
718
726
|
EventCategory["DeviceUpdated"] = "device.updated";
|
|
719
727
|
/**
|
|
720
728
|
* Transport-level connectivity. Emitted by the device driver when
|
|
@@ -777,6 +785,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
777
785
|
return EventCategory;
|
|
778
786
|
}({});
|
|
779
787
|
//#endregion
|
|
788
|
+
//#region ../types/dist/device-type-CEqjmUnR.mjs
|
|
789
|
+
/**
|
|
790
|
+
import { errMsg } from '@camstack/types'
|
|
791
|
+
* Extract a human-readable message from an unknown error value.
|
|
792
|
+
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
793
|
+
*/
|
|
794
|
+
function errMsg(err) {
|
|
795
|
+
if (err instanceof Error) return err.message;
|
|
796
|
+
if (typeof err === "string") return err;
|
|
797
|
+
return String(err);
|
|
798
|
+
}
|
|
799
|
+
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
800
|
+
DeviceType["Camera"] = "camera";
|
|
801
|
+
DeviceType["Hub"] = "hub";
|
|
802
|
+
DeviceType["Light"] = "light";
|
|
803
|
+
DeviceType["Siren"] = "siren";
|
|
804
|
+
DeviceType["Switch"] = "switch";
|
|
805
|
+
DeviceType["Sensor"] = "sensor";
|
|
806
|
+
DeviceType["Thermostat"] = "thermostat";
|
|
807
|
+
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
808
|
+
* `climate-control` cap surface with `Thermostat` but renders a
|
|
809
|
+
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
810
|
+
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
811
|
+
* reusable by other AC integrations. */
|
|
812
|
+
DeviceType["Climate"] = "climate";
|
|
813
|
+
DeviceType["Button"] = "button";
|
|
814
|
+
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
815
|
+
* event vocabulary verbatim (no normalization). Installed with the
|
|
816
|
+
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
817
|
+
* HA bus events (e.g. `zha_event`, generic). */
|
|
818
|
+
DeviceType["EventEmitter"] = "event-emitter";
|
|
819
|
+
/** Firmware/software update entity — current vs available version,
|
|
820
|
+
* updatable flag, update state, and an install action. Installed with
|
|
821
|
+
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
822
|
+
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
823
|
+
DeviceType["Update"] = "update";
|
|
824
|
+
DeviceType["Generic"] = "generic";
|
|
825
|
+
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
826
|
+
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
827
|
+
* endpoint; the `notifier` cap defines the send surface. */
|
|
828
|
+
DeviceType["Notifier"] = "notifier";
|
|
829
|
+
/** Pre-recorded action sequence with optional parameters
|
|
830
|
+
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
831
|
+
DeviceType["Script"] = "script";
|
|
832
|
+
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
833
|
+
* trigger surface exposed via `automation-control` cap. */
|
|
834
|
+
DeviceType["Automation"] = "automation";
|
|
835
|
+
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
836
|
+
DeviceType["Lock"] = "lock";
|
|
837
|
+
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
838
|
+
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
839
|
+
DeviceType["Cover"] = "cover";
|
|
840
|
+
/** Pipe / water / gas valve with open/close/stop and optional
|
|
841
|
+
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
842
|
+
* modelled on the same open/closed lifecycle. */
|
|
843
|
+
DeviceType["Valve"] = "valve";
|
|
844
|
+
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
845
|
+
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
846
|
+
* modelled on the same target / mode lifecycle. */
|
|
847
|
+
DeviceType["Humidifier"] = "humidifier";
|
|
848
|
+
/** Water heater / boiler with target temperature + operation mode +
|
|
849
|
+
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
850
|
+
* climate-family actuator. */
|
|
851
|
+
DeviceType["WaterHeater"] = "water-heater";
|
|
852
|
+
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
853
|
+
DeviceType["Fan"] = "fan";
|
|
854
|
+
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
855
|
+
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
856
|
+
DeviceType["MediaPlayer"] = "media-player";
|
|
857
|
+
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
858
|
+
* `alarm-panel` cap. */
|
|
859
|
+
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
860
|
+
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
861
|
+
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
862
|
+
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
863
|
+
* TextControl / DateTimeControl. */
|
|
864
|
+
DeviceType["Control"] = "control";
|
|
865
|
+
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
866
|
+
* `presence` cap. */
|
|
867
|
+
DeviceType["Presence"] = "presence";
|
|
868
|
+
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
869
|
+
* `weather` cap. */
|
|
870
|
+
DeviceType["Weather"] = "weather";
|
|
871
|
+
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
872
|
+
DeviceType["Vacuum"] = "vacuum";
|
|
873
|
+
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
874
|
+
* `lawn-mower-control` cap. */
|
|
875
|
+
DeviceType["LawnMower"] = "lawn-mower";
|
|
876
|
+
/** Physical HA device group — parent container for entity-children
|
|
877
|
+
* adopted from a single HA device entry. Not renderable as a
|
|
878
|
+
* standalone device; exists only to anchor child entities. */
|
|
879
|
+
DeviceType["Container"] = "container";
|
|
880
|
+
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
881
|
+
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
882
|
+
DeviceType["Image"] = "image";
|
|
883
|
+
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
884
|
+
* level, battery, desiccant life, feeding state and manual-feed /
|
|
885
|
+
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
886
|
+
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
887
|
+
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
888
|
+
* integrations sharing the same food/desiccant/hopper surface. */
|
|
889
|
+
DeviceType["PetFeeder"] = "pet-feeder";
|
|
890
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
891
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
892
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
893
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
894
|
+
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
895
|
+
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
896
|
+
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
897
|
+
* A plug-built dishwasher carries running-state and power instead. */
|
|
898
|
+
DeviceType["Dishwasher"] = "dishwasher";
|
|
899
|
+
/** Washing machine. A plug-built one is running while its power stays
|
|
900
|
+
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
901
|
+
DeviceType["Washer"] = "washer";
|
|
902
|
+
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
903
|
+
DeviceType["Dryer"] = "dryer";
|
|
904
|
+
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
905
|
+
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
906
|
+
* address, connection state) and the actions the box really supports
|
|
907
|
+
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
908
|
+
* adopted beside it — and its connection settings stay on the integration. */
|
|
909
|
+
DeviceType["Gateway"] = "gateway";
|
|
910
|
+
return DeviceType;
|
|
911
|
+
}({});
|
|
912
|
+
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
913
|
+
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
914
|
+
DeviceFeature["Rebootable"] = "rebootable";
|
|
915
|
+
/**
|
|
916
|
+
* Device supports an on-demand re-sync of its derived spec with its
|
|
917
|
+
* upstream source — drives the generic Re-sync button. The owning
|
|
918
|
+
* provider implements the action via the `device-adoption.resync` cap.
|
|
919
|
+
*/
|
|
920
|
+
DeviceFeature["Resyncable"] = "resyncable";
|
|
921
|
+
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
922
|
+
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
923
|
+
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
924
|
+
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
925
|
+
/**
|
|
926
|
+
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
927
|
+
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
928
|
+
* camera ships autotrack — the admin UI uses this flag to gate
|
|
929
|
+
* the autotrack toggle / settings card without re-deriving from
|
|
930
|
+
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
931
|
+
* driver sets this feature when probe confirms the firmware
|
|
932
|
+
* surface, and registers the cap in the same code path.
|
|
933
|
+
*/
|
|
934
|
+
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
935
|
+
/**
|
|
936
|
+
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
937
|
+
* motion detection automatically activates this device. Mirrors
|
|
938
|
+
* `motion-trigger` cap registration: drivers set this feature in the
|
|
939
|
+
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
940
|
+
*
|
|
941
|
+
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
942
|
+
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
943
|
+
* filters that want "all devices with on-motion behaviour".
|
|
944
|
+
*/
|
|
945
|
+
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
946
|
+
/** Light supports rgb-triplet color via `color` cap. */
|
|
947
|
+
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
948
|
+
/** Light supports HSV color via `color` cap. */
|
|
949
|
+
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
950
|
+
/** Light supports color-temperature (mired) via `color` cap. */
|
|
951
|
+
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
952
|
+
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
953
|
+
* targetHigh). Gates the range slider UI. */
|
|
954
|
+
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
955
|
+
/** Thermostat exposes target humidity and/or current humidity
|
|
956
|
+
* readings. Gates the humidity controls. */
|
|
957
|
+
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
958
|
+
/** Thermostat exposes a fan-mode selector. */
|
|
959
|
+
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
960
|
+
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
961
|
+
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
962
|
+
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
963
|
+
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
964
|
+
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
965
|
+
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
966
|
+
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
967
|
+
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
968
|
+
/** Cover exposes intermediate position control (0..100). Gates the
|
|
969
|
+
* position slider UI. */
|
|
970
|
+
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
971
|
+
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
972
|
+
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
973
|
+
/** Valve exposes intermediate position control (0..100). Gates the
|
|
974
|
+
* position slider / drag surface UI. */
|
|
975
|
+
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
976
|
+
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
977
|
+
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
978
|
+
/** Fan exposes a preset mode selector. */
|
|
979
|
+
DeviceFeature["FanPreset"] = "fan-preset";
|
|
980
|
+
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
981
|
+
* ceiling fans. */
|
|
982
|
+
DeviceFeature["FanDirection"] = "fan-direction";
|
|
983
|
+
/** Fan exposes an oscillation toggle. */
|
|
984
|
+
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
985
|
+
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
986
|
+
* field on the UI lock-controls panel. */
|
|
987
|
+
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
988
|
+
/** Lock supports a latch-release ("open door") action distinct from
|
|
989
|
+
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
990
|
+
* `supported_features`. Gates the Open Door button in the UI. */
|
|
991
|
+
DeviceFeature["LockOpen"] = "lock-open";
|
|
992
|
+
/** Media player exposes a seek-to-position surface. */
|
|
993
|
+
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
994
|
+
/** Media player exposes a volume-level setter. */
|
|
995
|
+
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
996
|
+
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
997
|
+
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
998
|
+
/** Media player exposes a shuffle toggle. */
|
|
999
|
+
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
1000
|
+
/** Media player exposes a repeat mode (off / all / one). */
|
|
1001
|
+
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
1002
|
+
/** Media player exposes a source / input selector. */
|
|
1003
|
+
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
1004
|
+
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
1005
|
+
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
1006
|
+
/** Media player exposes next-track. */
|
|
1007
|
+
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
1008
|
+
/** Media player exposes previous-track. */
|
|
1009
|
+
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
1010
|
+
/** Media player exposes stop distinct from pause. */
|
|
1011
|
+
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
1012
|
+
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
1013
|
+
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
1014
|
+
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
1015
|
+
* addition to a textual location. */
|
|
1016
|
+
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
1017
|
+
/** Notifier accepts an inline / URL image attachment. */
|
|
1018
|
+
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
1019
|
+
/** Notifier accepts a priority hint (high/normal/low). */
|
|
1020
|
+
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
1021
|
+
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
1022
|
+
* fields. */
|
|
1023
|
+
DeviceFeature["NotifierData"] = "notifier-data";
|
|
1024
|
+
/** Notifier supports interactive action buttons / callbacks. */
|
|
1025
|
+
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
1026
|
+
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
1027
|
+
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
1028
|
+
/** Script runner accepts a variables map on each run invocation. */
|
|
1029
|
+
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
1030
|
+
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
1031
|
+
* automation's actions while bypassing its condition block. */
|
|
1032
|
+
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
1033
|
+
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
1034
|
+
* map tile in the vacuum UI. */
|
|
1035
|
+
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
1036
|
+
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
1037
|
+
* switches group. */
|
|
1038
|
+
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
1039
|
+
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
1040
|
+
* mower map tile. */
|
|
1041
|
+
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
1042
|
+
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
1043
|
+
* spots) — gates the Mow action + map selectors. */
|
|
1044
|
+
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
1045
|
+
return DeviceFeature;
|
|
1046
|
+
}({});
|
|
1047
|
+
/**
|
|
1048
|
+
* Semantic role a device plays within its parent. Populated by driver
|
|
1049
|
+
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
1050
|
+
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
1051
|
+
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
1052
|
+
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
1053
|
+
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
1054
|
+
*
|
|
1055
|
+
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
1056
|
+
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
1057
|
+
*/
|
|
1058
|
+
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
1059
|
+
DeviceRole["Siren"] = "siren";
|
|
1060
|
+
DeviceRole["Floodlight"] = "floodlight";
|
|
1061
|
+
DeviceRole["Spotlight"] = "spotlight";
|
|
1062
|
+
DeviceRole["PirSensor"] = "pir-sensor";
|
|
1063
|
+
DeviceRole["Chime"] = "chime";
|
|
1064
|
+
DeviceRole["Autotrack"] = "autotrack";
|
|
1065
|
+
DeviceRole["Nightvision"] = "nightvision";
|
|
1066
|
+
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
1067
|
+
DeviceRole["Doorbell"] = "doorbell";
|
|
1068
|
+
/** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
|
|
1069
|
+
DeviceRole["CoverBlind"] = "cover-blind";
|
|
1070
|
+
/** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
|
|
1071
|
+
DeviceRole["CoverShutter"] = "cover-shutter";
|
|
1072
|
+
/** Exterior awning. HA `device_class: awning`. */
|
|
1073
|
+
DeviceRole["CoverAwning"] = "cover-awning";
|
|
1074
|
+
/** Curtain / drape. HA `device_class: curtain`. */
|
|
1075
|
+
DeviceRole["CoverCurtain"] = "cover-curtain";
|
|
1076
|
+
/** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
|
|
1077
|
+
DeviceRole["CoverGarage"] = "cover-garage";
|
|
1078
|
+
/** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
|
|
1079
|
+
DeviceRole["CoverGate"] = "cover-gate";
|
|
1080
|
+
/** Door. HA `device_class: door`. A security actuator: exports gate it. */
|
|
1081
|
+
DeviceRole["CoverDoor"] = "cover-door";
|
|
1082
|
+
/** Window. HA `device_class: window`. */
|
|
1083
|
+
DeviceRole["CoverWindow"] = "cover-window";
|
|
1084
|
+
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
1085
|
+
* real Switch device for UI rendering / export adapters. */
|
|
1086
|
+
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
1087
|
+
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
1088
|
+
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
1089
|
+
DeviceRole["Outlet"] = "outlet";
|
|
1090
|
+
/** Generic motion / occupancy / moving event source. Distinct from
|
|
1091
|
+
* the camera accessory PirSensor role: that one is a camera child;
|
|
1092
|
+
* this is a standalone HA / 3rd-party motion sensor. */
|
|
1093
|
+
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
1094
|
+
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
1095
|
+
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
1096
|
+
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
1097
|
+
DeviceRole["COSensor"] = "co-sensor";
|
|
1098
|
+
DeviceRole["GasSensor"] = "gas-sensor";
|
|
1099
|
+
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
1100
|
+
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
1101
|
+
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
1102
|
+
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
1103
|
+
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
1104
|
+
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
1105
|
+
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
1106
|
+
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
1107
|
+
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
1108
|
+
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
1109
|
+
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
1110
|
+
DeviceRole["WindSensor"] = "wind-sensor";
|
|
1111
|
+
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
1112
|
+
DeviceRole["RainSensor"] = "rain-sensor";
|
|
1113
|
+
/** UV index (weather-station `uv-sensor` cap). */
|
|
1114
|
+
DeviceRole["UvSensor"] = "uv-sensor";
|
|
1115
|
+
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
1116
|
+
* Distinct from AmbientLightSensor (lux). */
|
|
1117
|
+
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
1118
|
+
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
1119
|
+
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
1120
|
+
DeviceRole["PowerSensor"] = "power-sensor";
|
|
1121
|
+
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
1122
|
+
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
1123
|
+
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
1124
|
+
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
1125
|
+
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
1126
|
+
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
1127
|
+
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
1128
|
+
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
1129
|
+
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
1130
|
+
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
1131
|
+
* `attributes.options`). */
|
|
1132
|
+
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
1133
|
+
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
1134
|
+
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
1135
|
+
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
1136
|
+
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
1137
|
+
/** Last-resort fallback when nothing else matches. */
|
|
1138
|
+
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
1139
|
+
DeviceRole["NumericControl"] = "numeric-control";
|
|
1140
|
+
DeviceRole["SelectControl"] = "select-control";
|
|
1141
|
+
DeviceRole["TextControl"] = "text-control";
|
|
1142
|
+
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
1143
|
+
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
1144
|
+
* rich features (image, priority, channel routing). */
|
|
1145
|
+
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
1146
|
+
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
1147
|
+
* `notify.discord_*`, etc.). */
|
|
1148
|
+
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
1149
|
+
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
1150
|
+
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
1151
|
+
/** Fallback when the notifier service name doesn't match a known
|
|
1152
|
+
* pattern. */
|
|
1153
|
+
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
1154
|
+
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
1155
|
+
* cap. A `DeviceType.Generic` device. */
|
|
1156
|
+
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
1157
|
+
return DeviceRole;
|
|
1158
|
+
}({});
|
|
1159
|
+
//#endregion
|
|
780
1160
|
//#region ../../node_modules/zod/v4/core/core.js
|
|
781
1161
|
var _a$1;
|
|
782
1162
|
function $constructor(name, initializer, params) {
|
|
@@ -5588,7 +5968,7 @@ var ZodIssueCode = {
|
|
|
5588
5968
|
var ZodFirstPartyTypeKind;
|
|
5589
5969
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5590
5970
|
//#endregion
|
|
5591
|
-
//#region ../types/dist/sleep-
|
|
5971
|
+
//#region ../types/dist/sleep-BKzhQTSm.mjs
|
|
5592
5972
|
/**
|
|
5593
5973
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5594
5974
|
* window to float samples (D455).
|
|
@@ -6104,6 +6484,102 @@ function emitReadiness(bus, params) {
|
|
|
6104
6484
|
ts
|
|
6105
6485
|
}));
|
|
6106
6486
|
}
|
|
6487
|
+
var NOT_RUN = {
|
|
6488
|
+
declared: false,
|
|
6489
|
+
snapshot: null,
|
|
6490
|
+
applied: [],
|
|
6491
|
+
failed: [],
|
|
6492
|
+
blocked: [],
|
|
6493
|
+
skipped: [],
|
|
6494
|
+
blockOwner: []
|
|
6495
|
+
};
|
|
6496
|
+
/**
|
|
6497
|
+
* Offer `migrations` to the engine and run what it says to run, in its order. Never
|
|
6498
|
+
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6499
|
+
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6500
|
+
*/
|
|
6501
|
+
async function runAddonMigrations(host, migrations, logger) {
|
|
6502
|
+
const first = migrations[0];
|
|
6503
|
+
if (first === void 0) return NOT_RUN;
|
|
6504
|
+
const owner = first.meta.owner;
|
|
6505
|
+
const log = logger.withTags({ migrationOwner: owner });
|
|
6506
|
+
const byId = new Map(migrations.map((m) => [m.meta.id, m]));
|
|
6507
|
+
let declaration;
|
|
6508
|
+
try {
|
|
6509
|
+
declaration = await host.declareOwner({
|
|
6510
|
+
owner,
|
|
6511
|
+
metas: migrations.map((m) => m.meta)
|
|
6512
|
+
});
|
|
6513
|
+
} catch (err) {
|
|
6514
|
+
log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
|
|
6515
|
+
return {
|
|
6516
|
+
...NOT_RUN,
|
|
6517
|
+
blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
|
|
6518
|
+
};
|
|
6519
|
+
}
|
|
6520
|
+
const applied = [];
|
|
6521
|
+
const failed = [];
|
|
6522
|
+
const blocked = [];
|
|
6523
|
+
const skipped = [];
|
|
6524
|
+
const settle = (id, status) => {
|
|
6525
|
+
if (status === "applied") applied.push(id);
|
|
6526
|
+
else if (status === "failed") failed.push(id);
|
|
6527
|
+
else if (status === "blocked") blocked.push(id);
|
|
6528
|
+
else skipped.push(id);
|
|
6529
|
+
};
|
|
6530
|
+
for (const step of declaration.steps) {
|
|
6531
|
+
const bound = byId.get(step.id);
|
|
6532
|
+
if (bound === void 0 || step.action === "skip") {
|
|
6533
|
+
skipped.push(step.id);
|
|
6534
|
+
continue;
|
|
6535
|
+
}
|
|
6536
|
+
const tags = { migrationId: step.id };
|
|
6537
|
+
try {
|
|
6538
|
+
const gate = await host.startMigration({ id: step.id });
|
|
6539
|
+
if (!gate.proceed) {
|
|
6540
|
+
log.warn("migration not run", {
|
|
6541
|
+
tags,
|
|
6542
|
+
meta: {
|
|
6543
|
+
state: gate.state,
|
|
6544
|
+
reason: gate.reason
|
|
6545
|
+
}
|
|
6546
|
+
});
|
|
6547
|
+
settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
|
|
6548
|
+
continue;
|
|
6549
|
+
}
|
|
6550
|
+
const result = await bound.execute();
|
|
6551
|
+
const outcome = await host.finishMigration({
|
|
6552
|
+
id: step.id,
|
|
6553
|
+
result
|
|
6554
|
+
});
|
|
6555
|
+
if (outcome.status === "applied") log.info("migration applied", {
|
|
6556
|
+
tags,
|
|
6557
|
+
meta: { rowsChanged: outcome.rowsChanged }
|
|
6558
|
+
});
|
|
6559
|
+
else log.error("migration failed", {
|
|
6560
|
+
tags,
|
|
6561
|
+
meta: { error: outcome.error }
|
|
6562
|
+
});
|
|
6563
|
+
settle(step.id, outcome.status);
|
|
6564
|
+
} catch (err) {
|
|
6565
|
+
log.error("migration could not be completed with the engine", {
|
|
6566
|
+
tags,
|
|
6567
|
+
meta: { error: errMsg(err) }
|
|
6568
|
+
});
|
|
6569
|
+
failed.push(step.id);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
|
|
6573
|
+
return {
|
|
6574
|
+
declared: true,
|
|
6575
|
+
snapshot: declaration.snapshot,
|
|
6576
|
+
applied,
|
|
6577
|
+
failed,
|
|
6578
|
+
blocked,
|
|
6579
|
+
skipped,
|
|
6580
|
+
blockOwner
|
|
6581
|
+
};
|
|
6582
|
+
}
|
|
6107
6583
|
/**
|
|
6108
6584
|
* Build a {@link DurableState} over a single store key. Transport-agnostic:
|
|
6109
6585
|
* `read`/`write` are the addon-store or device-store accessors. The whole
|
|
@@ -7159,6 +7635,28 @@ var BaseAddon = class {
|
|
|
7159
7635
|
return normalized;
|
|
7160
7636
|
}
|
|
7161
7637
|
/**
|
|
7638
|
+
* Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
|
|
7639
|
+
* engine. Call it at the point of the boot where the data must already have the new
|
|
7640
|
+
* shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
|
|
7641
|
+
* restored devices — with the catalogue bound to the addon's ports
|
|
7642
|
+
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7643
|
+
*
|
|
7644
|
+
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7645
|
+
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7646
|
+
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7647
|
+
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7648
|
+
*/
|
|
7649
|
+
async runMigrations(migrations) {
|
|
7650
|
+
const api = this.ctx.api.migrations;
|
|
7651
|
+
const report = await runAddonMigrations({
|
|
7652
|
+
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7653
|
+
startMigration: (input) => api.startMigration.mutate(input),
|
|
7654
|
+
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7655
|
+
}, migrations, this.ctx.logger);
|
|
7656
|
+
if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
|
|
7657
|
+
return report;
|
|
7658
|
+
}
|
|
7659
|
+
/**
|
|
7162
7660
|
* Called by the isolated-process runner AFTER `broker.start()`.
|
|
7163
7661
|
* In-process addons never need this because the hub broker is already
|
|
7164
7662
|
* running when `initialize()` fires. For forked children the broker
|
|
@@ -8456,410 +8954,6 @@ function defineEntryKind(name, spec) {
|
|
|
8456
8954
|
if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
|
|
8457
8955
|
return defined;
|
|
8458
8956
|
}
|
|
8459
|
-
var READING_QUANTITIES = [
|
|
8460
|
-
"temperature",
|
|
8461
|
-
"pressure",
|
|
8462
|
-
"speed",
|
|
8463
|
-
"precipitation",
|
|
8464
|
-
"precipitation-rate",
|
|
8465
|
-
"length",
|
|
8466
|
-
"area",
|
|
8467
|
-
"illuminance",
|
|
8468
|
-
"irradiance",
|
|
8469
|
-
"power",
|
|
8470
|
-
"energy",
|
|
8471
|
-
"voltage",
|
|
8472
|
-
"current",
|
|
8473
|
-
"apparent-power",
|
|
8474
|
-
"humidity",
|
|
8475
|
-
"percentage",
|
|
8476
|
-
"concentration-volume",
|
|
8477
|
-
"concentration-mass",
|
|
8478
|
-
"concentration-ppb",
|
|
8479
|
-
"volume",
|
|
8480
|
-
"duration",
|
|
8481
|
-
"duration-ms",
|
|
8482
|
-
"information",
|
|
8483
|
-
"sound-level",
|
|
8484
|
-
"signal-level",
|
|
8485
|
-
"colour-temperature",
|
|
8486
|
-
"angle",
|
|
8487
|
-
"bitrate",
|
|
8488
|
-
"rate",
|
|
8489
|
-
"mass",
|
|
8490
|
-
"index",
|
|
8491
|
-
"ratio",
|
|
8492
|
-
"count",
|
|
8493
|
-
"none",
|
|
8494
|
-
"device"
|
|
8495
|
-
];
|
|
8496
|
-
var ReadingQuantitySchema = _enum(READING_QUANTITIES);
|
|
8497
|
-
var QUANTITY_SET = new Set(READING_QUANTITIES);
|
|
8498
|
-
function isReadingQuantity(value) {
|
|
8499
|
-
return typeof value === "string" && QUANTITY_SET.has(value);
|
|
8500
|
-
}
|
|
8501
|
-
/**
|
|
8502
|
-
* Stored unit symbol for a quantity. `none` means the number is dimensionless
|
|
8503
|
-
* (or an index / ratio / count). `device` means the cap has no single unit.
|
|
8504
|
-
*/
|
|
8505
|
-
var QUANTITY_BASE = {
|
|
8506
|
-
temperature: "°C",
|
|
8507
|
-
pressure: "hPa",
|
|
8508
|
-
speed: "m/s",
|
|
8509
|
-
precipitation: "mm",
|
|
8510
|
-
"precipitation-rate": "mm/h",
|
|
8511
|
-
length: "m",
|
|
8512
|
-
area: "m²",
|
|
8513
|
-
illuminance: "lx",
|
|
8514
|
-
irradiance: "W/m²",
|
|
8515
|
-
power: "W",
|
|
8516
|
-
energy: "kWh",
|
|
8517
|
-
voltage: "V",
|
|
8518
|
-
current: "A",
|
|
8519
|
-
"apparent-power": "VA",
|
|
8520
|
-
humidity: "%",
|
|
8521
|
-
percentage: "%",
|
|
8522
|
-
"concentration-volume": "ppm",
|
|
8523
|
-
"concentration-mass": "µg/m³",
|
|
8524
|
-
"concentration-ppb": "ppb",
|
|
8525
|
-
volume: "L",
|
|
8526
|
-
duration: "s",
|
|
8527
|
-
"duration-ms": "ms",
|
|
8528
|
-
information: "B",
|
|
8529
|
-
"sound-level": "dBFS",
|
|
8530
|
-
"signal-level": "dBm",
|
|
8531
|
-
"colour-temperature": "mired",
|
|
8532
|
-
angle: "°",
|
|
8533
|
-
bitrate: "kbps",
|
|
8534
|
-
rate: "fps",
|
|
8535
|
-
mass: "kg",
|
|
8536
|
-
index: "none",
|
|
8537
|
-
ratio: "none",
|
|
8538
|
-
count: "none",
|
|
8539
|
-
none: "none",
|
|
8540
|
-
device: "device"
|
|
8541
|
-
};
|
|
8542
|
-
/** The stored unit, or `undefined` when the quantity has no unit symbol. */
|
|
8543
|
-
function baseUnitOf(quantity) {
|
|
8544
|
-
const symbol = QUANTITY_BASE[quantity];
|
|
8545
|
-
if (symbol === "none" || symbol === "device") return void 0;
|
|
8546
|
-
return symbol;
|
|
8547
|
-
}
|
|
8548
|
-
function readingMeta(quantity, extras) {
|
|
8549
|
-
return {
|
|
8550
|
-
quantity,
|
|
8551
|
-
...extras?.precision !== void 0 ? { precision: extras.precision } : {},
|
|
8552
|
-
...extras?.label !== void 0 ? { label: extras.label } : {}
|
|
8553
|
-
};
|
|
8554
|
-
}
|
|
8555
|
-
/** Shared Zod schemas used across streaming capabilities. */
|
|
8556
|
-
var CamProfileSchema = _enum([
|
|
8557
|
-
"high",
|
|
8558
|
-
"mid",
|
|
8559
|
-
"low"
|
|
8560
|
-
]);
|
|
8561
|
-
/**
|
|
8562
|
-
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
8563
|
-
*
|
|
8564
|
-
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
8565
|
-
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
8566
|
-
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
8567
|
-
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
8568
|
-
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
8569
|
-
* "the lowest AVAILABLE native rung" (D537).
|
|
8570
|
-
*/
|
|
8571
|
-
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
8572
|
-
var CamStreamKindSchema = _enum([
|
|
8573
|
-
"pull-rtsp",
|
|
8574
|
-
"pull-rtmp",
|
|
8575
|
-
"pull-http",
|
|
8576
|
-
"pull-flv",
|
|
8577
|
-
"pull-rfc4571",
|
|
8578
|
-
"push-annexb",
|
|
8579
|
-
"derived"
|
|
8580
|
-
]);
|
|
8581
|
-
var CamStreamResolutionSchema = object({
|
|
8582
|
-
width: number().int().positive().meta(readingMeta("none")),
|
|
8583
|
-
height: number().int().positive().meta(readingMeta("none"))
|
|
8584
|
-
});
|
|
8585
|
-
var CameraStreamSchema = object({
|
|
8586
|
-
/** Stable, provider-assigned id unique within the (deviceId) scope. */
|
|
8587
|
-
camStreamId: string().min(1),
|
|
8588
|
-
deviceId: number().int().nonnegative(),
|
|
8589
|
-
kind: CamStreamKindSchema,
|
|
8590
|
-
/** Required for pull-* kinds. Ignored for push-annexb. */
|
|
8591
|
-
url: string().optional(),
|
|
8592
|
-
codec: string().optional(),
|
|
8593
|
-
resolution: CamStreamResolutionSchema.optional(),
|
|
8594
|
-
fps: number().positive().optional(),
|
|
8595
|
-
/** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
|
|
8596
|
-
label: string().optional(),
|
|
8597
|
-
/**
|
|
8598
|
-
* Device-level features the publisher advertised (e.g. `battery-operated`).
|
|
8599
|
-
* The broker, snapshot orchestrator, and prebuffer manager all consult
|
|
8600
|
-
* this list to derive policy — relaxed stall watchdog for battery
|
|
8601
|
-
* cams, prebuffer off by default, longer snapshot rate-limit, etc.
|
|
8602
|
-
*
|
|
8603
|
-
* Single source of truth replacing per-stream flags like the
|
|
8604
|
-
* historical `allowStall`: if the publisher knows the camera is
|
|
8605
|
-
* battery-powered, every downstream service derives the right policy
|
|
8606
|
-
* from this list.
|
|
8607
|
-
*/
|
|
8608
|
-
deviceFeatures: array(string()).optional(),
|
|
8609
|
-
/**
|
|
8610
|
-
* Whether this stream participates in the broker's automatic profile
|
|
8611
|
-
* assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
|
|
8612
|
-
* use `false` when they want a stream to be SELECTABLE in the UI but not
|
|
8613
|
-
* picked by default — e.g. Reolink publishes its native Baichuan streams
|
|
8614
|
-
* as `autoEligible: true` (the recommended path) and its RTSP / RTMP
|
|
8615
|
-
* mirrors as `autoEligible: false` (still pickable per slot, just not
|
|
8616
|
-
* the auto choice). Manual `assignProfile` calls remain valid for
|
|
8617
|
-
* non-eligible streams.
|
|
8618
|
-
*/
|
|
8619
|
-
autoEligible: boolean().optional(),
|
|
8620
|
-
/**
|
|
8621
|
-
* The profile slot the PUBLISHER says this stream is: `low` for a stream
|
|
8622
|
-
* whose only sensible role is the low-bandwidth one, `high` for a main
|
|
8623
|
-
* stream. Consulted by the broker's automatic ranking
|
|
8624
|
-
* (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
|
|
8625
|
-
* an operator's manual assignment still wins, and an absent hint is the
|
|
8626
|
-
* pixel-ranked default every camera had before this field existed.
|
|
8627
|
-
*
|
|
8628
|
-
* Why it exists: a camera with ONE stream ranked to `mid` — the slot no
|
|
8629
|
-
* storage class is written for on its own (`recordings` is high|mid,
|
|
8630
|
-
* `recordingsLow` is low) and the one profile scrub does not read. The robot
|
|
8631
|
-
* camera's single 720p relay is a `low` in every respect except the slot the
|
|
8632
|
-
* heuristic put it in.
|
|
8633
|
-
*/
|
|
8634
|
-
profileHint: CamProfileSchema.optional(),
|
|
8635
|
-
/**
|
|
8636
|
-
* Transport-specific opaque metadata. The broker passes it through to
|
|
8637
|
-
* the source reader without inspecting it. Currently used by
|
|
8638
|
-
* `pull-rfc4571` streams to carry the upstream SDP (so the reader can
|
|
8639
|
-
* route RTP packets to the right depacketizer without an in-band
|
|
8640
|
-
* DESCRIBE phase). Other kinds typically leave it undefined.
|
|
8641
|
-
*/
|
|
8642
|
-
metadata: record(string(), unknown()).optional()
|
|
8643
|
-
});
|
|
8644
|
-
var ProfileSlotStatusSchema = _enum([
|
|
8645
|
-
"unassigned",
|
|
8646
|
-
"idle",
|
|
8647
|
-
"connecting",
|
|
8648
|
-
"streaming",
|
|
8649
|
-
"error"
|
|
8650
|
-
]);
|
|
8651
|
-
var ProfileSlotSchema = object({
|
|
8652
|
-
deviceId: number().int().nonnegative(),
|
|
8653
|
-
profile: CamProfileSchema,
|
|
8654
|
-
/** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
|
|
8655
|
-
brokerId: string(),
|
|
8656
|
-
/** `null` when the profile is unassigned. */
|
|
8657
|
-
sourceCamStreamId: string().nullable(),
|
|
8658
|
-
status: ProfileSlotStatusSchema,
|
|
8659
|
-
resolution: CamStreamResolutionSchema.optional(),
|
|
8660
|
-
codec: string().optional(),
|
|
8661
|
-
preBufferSec: number().nonnegative().optional(),
|
|
8662
|
-
errorMessage: string().optional()
|
|
8663
|
-
});
|
|
8664
|
-
/**
|
|
8665
|
-
* Zod schema for StreamSourceEntry — the canonical stream descriptor
|
|
8666
|
-
* exposed by ICameraDevice.getStreamSources() and consumed by the broker.
|
|
8667
|
-
*/
|
|
8668
|
-
var StreamSourceEntrySchema$1 = object({
|
|
8669
|
-
id: string(),
|
|
8670
|
-
label: string(),
|
|
8671
|
-
protocol: _enum([
|
|
8672
|
-
"rtsp",
|
|
8673
|
-
"rtmp",
|
|
8674
|
-
"annexb",
|
|
8675
|
-
"http-mjpeg",
|
|
8676
|
-
"webrtc",
|
|
8677
|
-
"custom"
|
|
8678
|
-
]),
|
|
8679
|
-
url: string().optional(),
|
|
8680
|
-
resolution: object({
|
|
8681
|
-
width: number(),
|
|
8682
|
-
height: number()
|
|
8683
|
-
}).readonly().optional(),
|
|
8684
|
-
fps: number().optional(),
|
|
8685
|
-
bitrate: number().optional(),
|
|
8686
|
-
codec: string().optional(),
|
|
8687
|
-
profileHint: CamProfileSchema.optional()
|
|
8688
|
-
});
|
|
8689
|
-
object({
|
|
8690
|
-
type: string(),
|
|
8691
|
-
url: string(),
|
|
8692
|
-
videoCodec: string().optional(),
|
|
8693
|
-
audioCodec: string().optional(),
|
|
8694
|
-
metadata: record(string(), unknown()).readonly().optional()
|
|
8695
|
-
});
|
|
8696
|
-
/**
|
|
8697
|
-
* Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
|
|
8698
|
-
* frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
|
|
8699
|
-
* interface in `interfaces/frame-handle.ts` field-for-field so a cap method
|
|
8700
|
-
* (the runner's native-crop and pipeline-executor inputs) can carry handles
|
|
8701
|
-
* over tRPC / Moleculer.
|
|
8702
|
-
*
|
|
8703
|
-
* The `satisfies` assertion below pins this schema to the interface: if a
|
|
8704
|
-
* field is added to / removed from `FrameHandle` without a matching schema
|
|
8705
|
-
* edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
|
|
8706
|
-
* `FrameHandle` and the build fails — no silent drift.
|
|
8707
|
-
*/
|
|
8708
|
-
var FrameHandleSchema = object({
|
|
8709
|
-
shmId: string(),
|
|
8710
|
-
slot: number().int().nonnegative(),
|
|
8711
|
-
seq: number().int().nonnegative(),
|
|
8712
|
-
width: number().int().positive(),
|
|
8713
|
-
height: number().int().positive(),
|
|
8714
|
-
format: _enum([
|
|
8715
|
-
"jpeg",
|
|
8716
|
-
"rgb",
|
|
8717
|
-
"bgr",
|
|
8718
|
-
"yuv420",
|
|
8719
|
-
"gray"
|
|
8720
|
-
]),
|
|
8721
|
-
pts: number(),
|
|
8722
|
-
byteLength: number().int().nonnegative(),
|
|
8723
|
-
nodeId: string(),
|
|
8724
|
-
slotCount: number().int().positive()
|
|
8725
|
-
});
|
|
8726
|
-
/**
|
|
8727
|
-
* Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
|
|
8728
|
-
* `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
|
|
8729
|
-
* sample bytes plus the track parameters a consumer needs to interpret them.
|
|
8730
|
-
*
|
|
8731
|
-
* Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
|
|
8732
|
-
* Float32 PCM), so unlike video frames they ship their bytes INLINE over
|
|
8733
|
-
* tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
|
|
8734
|
-
* (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
|
|
8735
|
-
* it.
|
|
8736
|
-
*/
|
|
8737
|
-
var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
|
|
8738
|
-
var DecodedAudioChunkSchema = object({
|
|
8739
|
-
data: _instanceof(Uint8Array),
|
|
8740
|
-
sampleRate: number().int().positive(),
|
|
8741
|
-
channels: number().int().positive(),
|
|
8742
|
-
timestamp: number(),
|
|
8743
|
-
/**
|
|
8744
|
-
* Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
|
|
8745
|
-
* byte, for any peer that never heard of this field. A coded window
|
|
8746
|
-
* (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
|
|
8747
|
-
* subscription that DECLARED it accepts one, so absence can never mean
|
|
8748
|
-
* "coded bytes a consumer will read as floats" (D455).
|
|
8749
|
-
*/
|
|
8750
|
-
format: AudioChunkFormatSchema.optional()
|
|
8751
|
-
});
|
|
8752
|
-
/**
|
|
8753
|
-
* Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
|
|
8754
|
-
* its bytes inline: a consumer asks the broker for the decoded audio-chunk
|
|
8755
|
-
* stream of one broker; the broker keeps a per-subscription bounded FIFO queue
|
|
8756
|
-
* the consumer drains via `pullAudioChunks`.
|
|
8757
|
-
*/
|
|
8758
|
-
var SubscribeAudioChunksInputSchema = object({
|
|
8759
|
-
brokerId: string(),
|
|
8760
|
-
/** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
|
|
8761
|
-
tag: string().optional(),
|
|
8762
|
-
/**
|
|
8763
|
-
* Byte formats this subscriber can READ, best first. The broker serves the
|
|
8764
|
-
* chunk's own format when it is in this list and expands to `f32le`
|
|
8765
|
-
* otherwise, so a subscriber is never handed bytes it cannot interpret.
|
|
8766
|
-
*
|
|
8767
|
-
* Absent (or without the source format) means `f32le` — the behaviour every
|
|
8768
|
-
* subscriber had before D455, unchanged. This is the negotiation half of
|
|
8769
|
-
* the source-bytes lever: it is what lets the broker and its consumers
|
|
8770
|
-
* deploy one at a time across three nodes.
|
|
8771
|
-
*/
|
|
8772
|
-
accept: array(AudioChunkFormatSchema).readonly().optional()
|
|
8773
|
-
});
|
|
8774
|
-
/** Result of `stream-broker.subscribeAudioChunks`. */
|
|
8775
|
-
var SubscribeAudioChunksResultSchema = object({
|
|
8776
|
-
/** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
|
|
8777
|
-
subscriptionId: string() });
|
|
8778
|
-
var BrokerStatsSchema = object({
|
|
8779
|
-
status: _enum([
|
|
8780
|
-
"idle",
|
|
8781
|
-
"connecting",
|
|
8782
|
-
"streaming",
|
|
8783
|
-
"error",
|
|
8784
|
-
"stopped"
|
|
8785
|
-
]),
|
|
8786
|
-
inputFps: number(),
|
|
8787
|
-
encodedSubscribers: number(),
|
|
8788
|
-
uptimeMs: number(),
|
|
8789
|
-
bitrateKbps: number(),
|
|
8790
|
-
idrIntervalMs: number(),
|
|
8791
|
-
codec: string().optional(),
|
|
8792
|
-
totalBytes: number(),
|
|
8793
|
-
packetCount: number(),
|
|
8794
|
-
rtspClients: number(),
|
|
8795
|
-
pipeClients: number(),
|
|
8796
|
-
preBufferSec: number(),
|
|
8797
|
-
preBufferMs: number(),
|
|
8798
|
-
preBufferPackets: number(),
|
|
8799
|
-
/**
|
|
8800
|
-
* Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
|
|
8801
|
-
* when the stream has no audio track or the broker is in cold start.
|
|
8802
|
-
* `supported = false` means the codec was detected but the local
|
|
8803
|
-
* decoder pipeline cannot produce PCM chunks (e.g. AAC without the
|
|
8804
|
-
* AAC pipeline wired). Surfaced in the UI device-overview so operators
|
|
8805
|
-
* can pre-pick the audio-analysis model that matches the codec.
|
|
8806
|
-
*/
|
|
8807
|
-
audio: object({
|
|
8808
|
-
codec: string(),
|
|
8809
|
-
sampleRate: number(),
|
|
8810
|
-
channels: number(),
|
|
8811
|
-
supported: boolean()
|
|
8812
|
-
}).nullable().optional(),
|
|
8813
|
-
/**
|
|
8814
|
-
* BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
|
|
8815
|
-
* distributing none of the device's audio, on live or recording.
|
|
8816
|
-
*
|
|
8817
|
-
* Present so a silent camera can be told apart from a broken one on the
|
|
8818
|
-
* stream panel itself, without cross-referencing the switch group: a
|
|
8819
|
-
* broker holding an `audio` track descriptor while `audioMuted` is true is
|
|
8820
|
-
* working exactly as asked. `audioMutedDropped` counts the audio units
|
|
8821
|
-
* thrown away since the current dial — it is how you confirm from stats
|
|
8822
|
-
* alone that the mute is on the packet path and not merely persisted.
|
|
8823
|
-
*/
|
|
8824
|
-
audioMuted: boolean().optional(),
|
|
8825
|
-
audioMutedDropped: number().optional()
|
|
8826
|
-
});
|
|
8827
|
-
/**
|
|
8828
|
-
* Exporter-facing "profile restream" entry. Returned by
|
|
8829
|
-
* `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
|
|
8830
|
-
* (high/mid/low). `brokerId` is the PROFILE-keyed broker id
|
|
8831
|
-
* (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
|
|
8832
|
-
* RTSP restream that aliases the profile's assigned source broker, so
|
|
8833
|
-
* every consumer dialling a profile converges on the same single pull.
|
|
8834
|
-
* `codec`/`resolution` describe the assigned SOURCE camStream (for the
|
|
8835
|
-
* `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
|
|
8836
|
-
* (raw per-camStream, live-view only).
|
|
8837
|
-
*/
|
|
8838
|
-
var ProfileRtspEntrySchema = object({
|
|
8839
|
-
profile: CamProfileSchema,
|
|
8840
|
-
/** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
|
|
8841
|
-
brokerId: string(),
|
|
8842
|
-
url: string(),
|
|
8843
|
-
mutedUrl: string(),
|
|
8844
|
-
enabled: boolean(),
|
|
8845
|
-
codec: string().optional(),
|
|
8846
|
-
resolution: CamStreamResolutionSchema.optional()
|
|
8847
|
-
});
|
|
8848
|
-
/**
|
|
8849
|
-
* The model weight formats a catalog entry can ship. Schema-free on purpose:
|
|
8850
|
-
* `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
|
|
8851
|
-
* (the model downloader in `@camstack/system`) needs only the list and must
|
|
8852
|
-
* reach it through `@camstack/types/addon` without building the cap schema
|
|
8853
|
-
* graph (D28).
|
|
8854
|
-
*/
|
|
8855
|
-
var MODEL_FORMATS = [
|
|
8856
|
-
"onnx",
|
|
8857
|
-
"coreml",
|
|
8858
|
-
"openvino",
|
|
8859
|
-
"tflite",
|
|
8860
|
-
"pt",
|
|
8861
|
-
"gguf"
|
|
8862
|
-
];
|
|
8863
8957
|
object({
|
|
8864
8958
|
field: string().min(1),
|
|
8865
8959
|
on: literal("advance")
|
|
@@ -8952,6 +9046,410 @@ function optionalMethod(input, output, options) {
|
|
|
8952
9046
|
function event(data) {
|
|
8953
9047
|
return { data };
|
|
8954
9048
|
}
|
|
9049
|
+
var READING_QUANTITIES = [
|
|
9050
|
+
"temperature",
|
|
9051
|
+
"pressure",
|
|
9052
|
+
"speed",
|
|
9053
|
+
"precipitation",
|
|
9054
|
+
"precipitation-rate",
|
|
9055
|
+
"length",
|
|
9056
|
+
"area",
|
|
9057
|
+
"illuminance",
|
|
9058
|
+
"irradiance",
|
|
9059
|
+
"power",
|
|
9060
|
+
"energy",
|
|
9061
|
+
"voltage",
|
|
9062
|
+
"current",
|
|
9063
|
+
"apparent-power",
|
|
9064
|
+
"humidity",
|
|
9065
|
+
"percentage",
|
|
9066
|
+
"concentration-volume",
|
|
9067
|
+
"concentration-mass",
|
|
9068
|
+
"concentration-ppb",
|
|
9069
|
+
"volume",
|
|
9070
|
+
"duration",
|
|
9071
|
+
"duration-ms",
|
|
9072
|
+
"information",
|
|
9073
|
+
"sound-level",
|
|
9074
|
+
"signal-level",
|
|
9075
|
+
"colour-temperature",
|
|
9076
|
+
"angle",
|
|
9077
|
+
"bitrate",
|
|
9078
|
+
"rate",
|
|
9079
|
+
"mass",
|
|
9080
|
+
"index",
|
|
9081
|
+
"ratio",
|
|
9082
|
+
"count",
|
|
9083
|
+
"none",
|
|
9084
|
+
"device"
|
|
9085
|
+
];
|
|
9086
|
+
var ReadingQuantitySchema = _enum(READING_QUANTITIES);
|
|
9087
|
+
var QUANTITY_SET = new Set(READING_QUANTITIES);
|
|
9088
|
+
function isReadingQuantity(value) {
|
|
9089
|
+
return typeof value === "string" && QUANTITY_SET.has(value);
|
|
9090
|
+
}
|
|
9091
|
+
/**
|
|
9092
|
+
* Stored unit symbol for a quantity. `none` means the number is dimensionless
|
|
9093
|
+
* (or an index / ratio / count). `device` means the cap has no single unit.
|
|
9094
|
+
*/
|
|
9095
|
+
var QUANTITY_BASE = {
|
|
9096
|
+
temperature: "°C",
|
|
9097
|
+
pressure: "hPa",
|
|
9098
|
+
speed: "m/s",
|
|
9099
|
+
precipitation: "mm",
|
|
9100
|
+
"precipitation-rate": "mm/h",
|
|
9101
|
+
length: "m",
|
|
9102
|
+
area: "m²",
|
|
9103
|
+
illuminance: "lx",
|
|
9104
|
+
irradiance: "W/m²",
|
|
9105
|
+
power: "W",
|
|
9106
|
+
energy: "kWh",
|
|
9107
|
+
voltage: "V",
|
|
9108
|
+
current: "A",
|
|
9109
|
+
"apparent-power": "VA",
|
|
9110
|
+
humidity: "%",
|
|
9111
|
+
percentage: "%",
|
|
9112
|
+
"concentration-volume": "ppm",
|
|
9113
|
+
"concentration-mass": "µg/m³",
|
|
9114
|
+
"concentration-ppb": "ppb",
|
|
9115
|
+
volume: "L",
|
|
9116
|
+
duration: "s",
|
|
9117
|
+
"duration-ms": "ms",
|
|
9118
|
+
information: "B",
|
|
9119
|
+
"sound-level": "dBFS",
|
|
9120
|
+
"signal-level": "dBm",
|
|
9121
|
+
"colour-temperature": "mired",
|
|
9122
|
+
angle: "°",
|
|
9123
|
+
bitrate: "kbps",
|
|
9124
|
+
rate: "fps",
|
|
9125
|
+
mass: "kg",
|
|
9126
|
+
index: "none",
|
|
9127
|
+
ratio: "none",
|
|
9128
|
+
count: "none",
|
|
9129
|
+
none: "none",
|
|
9130
|
+
device: "device"
|
|
9131
|
+
};
|
|
9132
|
+
/** The stored unit, or `undefined` when the quantity has no unit symbol. */
|
|
9133
|
+
function baseUnitOf(quantity) {
|
|
9134
|
+
const symbol = QUANTITY_BASE[quantity];
|
|
9135
|
+
if (symbol === "none" || symbol === "device") return void 0;
|
|
9136
|
+
return symbol;
|
|
9137
|
+
}
|
|
9138
|
+
function readingMeta(quantity, extras) {
|
|
9139
|
+
return {
|
|
9140
|
+
quantity,
|
|
9141
|
+
...extras?.precision !== void 0 ? { precision: extras.precision } : {},
|
|
9142
|
+
...extras?.label !== void 0 ? { label: extras.label } : {}
|
|
9143
|
+
};
|
|
9144
|
+
}
|
|
9145
|
+
/** Shared Zod schemas used across streaming capabilities. */
|
|
9146
|
+
var CamProfileSchema = _enum([
|
|
9147
|
+
"high",
|
|
9148
|
+
"mid",
|
|
9149
|
+
"low"
|
|
9150
|
+
]);
|
|
9151
|
+
/**
|
|
9152
|
+
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
9153
|
+
*
|
|
9154
|
+
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
9155
|
+
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
9156
|
+
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
9157
|
+
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
9158
|
+
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
9159
|
+
* "the lowest AVAILABLE native rung" (D537).
|
|
9160
|
+
*/
|
|
9161
|
+
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
9162
|
+
var CamStreamKindSchema = _enum([
|
|
9163
|
+
"pull-rtsp",
|
|
9164
|
+
"pull-rtmp",
|
|
9165
|
+
"pull-http",
|
|
9166
|
+
"pull-flv",
|
|
9167
|
+
"pull-rfc4571",
|
|
9168
|
+
"push-annexb",
|
|
9169
|
+
"derived"
|
|
9170
|
+
]);
|
|
9171
|
+
var CamStreamResolutionSchema = object({
|
|
9172
|
+
width: number().int().positive().meta(readingMeta("none")),
|
|
9173
|
+
height: number().int().positive().meta(readingMeta("none"))
|
|
9174
|
+
});
|
|
9175
|
+
var CameraStreamSchema = object({
|
|
9176
|
+
/** Stable, provider-assigned id unique within the (deviceId) scope. */
|
|
9177
|
+
camStreamId: string().min(1),
|
|
9178
|
+
deviceId: number().int().nonnegative(),
|
|
9179
|
+
kind: CamStreamKindSchema,
|
|
9180
|
+
/** Required for pull-* kinds. Ignored for push-annexb. */
|
|
9181
|
+
url: string().optional(),
|
|
9182
|
+
codec: string().optional(),
|
|
9183
|
+
resolution: CamStreamResolutionSchema.optional(),
|
|
9184
|
+
fps: number().positive().optional(),
|
|
9185
|
+
/** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
|
|
9186
|
+
label: string().optional(),
|
|
9187
|
+
/**
|
|
9188
|
+
* Device-level features the publisher advertised (e.g. `battery-operated`).
|
|
9189
|
+
* The broker, snapshot orchestrator, and prebuffer manager all consult
|
|
9190
|
+
* this list to derive policy — relaxed stall watchdog for battery
|
|
9191
|
+
* cams, prebuffer off by default, longer snapshot rate-limit, etc.
|
|
9192
|
+
*
|
|
9193
|
+
* Single source of truth replacing per-stream flags like the
|
|
9194
|
+
* historical `allowStall`: if the publisher knows the camera is
|
|
9195
|
+
* battery-powered, every downstream service derives the right policy
|
|
9196
|
+
* from this list.
|
|
9197
|
+
*/
|
|
9198
|
+
deviceFeatures: array(string()).optional(),
|
|
9199
|
+
/**
|
|
9200
|
+
* Whether this stream participates in the broker's automatic profile
|
|
9201
|
+
* assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
|
|
9202
|
+
* use `false` when they want a stream to be SELECTABLE in the UI but not
|
|
9203
|
+
* picked by default — e.g. Reolink publishes its native Baichuan streams
|
|
9204
|
+
* as `autoEligible: true` (the recommended path) and its RTSP / RTMP
|
|
9205
|
+
* mirrors as `autoEligible: false` (still pickable per slot, just not
|
|
9206
|
+
* the auto choice). Manual `assignProfile` calls remain valid for
|
|
9207
|
+
* non-eligible streams.
|
|
9208
|
+
*/
|
|
9209
|
+
autoEligible: boolean().optional(),
|
|
9210
|
+
/**
|
|
9211
|
+
* The profile slot the PUBLISHER says this stream is: `low` for a stream
|
|
9212
|
+
* whose only sensible role is the low-bandwidth one, `high` for a main
|
|
9213
|
+
* stream. Consulted by the broker's automatic ranking
|
|
9214
|
+
* (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
|
|
9215
|
+
* an operator's manual assignment still wins, and an absent hint is the
|
|
9216
|
+
* pixel-ranked default every camera had before this field existed.
|
|
9217
|
+
*
|
|
9218
|
+
* Why it exists: a camera with ONE stream ranked to `mid` — the slot no
|
|
9219
|
+
* storage class is written for on its own (`recordings` is high|mid,
|
|
9220
|
+
* `recordingsLow` is low) and the one profile scrub does not read. The robot
|
|
9221
|
+
* camera's single 720p relay is a `low` in every respect except the slot the
|
|
9222
|
+
* heuristic put it in.
|
|
9223
|
+
*/
|
|
9224
|
+
profileHint: CamProfileSchema.optional(),
|
|
9225
|
+
/**
|
|
9226
|
+
* Transport-specific opaque metadata. The broker passes it through to
|
|
9227
|
+
* the source reader without inspecting it. Currently used by
|
|
9228
|
+
* `pull-rfc4571` streams to carry the upstream SDP (so the reader can
|
|
9229
|
+
* route RTP packets to the right depacketizer without an in-band
|
|
9230
|
+
* DESCRIBE phase). Other kinds typically leave it undefined.
|
|
9231
|
+
*/
|
|
9232
|
+
metadata: record(string(), unknown()).optional()
|
|
9233
|
+
});
|
|
9234
|
+
var ProfileSlotStatusSchema = _enum([
|
|
9235
|
+
"unassigned",
|
|
9236
|
+
"idle",
|
|
9237
|
+
"connecting",
|
|
9238
|
+
"streaming",
|
|
9239
|
+
"error"
|
|
9240
|
+
]);
|
|
9241
|
+
var ProfileSlotSchema = object({
|
|
9242
|
+
deviceId: number().int().nonnegative(),
|
|
9243
|
+
profile: CamProfileSchema,
|
|
9244
|
+
/** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
|
|
9245
|
+
brokerId: string(),
|
|
9246
|
+
/** `null` when the profile is unassigned. */
|
|
9247
|
+
sourceCamStreamId: string().nullable(),
|
|
9248
|
+
status: ProfileSlotStatusSchema,
|
|
9249
|
+
resolution: CamStreamResolutionSchema.optional(),
|
|
9250
|
+
codec: string().optional(),
|
|
9251
|
+
preBufferSec: number().nonnegative().optional(),
|
|
9252
|
+
errorMessage: string().optional()
|
|
9253
|
+
});
|
|
9254
|
+
/**
|
|
9255
|
+
* Zod schema for StreamSourceEntry — the canonical stream descriptor
|
|
9256
|
+
* exposed by ICameraDevice.getStreamSources() and consumed by the broker.
|
|
9257
|
+
*/
|
|
9258
|
+
var StreamSourceEntrySchema$1 = object({
|
|
9259
|
+
id: string(),
|
|
9260
|
+
label: string(),
|
|
9261
|
+
protocol: _enum([
|
|
9262
|
+
"rtsp",
|
|
9263
|
+
"rtmp",
|
|
9264
|
+
"annexb",
|
|
9265
|
+
"http-mjpeg",
|
|
9266
|
+
"webrtc",
|
|
9267
|
+
"custom"
|
|
9268
|
+
]),
|
|
9269
|
+
url: string().optional(),
|
|
9270
|
+
resolution: object({
|
|
9271
|
+
width: number(),
|
|
9272
|
+
height: number()
|
|
9273
|
+
}).readonly().optional(),
|
|
9274
|
+
fps: number().optional(),
|
|
9275
|
+
bitrate: number().optional(),
|
|
9276
|
+
codec: string().optional(),
|
|
9277
|
+
profileHint: CamProfileSchema.optional()
|
|
9278
|
+
});
|
|
9279
|
+
object({
|
|
9280
|
+
type: string(),
|
|
9281
|
+
url: string(),
|
|
9282
|
+
videoCodec: string().optional(),
|
|
9283
|
+
audioCodec: string().optional(),
|
|
9284
|
+
metadata: record(string(), unknown()).readonly().optional()
|
|
9285
|
+
});
|
|
9286
|
+
/**
|
|
9287
|
+
* Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
|
|
9288
|
+
* frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
|
|
9289
|
+
* interface in `interfaces/frame-handle.ts` field-for-field so a cap method
|
|
9290
|
+
* (the runner's native-crop and pipeline-executor inputs) can carry handles
|
|
9291
|
+
* over tRPC / Moleculer.
|
|
9292
|
+
*
|
|
9293
|
+
* The `satisfies` assertion below pins this schema to the interface: if a
|
|
9294
|
+
* field is added to / removed from `FrameHandle` without a matching schema
|
|
9295
|
+
* edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
|
|
9296
|
+
* `FrameHandle` and the build fails — no silent drift.
|
|
9297
|
+
*/
|
|
9298
|
+
var FrameHandleSchema = object({
|
|
9299
|
+
shmId: string(),
|
|
9300
|
+
slot: number().int().nonnegative(),
|
|
9301
|
+
seq: number().int().nonnegative(),
|
|
9302
|
+
width: number().int().positive(),
|
|
9303
|
+
height: number().int().positive(),
|
|
9304
|
+
format: _enum([
|
|
9305
|
+
"jpeg",
|
|
9306
|
+
"rgb",
|
|
9307
|
+
"bgr",
|
|
9308
|
+
"yuv420",
|
|
9309
|
+
"gray"
|
|
9310
|
+
]),
|
|
9311
|
+
pts: number(),
|
|
9312
|
+
byteLength: number().int().nonnegative(),
|
|
9313
|
+
nodeId: string(),
|
|
9314
|
+
slotCount: number().int().positive()
|
|
9315
|
+
});
|
|
9316
|
+
/**
|
|
9317
|
+
* Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
|
|
9318
|
+
* `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
|
|
9319
|
+
* sample bytes plus the track parameters a consumer needs to interpret them.
|
|
9320
|
+
*
|
|
9321
|
+
* Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
|
|
9322
|
+
* Float32 PCM), so unlike video frames they ship their bytes INLINE over
|
|
9323
|
+
* tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
|
|
9324
|
+
* (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
|
|
9325
|
+
* it.
|
|
9326
|
+
*/
|
|
9327
|
+
var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
|
|
9328
|
+
var DecodedAudioChunkSchema = object({
|
|
9329
|
+
data: _instanceof(Uint8Array),
|
|
9330
|
+
sampleRate: number().int().positive(),
|
|
9331
|
+
channels: number().int().positive(),
|
|
9332
|
+
timestamp: number(),
|
|
9333
|
+
/**
|
|
9334
|
+
* Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
|
|
9335
|
+
* byte, for any peer that never heard of this field. A coded window
|
|
9336
|
+
* (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
|
|
9337
|
+
* subscription that DECLARED it accepts one, so absence can never mean
|
|
9338
|
+
* "coded bytes a consumer will read as floats" (D455).
|
|
9339
|
+
*/
|
|
9340
|
+
format: AudioChunkFormatSchema.optional()
|
|
9341
|
+
});
|
|
9342
|
+
/**
|
|
9343
|
+
* Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
|
|
9344
|
+
* its bytes inline: a consumer asks the broker for the decoded audio-chunk
|
|
9345
|
+
* stream of one broker; the broker keeps a per-subscription bounded FIFO queue
|
|
9346
|
+
* the consumer drains via `pullAudioChunks`.
|
|
9347
|
+
*/
|
|
9348
|
+
var SubscribeAudioChunksInputSchema = object({
|
|
9349
|
+
brokerId: string(),
|
|
9350
|
+
/** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
|
|
9351
|
+
tag: string().optional(),
|
|
9352
|
+
/**
|
|
9353
|
+
* Byte formats this subscriber can READ, best first. The broker serves the
|
|
9354
|
+
* chunk's own format when it is in this list and expands to `f32le`
|
|
9355
|
+
* otherwise, so a subscriber is never handed bytes it cannot interpret.
|
|
9356
|
+
*
|
|
9357
|
+
* Absent (or without the source format) means `f32le` — the behaviour every
|
|
9358
|
+
* subscriber had before D455, unchanged. This is the negotiation half of
|
|
9359
|
+
* the source-bytes lever: it is what lets the broker and its consumers
|
|
9360
|
+
* deploy one at a time across three nodes.
|
|
9361
|
+
*/
|
|
9362
|
+
accept: array(AudioChunkFormatSchema).readonly().optional()
|
|
9363
|
+
});
|
|
9364
|
+
/** Result of `stream-broker.subscribeAudioChunks`. */
|
|
9365
|
+
var SubscribeAudioChunksResultSchema = object({
|
|
9366
|
+
/** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
|
|
9367
|
+
subscriptionId: string() });
|
|
9368
|
+
var BrokerStatsSchema = object({
|
|
9369
|
+
status: _enum([
|
|
9370
|
+
"idle",
|
|
9371
|
+
"connecting",
|
|
9372
|
+
"streaming",
|
|
9373
|
+
"error",
|
|
9374
|
+
"stopped"
|
|
9375
|
+
]),
|
|
9376
|
+
inputFps: number(),
|
|
9377
|
+
encodedSubscribers: number(),
|
|
9378
|
+
uptimeMs: number(),
|
|
9379
|
+
bitrateKbps: number(),
|
|
9380
|
+
idrIntervalMs: number(),
|
|
9381
|
+
codec: string().optional(),
|
|
9382
|
+
totalBytes: number(),
|
|
9383
|
+
packetCount: number(),
|
|
9384
|
+
rtspClients: number(),
|
|
9385
|
+
pipeClients: number(),
|
|
9386
|
+
preBufferSec: number(),
|
|
9387
|
+
preBufferMs: number(),
|
|
9388
|
+
preBufferPackets: number(),
|
|
9389
|
+
/**
|
|
9390
|
+
* Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
|
|
9391
|
+
* when the stream has no audio track or the broker is in cold start.
|
|
9392
|
+
* `supported = false` means the codec was detected but the local
|
|
9393
|
+
* decoder pipeline cannot produce PCM chunks (e.g. AAC without the
|
|
9394
|
+
* AAC pipeline wired). Surfaced in the UI device-overview so operators
|
|
9395
|
+
* can pre-pick the audio-analysis model that matches the codec.
|
|
9396
|
+
*/
|
|
9397
|
+
audio: object({
|
|
9398
|
+
codec: string(),
|
|
9399
|
+
sampleRate: number(),
|
|
9400
|
+
channels: number(),
|
|
9401
|
+
supported: boolean()
|
|
9402
|
+
}).nullable().optional(),
|
|
9403
|
+
/**
|
|
9404
|
+
* BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
|
|
9405
|
+
* distributing none of the device's audio, on live or recording.
|
|
9406
|
+
*
|
|
9407
|
+
* Present so a silent camera can be told apart from a broken one on the
|
|
9408
|
+
* stream panel itself, without cross-referencing the switch group: a
|
|
9409
|
+
* broker holding an `audio` track descriptor while `audioMuted` is true is
|
|
9410
|
+
* working exactly as asked. `audioMutedDropped` counts the audio units
|
|
9411
|
+
* thrown away since the current dial — it is how you confirm from stats
|
|
9412
|
+
* alone that the mute is on the packet path and not merely persisted.
|
|
9413
|
+
*/
|
|
9414
|
+
audioMuted: boolean().optional(),
|
|
9415
|
+
audioMutedDropped: number().optional()
|
|
9416
|
+
});
|
|
9417
|
+
/**
|
|
9418
|
+
* Exporter-facing "profile restream" entry. Returned by
|
|
9419
|
+
* `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
|
|
9420
|
+
* (high/mid/low). `brokerId` is the PROFILE-keyed broker id
|
|
9421
|
+
* (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
|
|
9422
|
+
* RTSP restream that aliases the profile's assigned source broker, so
|
|
9423
|
+
* every consumer dialling a profile converges on the same single pull.
|
|
9424
|
+
* `codec`/`resolution` describe the assigned SOURCE camStream (for the
|
|
9425
|
+
* `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
|
|
9426
|
+
* (raw per-camStream, live-view only).
|
|
9427
|
+
*/
|
|
9428
|
+
var ProfileRtspEntrySchema = object({
|
|
9429
|
+
profile: CamProfileSchema,
|
|
9430
|
+
/** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
|
|
9431
|
+
brokerId: string(),
|
|
9432
|
+
url: string(),
|
|
9433
|
+
mutedUrl: string(),
|
|
9434
|
+
enabled: boolean(),
|
|
9435
|
+
codec: string().optional(),
|
|
9436
|
+
resolution: CamStreamResolutionSchema.optional()
|
|
9437
|
+
});
|
|
9438
|
+
/**
|
|
9439
|
+
* The model weight formats a catalog entry can ship. Schema-free on purpose:
|
|
9440
|
+
* `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
|
|
9441
|
+
* (the model downloader in `@camstack/server/system`) needs only the list and must
|
|
9442
|
+
* reach it through `@camstack/types/addon` without building the cap schema
|
|
9443
|
+
* graph (D28).
|
|
9444
|
+
*/
|
|
9445
|
+
var MODEL_FORMATS = [
|
|
9446
|
+
"onnx",
|
|
9447
|
+
"coreml",
|
|
9448
|
+
"openvino",
|
|
9449
|
+
"tflite",
|
|
9450
|
+
"pt",
|
|
9451
|
+
"gguf"
|
|
9452
|
+
];
|
|
8955
9453
|
/**
|
|
8956
9454
|
* Per-call node pinning for `ctx.api` capability calls.
|
|
8957
9455
|
*
|
|
@@ -9211,6 +9709,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9211
9709
|
"device-ops",
|
|
9212
9710
|
"device-provider",
|
|
9213
9711
|
"device-removal-hook",
|
|
9712
|
+
"device-runs",
|
|
9214
9713
|
"device-state",
|
|
9215
9714
|
"device-status",
|
|
9216
9715
|
"dishwasher",
|
|
@@ -9252,6 +9751,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9252
9751
|
"mesh-network",
|
|
9253
9752
|
"meter",
|
|
9254
9753
|
"metrics-provider",
|
|
9754
|
+
"migrations",
|
|
9255
9755
|
"model-convert",
|
|
9256
9756
|
"model-distributor",
|
|
9257
9757
|
"motion",
|
|
@@ -11154,379 +11654,7 @@ object({
|
|
|
11154
11654
|
params: record(string(), union([string(), number()])).optional()
|
|
11155
11655
|
});
|
|
11156
11656
|
//#endregion
|
|
11157
|
-
//#region ../types/dist/
|
|
11158
|
-
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11159
|
-
DeviceType["Camera"] = "camera";
|
|
11160
|
-
DeviceType["Hub"] = "hub";
|
|
11161
|
-
DeviceType["Light"] = "light";
|
|
11162
|
-
DeviceType["Siren"] = "siren";
|
|
11163
|
-
DeviceType["Switch"] = "switch";
|
|
11164
|
-
DeviceType["Sensor"] = "sensor";
|
|
11165
|
-
DeviceType["Thermostat"] = "thermostat";
|
|
11166
|
-
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
11167
|
-
* `climate-control` cap surface with `Thermostat` but renders a
|
|
11168
|
-
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
11169
|
-
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
11170
|
-
* reusable by other AC integrations. */
|
|
11171
|
-
DeviceType["Climate"] = "climate";
|
|
11172
|
-
DeviceType["Button"] = "button";
|
|
11173
|
-
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
11174
|
-
* event vocabulary verbatim (no normalization). Installed with the
|
|
11175
|
-
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
11176
|
-
* HA bus events (e.g. `zha_event`, generic). */
|
|
11177
|
-
DeviceType["EventEmitter"] = "event-emitter";
|
|
11178
|
-
/** Firmware/software update entity — current vs available version,
|
|
11179
|
-
* updatable flag, update state, and an install action. Installed with
|
|
11180
|
-
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
11181
|
-
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
11182
|
-
DeviceType["Update"] = "update";
|
|
11183
|
-
DeviceType["Generic"] = "generic";
|
|
11184
|
-
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
11185
|
-
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
11186
|
-
* endpoint; the `notifier` cap defines the send surface. */
|
|
11187
|
-
DeviceType["Notifier"] = "notifier";
|
|
11188
|
-
/** Pre-recorded action sequence with optional parameters
|
|
11189
|
-
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
11190
|
-
DeviceType["Script"] = "script";
|
|
11191
|
-
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
11192
|
-
* trigger surface exposed via `automation-control` cap. */
|
|
11193
|
-
DeviceType["Automation"] = "automation";
|
|
11194
|
-
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
11195
|
-
DeviceType["Lock"] = "lock";
|
|
11196
|
-
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
11197
|
-
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
11198
|
-
DeviceType["Cover"] = "cover";
|
|
11199
|
-
/** Pipe / water / gas valve with open/close/stop and optional
|
|
11200
|
-
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
11201
|
-
* modelled on the same open/closed lifecycle. */
|
|
11202
|
-
DeviceType["Valve"] = "valve";
|
|
11203
|
-
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
11204
|
-
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
11205
|
-
* modelled on the same target / mode lifecycle. */
|
|
11206
|
-
DeviceType["Humidifier"] = "humidifier";
|
|
11207
|
-
/** Water heater / boiler with target temperature + operation mode +
|
|
11208
|
-
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
11209
|
-
* climate-family actuator. */
|
|
11210
|
-
DeviceType["WaterHeater"] = "water-heater";
|
|
11211
|
-
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
11212
|
-
DeviceType["Fan"] = "fan";
|
|
11213
|
-
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
11214
|
-
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
11215
|
-
DeviceType["MediaPlayer"] = "media-player";
|
|
11216
|
-
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
11217
|
-
* `alarm-panel` cap. */
|
|
11218
|
-
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
11219
|
-
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
11220
|
-
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
11221
|
-
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
11222
|
-
* TextControl / DateTimeControl. */
|
|
11223
|
-
DeviceType["Control"] = "control";
|
|
11224
|
-
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
11225
|
-
* `presence` cap. */
|
|
11226
|
-
DeviceType["Presence"] = "presence";
|
|
11227
|
-
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
11228
|
-
* `weather` cap. */
|
|
11229
|
-
DeviceType["Weather"] = "weather";
|
|
11230
|
-
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
11231
|
-
DeviceType["Vacuum"] = "vacuum";
|
|
11232
|
-
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
11233
|
-
* `lawn-mower-control` cap. */
|
|
11234
|
-
DeviceType["LawnMower"] = "lawn-mower";
|
|
11235
|
-
/** Physical HA device group — parent container for entity-children
|
|
11236
|
-
* adopted from a single HA device entry. Not renderable as a
|
|
11237
|
-
* standalone device; exists only to anchor child entities. */
|
|
11238
|
-
DeviceType["Container"] = "container";
|
|
11239
|
-
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
11240
|
-
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
11241
|
-
DeviceType["Image"] = "image";
|
|
11242
|
-
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
11243
|
-
* level, battery, desiccant life, feeding state and manual-feed /
|
|
11244
|
-
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
11245
|
-
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
11246
|
-
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
11247
|
-
* integrations sharing the same food/desiccant/hopper surface. */
|
|
11248
|
-
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11249
|
-
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11250
|
-
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11251
|
-
* of a container and is BUILT as a composed device (D967). */
|
|
11252
|
-
DeviceType["PetFountain"] = "pet-fountain";
|
|
11253
|
-
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
11254
|
-
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
11255
|
-
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
11256
|
-
* A plug-built dishwasher carries running-state and power instead. */
|
|
11257
|
-
DeviceType["Dishwasher"] = "dishwasher";
|
|
11258
|
-
/** Washing machine. A plug-built one is running while its power stays
|
|
11259
|
-
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
11260
|
-
DeviceType["Washer"] = "washer";
|
|
11261
|
-
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
11262
|
-
DeviceType["Dryer"] = "dryer";
|
|
11263
|
-
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
11264
|
-
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
11265
|
-
* address, connection state) and the actions the box really supports
|
|
11266
|
-
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
11267
|
-
* adopted beside it — and its connection settings stay on the integration. */
|
|
11268
|
-
DeviceType["Gateway"] = "gateway";
|
|
11269
|
-
return DeviceType;
|
|
11270
|
-
}({});
|
|
11271
|
-
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
11272
|
-
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
11273
|
-
DeviceFeature["Rebootable"] = "rebootable";
|
|
11274
|
-
/**
|
|
11275
|
-
* Device supports an on-demand re-sync of its derived spec with its
|
|
11276
|
-
* upstream source — drives the generic Re-sync button. The owning
|
|
11277
|
-
* provider implements the action via the `device-adoption.resync` cap.
|
|
11278
|
-
*/
|
|
11279
|
-
DeviceFeature["Resyncable"] = "resyncable";
|
|
11280
|
-
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
11281
|
-
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
11282
|
-
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
11283
|
-
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
11284
|
-
/**
|
|
11285
|
-
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
11286
|
-
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
11287
|
-
* camera ships autotrack — the admin UI uses this flag to gate
|
|
11288
|
-
* the autotrack toggle / settings card without re-deriving from
|
|
11289
|
-
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
11290
|
-
* driver sets this feature when probe confirms the firmware
|
|
11291
|
-
* surface, and registers the cap in the same code path.
|
|
11292
|
-
*/
|
|
11293
|
-
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
11294
|
-
/**
|
|
11295
|
-
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
11296
|
-
* motion detection automatically activates this device. Mirrors
|
|
11297
|
-
* `motion-trigger` cap registration: drivers set this feature in the
|
|
11298
|
-
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
11299
|
-
*
|
|
11300
|
-
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
11301
|
-
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
11302
|
-
* filters that want "all devices with on-motion behaviour".
|
|
11303
|
-
*/
|
|
11304
|
-
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
11305
|
-
/** Light supports rgb-triplet color via `color` cap. */
|
|
11306
|
-
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
11307
|
-
/** Light supports HSV color via `color` cap. */
|
|
11308
|
-
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
11309
|
-
/** Light supports color-temperature (mired) via `color` cap. */
|
|
11310
|
-
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
11311
|
-
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
11312
|
-
* targetHigh). Gates the range slider UI. */
|
|
11313
|
-
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
11314
|
-
/** Thermostat exposes target humidity and/or current humidity
|
|
11315
|
-
* readings. Gates the humidity controls. */
|
|
11316
|
-
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
11317
|
-
/** Thermostat exposes a fan-mode selector. */
|
|
11318
|
-
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
11319
|
-
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
11320
|
-
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
11321
|
-
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
11322
|
-
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
11323
|
-
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
11324
|
-
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
11325
|
-
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
11326
|
-
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
11327
|
-
/** Cover exposes intermediate position control (0..100). Gates the
|
|
11328
|
-
* position slider UI. */
|
|
11329
|
-
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
11330
|
-
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
11331
|
-
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
11332
|
-
/** Valve exposes intermediate position control (0..100). Gates the
|
|
11333
|
-
* position slider / drag surface UI. */
|
|
11334
|
-
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
11335
|
-
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
11336
|
-
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
11337
|
-
/** Fan exposes a preset mode selector. */
|
|
11338
|
-
DeviceFeature["FanPreset"] = "fan-preset";
|
|
11339
|
-
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
11340
|
-
* ceiling fans. */
|
|
11341
|
-
DeviceFeature["FanDirection"] = "fan-direction";
|
|
11342
|
-
/** Fan exposes an oscillation toggle. */
|
|
11343
|
-
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
11344
|
-
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
11345
|
-
* field on the UI lock-controls panel. */
|
|
11346
|
-
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
11347
|
-
/** Lock supports a latch-release ("open door") action distinct from
|
|
11348
|
-
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
11349
|
-
* `supported_features`. Gates the Open Door button in the UI. */
|
|
11350
|
-
DeviceFeature["LockOpen"] = "lock-open";
|
|
11351
|
-
/** Media player exposes a seek-to-position surface. */
|
|
11352
|
-
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
11353
|
-
/** Media player exposes a volume-level setter. */
|
|
11354
|
-
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
11355
|
-
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
11356
|
-
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
11357
|
-
/** Media player exposes a shuffle toggle. */
|
|
11358
|
-
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
11359
|
-
/** Media player exposes a repeat mode (off / all / one). */
|
|
11360
|
-
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
11361
|
-
/** Media player exposes a source / input selector. */
|
|
11362
|
-
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
11363
|
-
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
11364
|
-
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
11365
|
-
/** Media player exposes next-track. */
|
|
11366
|
-
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
11367
|
-
/** Media player exposes previous-track. */
|
|
11368
|
-
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
11369
|
-
/** Media player exposes stop distinct from pause. */
|
|
11370
|
-
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
11371
|
-
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
11372
|
-
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
11373
|
-
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
11374
|
-
* addition to a textual location. */
|
|
11375
|
-
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
11376
|
-
/** Notifier accepts an inline / URL image attachment. */
|
|
11377
|
-
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
11378
|
-
/** Notifier accepts a priority hint (high/normal/low). */
|
|
11379
|
-
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
11380
|
-
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
11381
|
-
* fields. */
|
|
11382
|
-
DeviceFeature["NotifierData"] = "notifier-data";
|
|
11383
|
-
/** Notifier supports interactive action buttons / callbacks. */
|
|
11384
|
-
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
11385
|
-
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
11386
|
-
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
11387
|
-
/** Script runner accepts a variables map on each run invocation. */
|
|
11388
|
-
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
11389
|
-
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
11390
|
-
* automation's actions while bypassing its condition block. */
|
|
11391
|
-
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
11392
|
-
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
11393
|
-
* map tile in the vacuum UI. */
|
|
11394
|
-
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
11395
|
-
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
11396
|
-
* switches group. */
|
|
11397
|
-
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
11398
|
-
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
11399
|
-
* mower map tile. */
|
|
11400
|
-
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
11401
|
-
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
11402
|
-
* spots) — gates the Mow action + map selectors. */
|
|
11403
|
-
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
11404
|
-
return DeviceFeature;
|
|
11405
|
-
}({});
|
|
11406
|
-
/**
|
|
11407
|
-
* Semantic role a device plays within its parent. Populated by driver
|
|
11408
|
-
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
11409
|
-
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
11410
|
-
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
11411
|
-
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
11412
|
-
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
11413
|
-
*
|
|
11414
|
-
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
11415
|
-
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
11416
|
-
*/
|
|
11417
|
-
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
11418
|
-
DeviceRole["Siren"] = "siren";
|
|
11419
|
-
DeviceRole["Floodlight"] = "floodlight";
|
|
11420
|
-
DeviceRole["Spotlight"] = "spotlight";
|
|
11421
|
-
DeviceRole["PirSensor"] = "pir-sensor";
|
|
11422
|
-
DeviceRole["Chime"] = "chime";
|
|
11423
|
-
DeviceRole["Autotrack"] = "autotrack";
|
|
11424
|
-
DeviceRole["Nightvision"] = "nightvision";
|
|
11425
|
-
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
11426
|
-
DeviceRole["Doorbell"] = "doorbell";
|
|
11427
|
-
/** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
|
|
11428
|
-
DeviceRole["CoverBlind"] = "cover-blind";
|
|
11429
|
-
/** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
|
|
11430
|
-
DeviceRole["CoverShutter"] = "cover-shutter";
|
|
11431
|
-
/** Exterior awning. HA `device_class: awning`. */
|
|
11432
|
-
DeviceRole["CoverAwning"] = "cover-awning";
|
|
11433
|
-
/** Curtain / drape. HA `device_class: curtain`. */
|
|
11434
|
-
DeviceRole["CoverCurtain"] = "cover-curtain";
|
|
11435
|
-
/** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
|
|
11436
|
-
DeviceRole["CoverGarage"] = "cover-garage";
|
|
11437
|
-
/** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
|
|
11438
|
-
DeviceRole["CoverGate"] = "cover-gate";
|
|
11439
|
-
/** Door. HA `device_class: door`. A security actuator: exports gate it. */
|
|
11440
|
-
DeviceRole["CoverDoor"] = "cover-door";
|
|
11441
|
-
/** Window. HA `device_class: window`. */
|
|
11442
|
-
DeviceRole["CoverWindow"] = "cover-window";
|
|
11443
|
-
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
11444
|
-
* real Switch device for UI rendering / export adapters. */
|
|
11445
|
-
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
11446
|
-
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
11447
|
-
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
11448
|
-
DeviceRole["Outlet"] = "outlet";
|
|
11449
|
-
/** Generic motion / occupancy / moving event source. Distinct from
|
|
11450
|
-
* the camera accessory PirSensor role: that one is a camera child;
|
|
11451
|
-
* this is a standalone HA / 3rd-party motion sensor. */
|
|
11452
|
-
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
11453
|
-
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
11454
|
-
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
11455
|
-
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
11456
|
-
DeviceRole["COSensor"] = "co-sensor";
|
|
11457
|
-
DeviceRole["GasSensor"] = "gas-sensor";
|
|
11458
|
-
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
11459
|
-
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
11460
|
-
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
11461
|
-
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
11462
|
-
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
11463
|
-
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
11464
|
-
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
11465
|
-
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
11466
|
-
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
11467
|
-
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
11468
|
-
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
11469
|
-
DeviceRole["WindSensor"] = "wind-sensor";
|
|
11470
|
-
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
11471
|
-
DeviceRole["RainSensor"] = "rain-sensor";
|
|
11472
|
-
/** UV index (weather-station `uv-sensor` cap). */
|
|
11473
|
-
DeviceRole["UvSensor"] = "uv-sensor";
|
|
11474
|
-
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
11475
|
-
* Distinct from AmbientLightSensor (lux). */
|
|
11476
|
-
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
11477
|
-
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
11478
|
-
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
11479
|
-
DeviceRole["PowerSensor"] = "power-sensor";
|
|
11480
|
-
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
11481
|
-
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
11482
|
-
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
11483
|
-
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
11484
|
-
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
11485
|
-
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
11486
|
-
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
11487
|
-
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
11488
|
-
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
11489
|
-
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
11490
|
-
* `attributes.options`). */
|
|
11491
|
-
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
11492
|
-
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
11493
|
-
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
11494
|
-
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
11495
|
-
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
11496
|
-
/** Last-resort fallback when nothing else matches. */
|
|
11497
|
-
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
11498
|
-
DeviceRole["NumericControl"] = "numeric-control";
|
|
11499
|
-
DeviceRole["SelectControl"] = "select-control";
|
|
11500
|
-
DeviceRole["TextControl"] = "text-control";
|
|
11501
|
-
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
11502
|
-
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
11503
|
-
* rich features (image, priority, channel routing). */
|
|
11504
|
-
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
11505
|
-
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
11506
|
-
* `notify.discord_*`, etc.). */
|
|
11507
|
-
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
11508
|
-
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
11509
|
-
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
11510
|
-
/** Fallback when the notifier service name doesn't match a known
|
|
11511
|
-
* pattern. */
|
|
11512
|
-
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
11513
|
-
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
11514
|
-
* cap. A `DeviceType.Generic` device. */
|
|
11515
|
-
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
11516
|
-
return DeviceRole;
|
|
11517
|
-
}({});
|
|
11518
|
-
/**
|
|
11519
|
-
import { errMsg } from '@camstack/types'
|
|
11520
|
-
* Extract a human-readable message from an unknown error value.
|
|
11521
|
-
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
11522
|
-
*/
|
|
11523
|
-
function errMsg(err) {
|
|
11524
|
-
if (err instanceof Error) return err.message;
|
|
11525
|
-
if (typeof err === "string") return err;
|
|
11526
|
-
return String(err);
|
|
11527
|
-
}
|
|
11528
|
-
//#endregion
|
|
11529
|
-
//#region ../types/dist/composition-C7WdThmP.mjs
|
|
11657
|
+
//#region ../types/dist/composition-BiKWlUCc.mjs
|
|
11530
11658
|
/**
|
|
11531
11659
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11532
11660
|
*
|
|
@@ -12459,7 +12587,15 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12459
12587
|
thresholdWatts: number().positive(),
|
|
12460
12588
|
startDwellMs: number().int().nonnegative(),
|
|
12461
12589
|
stopDwellMs: number().int().nonnegative(),
|
|
12462
|
-
member: HelperMemberSchema
|
|
12590
|
+
member: HelperMemberSchema,
|
|
12591
|
+
/**
|
|
12592
|
+
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12593
|
+
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12594
|
+
* that plug (power-source link of a NEW-target composition).
|
|
12595
|
+
*/
|
|
12596
|
+
powerSwitch: HelperMemberSchema.optional(),
|
|
12597
|
+
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12598
|
+
offSeconds: number().optional()
|
|
12463
12599
|
}),
|
|
12464
12600
|
object({
|
|
12465
12601
|
kind: literal("statistic"),
|
|
@@ -12745,6 +12881,71 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
|
|
|
12745
12881
|
var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
|
|
12746
12882
|
new Set(EVENT_OWNER_TYPES);
|
|
12747
12883
|
/**
|
|
12884
|
+
* What a published package needs from the rest of the install (D989).
|
|
12885
|
+
*
|
|
12886
|
+
* One published framework package (`@camstack/server`) carries the kernel, the
|
|
12887
|
+
* builtins and the capability contracts, so "which addon runs on which
|
|
12888
|
+
* framework" is a question of version, and the answer is DECLARED by the
|
|
12889
|
+
* package that knows it:
|
|
12890
|
+
*
|
|
12891
|
+
* - an addon says the oldest server it runs on (`requires.server: ">=X"`);
|
|
12892
|
+
* - the server says the oldest addon it works with
|
|
12893
|
+
* (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
|
|
12894
|
+
* around it (`requires.wrapper: { desktop, image }`);
|
|
12895
|
+
* - a wrapper (the desktop app, the container starter) says which server
|
|
12896
|
+
* range it can host, and reports its own kind and version.
|
|
12897
|
+
*
|
|
12898
|
+
* The publish workflow STAMPS these after the version bump
|
|
12899
|
+
* (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
|
|
12900
|
+
*
|
|
12901
|
+
* A requirement that cannot be checked is NOT met: a shell that reports no
|
|
12902
|
+
* version counts as below the minimum, and an unreadable running version is
|
|
12903
|
+
* named in the result (a failed read is never a pass).
|
|
12904
|
+
*/
|
|
12905
|
+
/** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
|
|
12906
|
+
var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
|
|
12907
|
+
object({
|
|
12908
|
+
/** The oldest `@camstack/server` this package runs on. */
|
|
12909
|
+
server: MinVersionSchema.optional(),
|
|
12910
|
+
/** The oldest installed version of each named addon package. */
|
|
12911
|
+
addons: record(string(), MinVersionSchema).optional(),
|
|
12912
|
+
/** The oldest shell around the server, per shell kind. */
|
|
12913
|
+
wrapper: object({
|
|
12914
|
+
desktop: MinVersionSchema.optional(),
|
|
12915
|
+
image: MinVersionSchema.optional()
|
|
12916
|
+
}).optional()
|
|
12917
|
+
});
|
|
12918
|
+
/** One unmet requirement; each names the versions on both sides. */
|
|
12919
|
+
var UnmetRequirementSchema = discriminatedUnion("kind", [
|
|
12920
|
+
object({
|
|
12921
|
+
kind: literal("server-too-old"),
|
|
12922
|
+
required: string(),
|
|
12923
|
+
/** `null` — the running server version could not be read. */
|
|
12924
|
+
running: string().nullable()
|
|
12925
|
+
}),
|
|
12926
|
+
object({
|
|
12927
|
+
kind: literal("addon-too-old"),
|
|
12928
|
+
packageName: string(),
|
|
12929
|
+
required: string(),
|
|
12930
|
+
/** `null` — the addon is not installed (or its version is unreadable). */
|
|
12931
|
+
running: string().nullable()
|
|
12932
|
+
}),
|
|
12933
|
+
object({
|
|
12934
|
+
kind: literal("wrapper-too-old"),
|
|
12935
|
+
wrapper: _enum(["desktop", "image"]),
|
|
12936
|
+
required: string(),
|
|
12937
|
+
/** `null` — the wrapper reported no version. */
|
|
12938
|
+
running: string().nullable()
|
|
12939
|
+
})
|
|
12940
|
+
]);
|
|
12941
|
+
var RequiredAddonUpdateSchema = object({
|
|
12942
|
+
packageName: string(),
|
|
12943
|
+
/** The minimum the target server declares. */
|
|
12944
|
+
required: string(),
|
|
12945
|
+
/** What this node has; `null` when the addon is not installed. */
|
|
12946
|
+
installed: string().nullable()
|
|
12947
|
+
});
|
|
12948
|
+
/**
|
|
12748
12949
|
* Which integration instance a settings form is for. An addon that serves several instances
|
|
12749
12950
|
* (`instanceMode: multiple`) can show a different form per instance — an NVR integration
|
|
12750
12951
|
* needs its connection, the default integration needs the addon's own sections.
|
|
@@ -13363,6 +13564,619 @@ var CameraSwitchGroupSchema = object({
|
|
|
13363
13564
|
fetchedAt: number()
|
|
13364
13565
|
});
|
|
13365
13566
|
/**
|
|
13567
|
+
* Query filter for settings-store collections.
|
|
13568
|
+
*/
|
|
13569
|
+
var QueryFilterSchema = object({
|
|
13570
|
+
where: record(string(), unknown()).optional(),
|
|
13571
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
13572
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
13573
|
+
/** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
|
|
13574
|
+
whereNot: record(string(), unknown()).optional(),
|
|
13575
|
+
/** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
|
|
13576
|
+
* empty list excludes nothing. See `QueryFilter.whereNotIn`. */
|
|
13577
|
+
whereNotIn: record(string(), array(unknown())).optional(),
|
|
13578
|
+
orderBy: object({
|
|
13579
|
+
field: string(),
|
|
13580
|
+
direction: _enum(["asc", "desc"])
|
|
13581
|
+
}).optional(),
|
|
13582
|
+
limit: number().optional(),
|
|
13583
|
+
offset: number().optional()
|
|
13584
|
+
});
|
|
13585
|
+
/**
|
|
13586
|
+
* The predicate half of a filter, for BULK MUTATIONS.
|
|
13587
|
+
*
|
|
13588
|
+
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
13589
|
+
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
13590
|
+
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
13591
|
+
* only so the shape stays composable — the implementation REJECTS a filter
|
|
13592
|
+
* that compiles to no predicate, because that is the whole collection.
|
|
13593
|
+
*/
|
|
13594
|
+
var MutationFilterSchema = object({
|
|
13595
|
+
where: record(string(), unknown()).optional(),
|
|
13596
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
13597
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
13598
|
+
whereNot: record(string(), unknown()).optional(),
|
|
13599
|
+
whereNotIn: record(string(), array(unknown())).optional()
|
|
13600
|
+
});
|
|
13601
|
+
/**
|
|
13602
|
+
* One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
|
|
13603
|
+
*
|
|
13604
|
+
* `as` names the slot in the result, so the SAME column may be asked twice with
|
|
13605
|
+
* two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
|
|
13606
|
+
* a `Record<column, op>` shape could not express.
|
|
13607
|
+
*/
|
|
13608
|
+
var AggregateFieldSchema = object({
|
|
13609
|
+
/** Result key. */
|
|
13610
|
+
as: string().min(1),
|
|
13611
|
+
/** Column to aggregate. Must be a real column of a declared collection. */
|
|
13612
|
+
field: string().min(1),
|
|
13613
|
+
op: _enum([
|
|
13614
|
+
"sum",
|
|
13615
|
+
"min",
|
|
13616
|
+
"max"
|
|
13617
|
+
])
|
|
13618
|
+
});
|
|
13619
|
+
/**
|
|
13620
|
+
* `COUNT(*)` plus one number per requested field.
|
|
13621
|
+
*
|
|
13622
|
+
* `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
|
|
13623
|
+
* that really is 0 are different facts, and an accounting caller that renders
|
|
13624
|
+
* "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
|
|
13625
|
+
*/
|
|
13626
|
+
var AggregateResultSchema = object({
|
|
13627
|
+
count: number().int(),
|
|
13628
|
+
values: record(string(), number().nullable())
|
|
13629
|
+
});
|
|
13630
|
+
/** A single stored record: `{ id, data }`. */
|
|
13631
|
+
var SettingsRecordSchema = object({
|
|
13632
|
+
id: string(),
|
|
13633
|
+
data: record(string(), unknown())
|
|
13634
|
+
});
|
|
13635
|
+
/**
|
|
13636
|
+
* One record of a BULK insert — {@link SettingsRecordSchema} with the id made
|
|
13637
|
+
* optional.
|
|
13638
|
+
*
|
|
13639
|
+
* A separate schema rather than loosening the shared one: every other method
|
|
13640
|
+
* on this cap addresses a row BY its id, and making that field optional
|
|
13641
|
+
* everywhere would turn a forgotten key into a silently generated one on
|
|
13642
|
+
* `update` and `delete` as well.
|
|
13643
|
+
*/
|
|
13644
|
+
var BulkRecordSchema = object({
|
|
13645
|
+
id: string().optional(),
|
|
13646
|
+
data: record(string(), unknown())
|
|
13647
|
+
});
|
|
13648
|
+
/**
|
|
13649
|
+
* The database FILES one `sqlite-settings` engine process owns (D851).
|
|
13650
|
+
* Closed on purpose: a file is a thing the engine opens, backs up and gives a
|
|
13651
|
+
* serving thread, so a new one is a code change, never a string a caller
|
|
13652
|
+
* invents. `settings` is `camstack.db` and the default for every collection
|
|
13653
|
+
* that names no file.
|
|
13654
|
+
*/
|
|
13655
|
+
var DataStoreDatabaseSchema = _enum([
|
|
13656
|
+
"settings",
|
|
13657
|
+
"events",
|
|
13658
|
+
"history"
|
|
13659
|
+
]);
|
|
13660
|
+
DataStoreDatabaseSchema.options;
|
|
13661
|
+
/**
|
|
13662
|
+
* One write inside a {@link settingsStoreCapability.methods.transact} call.
|
|
13663
|
+
* The three that a "replace this set, bump that revision" write needs, plus
|
|
13664
|
+
* the batched upsert a rollup writer needs.
|
|
13665
|
+
*/
|
|
13666
|
+
var TransactOpSchema = discriminatedUnion("op", [
|
|
13667
|
+
object({
|
|
13668
|
+
op: literal("deleteWhere"),
|
|
13669
|
+
collection: string(),
|
|
13670
|
+
filter: MutationFilterSchema
|
|
13671
|
+
}),
|
|
13672
|
+
object({
|
|
13673
|
+
op: literal("insertMany"),
|
|
13674
|
+
collection: string(),
|
|
13675
|
+
records: array(BulkRecordSchema).readonly()
|
|
13676
|
+
}),
|
|
13677
|
+
object({
|
|
13678
|
+
op: literal("update"),
|
|
13679
|
+
collection: string(),
|
|
13680
|
+
id: string(),
|
|
13681
|
+
data: record(string(), unknown())
|
|
13682
|
+
}),
|
|
13683
|
+
object({
|
|
13684
|
+
op: literal("upsertMany"),
|
|
13685
|
+
collection: string(),
|
|
13686
|
+
conflict: array(string()).readonly(),
|
|
13687
|
+
records: array(BulkRecordSchema).readonly()
|
|
13688
|
+
})
|
|
13689
|
+
]);
|
|
13690
|
+
/**
|
|
13691
|
+
* The precondition of a transaction: the row `key` of `collection` must hold
|
|
13692
|
+
* `equals` in `column`, read INSIDE the transaction.
|
|
13693
|
+
*/
|
|
13694
|
+
var TransactGuardSchema = object({
|
|
13695
|
+
collection: string(),
|
|
13696
|
+
key: string(),
|
|
13697
|
+
column: string(),
|
|
13698
|
+
equals: unknown()
|
|
13699
|
+
});
|
|
13700
|
+
/**
|
|
13701
|
+
* Column declaration for a structured (SQL-backed) collection.
|
|
13702
|
+
*
|
|
13703
|
+
* Logical types — the backend translates each to the matching SQLite
|
|
13704
|
+
* storage class and handles per-type marshaling:
|
|
13705
|
+
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
13706
|
+
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
13707
|
+
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
13708
|
+
*/
|
|
13709
|
+
var CollectionColumnSchema = object({
|
|
13710
|
+
name: string(),
|
|
13711
|
+
type: _enum([
|
|
13712
|
+
"TEXT",
|
|
13713
|
+
"INTEGER",
|
|
13714
|
+
"REAL",
|
|
13715
|
+
"JSON",
|
|
13716
|
+
"BOOLEAN"
|
|
13717
|
+
]),
|
|
13718
|
+
primaryKey: boolean().optional(),
|
|
13719
|
+
notNull: boolean().optional(),
|
|
13720
|
+
unique: boolean().optional(),
|
|
13721
|
+
/**
|
|
13722
|
+
* Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
|
|
13723
|
+
* columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
|
|
13724
|
+
* collection surface simply cannot express their existing tables — which is
|
|
13725
|
+
* why they were still on the uncapped `table*` API.
|
|
13726
|
+
*/
|
|
13727
|
+
defaultValue: union([
|
|
13728
|
+
string(),
|
|
13729
|
+
number(),
|
|
13730
|
+
boolean()
|
|
13731
|
+
]).optional()
|
|
13732
|
+
});
|
|
13733
|
+
var CollectionIndexSchema = object({
|
|
13734
|
+
name: string(),
|
|
13735
|
+
columns: array(string()).readonly(),
|
|
13736
|
+
unique: boolean().optional()
|
|
13737
|
+
});
|
|
13738
|
+
/**
|
|
13739
|
+
* The input of `declareCollection` — ONE schema for the door and for the
|
|
13740
|
+
* engine behind it, so the two surfaces cannot drift (the dispatcher forwards
|
|
13741
|
+
* the payload untouched).
|
|
13742
|
+
*
|
|
13743
|
+
* - `database` — the file the collection lives in (D851). Absent means
|
|
13744
|
+
* `settings`. A declaration naming a file other than the one the table is
|
|
13745
|
+
* already in is REFUSED by the engine, never moved.
|
|
13746
|
+
* - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
|
|
13747
|
+
* as `primaryKey: true` on that column. A composite-keyed collection has no
|
|
13748
|
+
* `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
|
|
13749
|
+
* with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
|
|
13750
|
+
* address one row by id are refused.
|
|
13751
|
+
* - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
|
|
13752
|
+
*/
|
|
13753
|
+
var DeclareCollectionInputSchema = object({
|
|
13754
|
+
namespace: string().optional(),
|
|
13755
|
+
collection: string(),
|
|
13756
|
+
columns: array(CollectionColumnSchema).readonly(),
|
|
13757
|
+
indexes: array(CollectionIndexSchema).readonly().optional(),
|
|
13758
|
+
database: DataStoreDatabaseSchema.optional(),
|
|
13759
|
+
primaryKey: array(string()).readonly().optional(),
|
|
13760
|
+
withoutRowid: boolean().optional()
|
|
13761
|
+
});
|
|
13762
|
+
method(object({
|
|
13763
|
+
namespace: string().optional(),
|
|
13764
|
+
collection: string(),
|
|
13765
|
+
key: string()
|
|
13766
|
+
}), unknown(), { auth: "admin" }), method(object({
|
|
13767
|
+
namespace: string().optional(),
|
|
13768
|
+
collection: string(),
|
|
13769
|
+
key: string(),
|
|
13770
|
+
value: unknown()
|
|
13771
|
+
}), _void(), {
|
|
13772
|
+
kind: "mutation",
|
|
13773
|
+
auth: "admin"
|
|
13774
|
+
}), method(object({
|
|
13775
|
+
namespace: string().optional(),
|
|
13776
|
+
collection: string(),
|
|
13777
|
+
filter: QueryFilterSchema.optional(),
|
|
13778
|
+
columns: array(string()).readonly().optional()
|
|
13779
|
+
}), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
|
|
13780
|
+
namespace: string().optional(),
|
|
13781
|
+
collection: string(),
|
|
13782
|
+
/** Declared columns identifying the slot; must be covered by a UNIQUE
|
|
13783
|
+
* index on the collection. Never empty. */
|
|
13784
|
+
conflict: array(string()).readonly(),
|
|
13785
|
+
record: BulkRecordSchema
|
|
13786
|
+
}), object({
|
|
13787
|
+
/** The id the row HAS: assigned by SQLite on an insert, or the existing
|
|
13788
|
+
* row's own id on an update. */
|
|
13789
|
+
id: union([string(), number()]) }), {
|
|
13790
|
+
kind: "mutation",
|
|
13791
|
+
auth: "admin"
|
|
13792
|
+
}), method(object({
|
|
13793
|
+
namespace: string().optional(),
|
|
13794
|
+
collection: string(),
|
|
13795
|
+
conflict: array(string()).readonly(),
|
|
13796
|
+
records: array(BulkRecordSchema).readonly()
|
|
13797
|
+
}), object({ upserted: number().int() }), {
|
|
13798
|
+
kind: "mutation",
|
|
13799
|
+
auth: "admin"
|
|
13800
|
+
}), method(object({
|
|
13801
|
+
namespace: string().optional(),
|
|
13802
|
+
collection: string(),
|
|
13803
|
+
record: object({
|
|
13804
|
+
id: string().optional(),
|
|
13805
|
+
data: record(string(), unknown())
|
|
13806
|
+
})
|
|
13807
|
+
}), object({
|
|
13808
|
+
/**
|
|
13809
|
+
* The id the row ACTUALLY got (D473): the one supplied, the UUID minted
|
|
13810
|
+
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
13811
|
+
* primary key — which is the only place an auto key is knowable.
|
|
13812
|
+
*/
|
|
13813
|
+
id: union([string(), number()]) }), {
|
|
13814
|
+
kind: "mutation",
|
|
13815
|
+
auth: "admin"
|
|
13816
|
+
}), method(object({
|
|
13817
|
+
namespace: string().optional(),
|
|
13818
|
+
collection: string(),
|
|
13819
|
+
records: array(BulkRecordSchema).readonly()
|
|
13820
|
+
}), object({ inserted: number().int() }), {
|
|
13821
|
+
kind: "mutation",
|
|
13822
|
+
auth: "admin"
|
|
13823
|
+
}), method(object({
|
|
13824
|
+
namespace: string().optional(),
|
|
13825
|
+
guard: TransactGuardSchema.optional(),
|
|
13826
|
+
ops: array(TransactOpSchema).readonly()
|
|
13827
|
+
}), object({
|
|
13828
|
+
applied: boolean(),
|
|
13829
|
+
current: unknown().optional()
|
|
13830
|
+
}), {
|
|
13831
|
+
kind: "mutation",
|
|
13832
|
+
auth: "admin"
|
|
13833
|
+
}), method(object({
|
|
13834
|
+
namespace: string().optional(),
|
|
13835
|
+
collection: string(),
|
|
13836
|
+
id: string(),
|
|
13837
|
+
data: record(string(), unknown())
|
|
13838
|
+
}), _void(), {
|
|
13839
|
+
kind: "mutation",
|
|
13840
|
+
auth: "admin"
|
|
13841
|
+
}), method(object({
|
|
13842
|
+
namespace: string().optional(),
|
|
13843
|
+
collection: string(),
|
|
13844
|
+
key: string()
|
|
13845
|
+
}), _void(), {
|
|
13846
|
+
kind: "mutation",
|
|
13847
|
+
auth: "admin"
|
|
13848
|
+
}), method(object({
|
|
13849
|
+
namespace: string().optional(),
|
|
13850
|
+
collection: string(),
|
|
13851
|
+
filter: MutationFilterSchema
|
|
13852
|
+
}), object({ deleted: number().int() }), {
|
|
13853
|
+
kind: "mutation",
|
|
13854
|
+
auth: "admin"
|
|
13855
|
+
}), method(object({
|
|
13856
|
+
namespace: string().optional(),
|
|
13857
|
+
collection: string(),
|
|
13858
|
+
filter: MutationFilterSchema,
|
|
13859
|
+
data: record(string(), unknown())
|
|
13860
|
+
}), object({ updated: number().int() }), {
|
|
13861
|
+
kind: "mutation",
|
|
13862
|
+
auth: "admin"
|
|
13863
|
+
}), method(object({
|
|
13864
|
+
namespace: string().optional(),
|
|
13865
|
+
collection: string(),
|
|
13866
|
+
filter: QueryFilterSchema.optional()
|
|
13867
|
+
}), number(), { auth: "admin" }), method(object({
|
|
13868
|
+
namespace: string().optional(),
|
|
13869
|
+
collection: string(),
|
|
13870
|
+
fields: array(AggregateFieldSchema).readonly(),
|
|
13871
|
+
filter: QueryFilterSchema.optional()
|
|
13872
|
+
}), AggregateResultSchema, { auth: "admin" }), method(object({
|
|
13873
|
+
namespace: string().optional(),
|
|
13874
|
+
collection: string(),
|
|
13875
|
+
field: string(),
|
|
13876
|
+
bucketSize: number().int().positive(),
|
|
13877
|
+
origin: number().int(),
|
|
13878
|
+
filter: QueryFilterSchema.optional()
|
|
13879
|
+
}), array(object({
|
|
13880
|
+
bucket: number().int(),
|
|
13881
|
+
count: number().int()
|
|
13882
|
+
})).readonly(), { auth: "admin" }), method(object({
|
|
13883
|
+
namespace: string().optional(),
|
|
13884
|
+
collection: string()
|
|
13885
|
+
}), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
|
|
13886
|
+
kind: "mutation",
|
|
13887
|
+
auth: "admin"
|
|
13888
|
+
});
|
|
13889
|
+
var MigrationIdSchema = string().regex(/^[a-z0-9-]+\/\d{4}-\d{2}-\d{2}-[a-z0-9-]+$/, "a migration id is \"<owner>/<yyyy-mm-dd>-<slug>\"");
|
|
13890
|
+
var MigrationLevelSchema = _enum([
|
|
13891
|
+
"hub-store",
|
|
13892
|
+
"addon-state",
|
|
13893
|
+
"node-config",
|
|
13894
|
+
"viewer",
|
|
13895
|
+
"addon-internal"
|
|
13896
|
+
]);
|
|
13897
|
+
var MigrationStoreSchema = _enum([
|
|
13898
|
+
"settings",
|
|
13899
|
+
"events",
|
|
13900
|
+
"history",
|
|
13901
|
+
"node-local",
|
|
13902
|
+
"addon-files"
|
|
13903
|
+
]);
|
|
13904
|
+
var MigrationScopeSchema = _enum(["cluster", "per-node"]);
|
|
13905
|
+
/** What depends on a migration while it is pending: `block-owner` is the default. */
|
|
13906
|
+
var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
|
|
13907
|
+
/**
|
|
13908
|
+
* `sql` is a hub-only body over the owner's connection. `plan` returns edits the
|
|
13909
|
+
* engine applies inside the migration's own transaction (hub-owned: computed in
|
|
13910
|
+
* that transaction; addon-owned: computed by the owner and shipped to the engine).
|
|
13911
|
+
* `procedure` is addon-owned work that is not SQL (keychain, integration registry,
|
|
13912
|
+
* device tombstones); the ledger records its outcome and it must be idempotent.
|
|
13913
|
+
*/
|
|
13914
|
+
var MigrationBodyKindSchema = _enum([
|
|
13915
|
+
"sql",
|
|
13916
|
+
"plan",
|
|
13917
|
+
"procedure"
|
|
13918
|
+
]);
|
|
13919
|
+
var MigrationMetaSchema = object({
|
|
13920
|
+
id: MigrationIdSchema,
|
|
13921
|
+
/** `hub`, an addon id, `node` or `viewer`. */
|
|
13922
|
+
owner: string().regex(/^[a-z0-9-]+$/),
|
|
13923
|
+
level: MigrationLevelSchema,
|
|
13924
|
+
/** Strictly increasing per owner: the order within an owner. */
|
|
13925
|
+
seq: number().int().positive(),
|
|
13926
|
+
/** Ids of any owner: the order across owners. */
|
|
13927
|
+
dependsOn: array(MigrationIdSchema),
|
|
13928
|
+
store: MigrationStoreSchema,
|
|
13929
|
+
scope: MigrationScopeSchema,
|
|
13930
|
+
onFailure: MigrationOnFailureSchema,
|
|
13931
|
+
bodyKind: MigrationBodyKindSchema,
|
|
13932
|
+
description: string().min(1),
|
|
13933
|
+
/** The marked field the migration's spec must assert survived. */
|
|
13934
|
+
fixtureMark: string().min(1)
|
|
13935
|
+
}).refine((m) => m.id.startsWith(`${m.owner}/`), {
|
|
13936
|
+
message: "the id must start with \"<owner>/\"",
|
|
13937
|
+
path: ["id"]
|
|
13938
|
+
});
|
|
13939
|
+
/** A cycle in the dependency graph, named by the ids on it. */
|
|
13940
|
+
var MigrationCycleError = class extends Error {
|
|
13941
|
+
ids;
|
|
13942
|
+
constructor(ids) {
|
|
13943
|
+
super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
|
|
13944
|
+
this.ids = ids;
|
|
13945
|
+
this.name = "MigrationCycleError";
|
|
13946
|
+
}
|
|
13947
|
+
};
|
|
13948
|
+
/**
|
|
13949
|
+
* Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
|
|
13950
|
+
* owner runs first". Ties are broken by `(owner, seq)`. Throws
|
|
13951
|
+
* {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
|
|
13952
|
+
* is ignored here — the catalogue schema is what refuses it.
|
|
13953
|
+
*/
|
|
13954
|
+
function orderMigrations(items) {
|
|
13955
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
13956
|
+
const deps = /* @__PURE__ */ new Map();
|
|
13957
|
+
const ownerSorted = /* @__PURE__ */ new Map();
|
|
13958
|
+
for (const item of items) {
|
|
13959
|
+
const list = ownerSorted.get(item.owner) ?? [];
|
|
13960
|
+
list.push(item);
|
|
13961
|
+
ownerSorted.set(item.owner, list);
|
|
13962
|
+
}
|
|
13963
|
+
for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
|
|
13964
|
+
for (const list of ownerSorted.values()) {
|
|
13965
|
+
list.sort((a, b) => a.seq - b.seq);
|
|
13966
|
+
for (let i = 1; i < list.length; i++) {
|
|
13967
|
+
const previous = list[i - 1];
|
|
13968
|
+
const current = list[i];
|
|
13969
|
+
if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
|
|
13973
|
+
const done = /* @__PURE__ */ new Set();
|
|
13974
|
+
const ordered = [];
|
|
13975
|
+
let remaining = [...items];
|
|
13976
|
+
while (remaining.length > 0) {
|
|
13977
|
+
const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
|
|
13978
|
+
if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
|
|
13979
|
+
ready.sort(tie);
|
|
13980
|
+
const next = ready[0];
|
|
13981
|
+
if (next === void 0) break;
|
|
13982
|
+
ordered.push(next);
|
|
13983
|
+
done.add(next.id);
|
|
13984
|
+
remaining = remaining.filter((item) => item.id !== next.id);
|
|
13985
|
+
}
|
|
13986
|
+
return ordered;
|
|
13987
|
+
}
|
|
13988
|
+
/** Every way a list of migrations (in declaration order) is not a catalogue. */
|
|
13989
|
+
function catalogueIssues(items) {
|
|
13990
|
+
const issues = [];
|
|
13991
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13992
|
+
const known = new Set(items.map((item) => item.id));
|
|
13993
|
+
const lastSeq = /* @__PURE__ */ new Map();
|
|
13994
|
+
const seqUsed = /* @__PURE__ */ new Map();
|
|
13995
|
+
for (const item of items) {
|
|
13996
|
+
if (seen.has(item.id)) issues.push({
|
|
13997
|
+
code: "duplicate-id",
|
|
13998
|
+
ids: [item.id],
|
|
13999
|
+
message: `duplicate id ${item.id}`
|
|
14000
|
+
});
|
|
14001
|
+
seen.add(item.id);
|
|
14002
|
+
const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
|
|
14003
|
+
if (used.has(item.seq)) issues.push({
|
|
14004
|
+
code: "duplicate-seq",
|
|
14005
|
+
ids: [item.id],
|
|
14006
|
+
message: `owner ${item.owner} has two migrations with seq ${item.seq}`
|
|
14007
|
+
});
|
|
14008
|
+
else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
|
|
14009
|
+
code: "seq-order",
|
|
14010
|
+
ids: [item.id],
|
|
14011
|
+
message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
|
|
14012
|
+
});
|
|
14013
|
+
used.add(item.seq);
|
|
14014
|
+
seqUsed.set(item.owner, used);
|
|
14015
|
+
lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
|
|
14016
|
+
for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
|
|
14017
|
+
code: "unknown-dependency",
|
|
14018
|
+
ids: [item.id, dep],
|
|
14019
|
+
message: `${item.id} dependsOn unknown id ${dep}`
|
|
14020
|
+
});
|
|
14021
|
+
}
|
|
14022
|
+
try {
|
|
14023
|
+
orderMigrations(items);
|
|
14024
|
+
} catch (err) {
|
|
14025
|
+
if (err instanceof MigrationCycleError) issues.push({
|
|
14026
|
+
code: "cycle",
|
|
14027
|
+
ids: err.ids,
|
|
14028
|
+
message: err.message
|
|
14029
|
+
});
|
|
14030
|
+
else throw err;
|
|
14031
|
+
}
|
|
14032
|
+
return issues;
|
|
14033
|
+
}
|
|
14034
|
+
array(MigrationMetaSchema).superRefine((items, ctx) => {
|
|
14035
|
+
for (const issue of catalogueIssues(items)) ctx.addIssue({
|
|
14036
|
+
code: "custom",
|
|
14037
|
+
message: `${issue.code}: ${issue.message}`
|
|
14038
|
+
});
|
|
14039
|
+
});
|
|
14040
|
+
/**
|
|
14041
|
+
* The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
|
|
14042
|
+
* re-reads, and a plan whose read no longer matches is refused (the migration is
|
|
14043
|
+
* `failed`, nothing it edited is kept).
|
|
14044
|
+
*/
|
|
14045
|
+
var PreconditionSchema = string().min(1).optional();
|
|
14046
|
+
var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
|
|
14047
|
+
object({
|
|
14048
|
+
op: literal("setKeys"),
|
|
14049
|
+
scope: string().min(1),
|
|
14050
|
+
values: record(string(), unknown()),
|
|
14051
|
+
precondition: PreconditionSchema
|
|
14052
|
+
}),
|
|
14053
|
+
object({
|
|
14054
|
+
op: literal("deleteKeys"),
|
|
14055
|
+
scope: string().min(1),
|
|
14056
|
+
keys: array(string().min(1)).min(1),
|
|
14057
|
+
precondition: PreconditionSchema
|
|
14058
|
+
}),
|
|
14059
|
+
object({
|
|
14060
|
+
op: literal("putRow"),
|
|
14061
|
+
table: string().min(1),
|
|
14062
|
+
key: record(string(), unknown()),
|
|
14063
|
+
row: record(string(), unknown()),
|
|
14064
|
+
precondition: PreconditionSchema
|
|
14065
|
+
}),
|
|
14066
|
+
object({
|
|
14067
|
+
op: literal("deleteRow"),
|
|
14068
|
+
table: string().min(1),
|
|
14069
|
+
key: record(string(), unknown()),
|
|
14070
|
+
precondition: PreconditionSchema
|
|
14071
|
+
}),
|
|
14072
|
+
object({
|
|
14073
|
+
op: literal("upsertRecord"),
|
|
14074
|
+
kind: string().min(1),
|
|
14075
|
+
recordKey: string().min(1),
|
|
14076
|
+
value: unknown(),
|
|
14077
|
+
precondition: PreconditionSchema
|
|
14078
|
+
}),
|
|
14079
|
+
object({
|
|
14080
|
+
op: literal("deleteRecord"),
|
|
14081
|
+
kind: string().min(1),
|
|
14082
|
+
recordKey: string().min(1),
|
|
14083
|
+
precondition: PreconditionSchema
|
|
14084
|
+
})
|
|
14085
|
+
])) });
|
|
14086
|
+
/**
|
|
14087
|
+
* What a `procedure` body reports. `already-done` means the rerun observed the
|
|
14088
|
+
* work finished (a crash between the work and the ledger row, or a manual fix)
|
|
14089
|
+
* and did none of it again; the ledger records `applied` either way.
|
|
14090
|
+
*/
|
|
14091
|
+
var MigrationProcedureResultSchema = object({
|
|
14092
|
+
outcome: _enum(["done", "already-done"]),
|
|
14093
|
+
summary: record(string(), unknown())
|
|
14094
|
+
});
|
|
14095
|
+
/** What an owner hands the engine when one of its migrations ran. */
|
|
14096
|
+
var MigrationOwnerResultSchema = discriminatedUnion("kind", [
|
|
14097
|
+
object({
|
|
14098
|
+
kind: literal("plan"),
|
|
14099
|
+
plan: MigrationPlanSchema
|
|
14100
|
+
}),
|
|
14101
|
+
object({
|
|
14102
|
+
kind: literal("procedure"),
|
|
14103
|
+
result: MigrationProcedureResultSchema
|
|
14104
|
+
}),
|
|
14105
|
+
object({
|
|
14106
|
+
kind: literal("failed"),
|
|
14107
|
+
error: string().min(1)
|
|
14108
|
+
})
|
|
14109
|
+
]);
|
|
14110
|
+
/**
|
|
14111
|
+
* `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
|
|
14112
|
+
* until the operator retries or a new code version ships.
|
|
14113
|
+
* `skipped-newer`: the database knows an id this code does not.
|
|
14114
|
+
*/
|
|
14115
|
+
var MigrationStatusSchema = _enum([
|
|
14116
|
+
"applied",
|
|
14117
|
+
"failed",
|
|
14118
|
+
"blocked",
|
|
14119
|
+
"running",
|
|
14120
|
+
"held",
|
|
14121
|
+
"skipped-newer"
|
|
14122
|
+
]);
|
|
14123
|
+
var MigrationLedgerEntrySchema = object({
|
|
14124
|
+
id: string(),
|
|
14125
|
+
database: DataStoreDatabaseSchema,
|
|
14126
|
+
owner: string(),
|
|
14127
|
+
level: MigrationLevelSchema,
|
|
14128
|
+
seq: number().int().nonnegative(),
|
|
14129
|
+
status: MigrationStatusSchema,
|
|
14130
|
+
attempts: number().int().nonnegative(),
|
|
14131
|
+
appliedAt: number().nullable(),
|
|
14132
|
+
codeVersion: string().nullable(),
|
|
14133
|
+
/** What the apply changed: rows per table, as the engine counted them. */
|
|
14134
|
+
planSummary: record(string(), unknown()).nullable(),
|
|
14135
|
+
progress: record(string(), unknown()).nullable(),
|
|
14136
|
+
error: string().nullable(),
|
|
14137
|
+
/** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
|
|
14138
|
+
reason: string().nullable(),
|
|
14139
|
+
nodeId: string().nullable(),
|
|
14140
|
+
bootId: string().nullable(),
|
|
14141
|
+
/**
|
|
14142
|
+
* The catalogued meta of this id (description, body kind, store, `dependsOn`…),
|
|
14143
|
+
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14144
|
+
* catalogue lists the id (a newer database).
|
|
14145
|
+
*/
|
|
14146
|
+
meta: MigrationMetaSchema.optional()
|
|
14147
|
+
});
|
|
14148
|
+
/** A catalogued migration that is not applied, with where it stands. */
|
|
14149
|
+
var MigrationPendingEntrySchema = object({
|
|
14150
|
+
meta: MigrationMetaSchema,
|
|
14151
|
+
database: DataStoreDatabaseSchema,
|
|
14152
|
+
state: _enum([
|
|
14153
|
+
"pending",
|
|
14154
|
+
"failed",
|
|
14155
|
+
"blocked",
|
|
14156
|
+
"held"
|
|
14157
|
+
]),
|
|
14158
|
+
reason: string().nullable()
|
|
14159
|
+
});
|
|
14160
|
+
/** Rows per table before and after a dry run, and what it would have changed. */
|
|
14161
|
+
var MigrationDryRunResultSchema = object({
|
|
14162
|
+
id: string(),
|
|
14163
|
+
ok: boolean(),
|
|
14164
|
+
error: string().nullable(),
|
|
14165
|
+
rowsChanged: number().int().nonnegative(),
|
|
14166
|
+
tables: array(object({
|
|
14167
|
+
table: string(),
|
|
14168
|
+
before: number().int().nonnegative(),
|
|
14169
|
+
after: number().int().nonnegative()
|
|
14170
|
+
}))
|
|
14171
|
+
});
|
|
14172
|
+
/** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
|
|
14173
|
+
var MigrationOwnerStepSchema = object({
|
|
14174
|
+
id: MigrationIdSchema,
|
|
14175
|
+
action: _enum(["run", "skip"]),
|
|
14176
|
+
/** The ledger state the engine found: `pending` has no row yet. */
|
|
14177
|
+
state: union([literal("pending"), MigrationStatusSchema])
|
|
14178
|
+
});
|
|
14179
|
+
/**
|
|
13366
14180
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
13367
14181
|
* gets back (cloud training, D805+).
|
|
13368
14182
|
*
|
|
@@ -19136,6 +19950,14 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19136
19950
|
}
|
|
19137
19951
|
} }
|
|
19138
19952
|
},
|
|
19953
|
+
"device-runs": {
|
|
19954
|
+
scope: "device",
|
|
19955
|
+
noun: t("Runs", "Esecuzioni"),
|
|
19956
|
+
events: {
|
|
19957
|
+
onRunOpened: { notOperatorFacing: "the durable record of a run that the running-state start flip already announces" },
|
|
19958
|
+
onRunClosed: { notOperatorFacing: "the durable record of a run that the running-state finish flip already announces" }
|
|
19959
|
+
}
|
|
19960
|
+
},
|
|
19139
19961
|
update: {
|
|
19140
19962
|
scope: "device",
|
|
19141
19963
|
noun: t("Firmware update", "Aggiornamento firmware"),
|
|
@@ -20353,6 +21175,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
20353
21175
|
"login-method": system(IDENTITY),
|
|
20354
21176
|
"mesh-network": system(NETWORK),
|
|
20355
21177
|
"metrics-provider": system(CLUSTER),
|
|
21178
|
+
migrations: system(INTERNAL),
|
|
20356
21179
|
"model-convert": system(ML),
|
|
20357
21180
|
"model-distributor": system(ML),
|
|
20358
21181
|
"mqtt-broker": system(NETWORK),
|
|
@@ -40555,7 +41378,8 @@ function normalizeUnit(raw) {
|
|
|
40555
41378
|
if (lower === "dbm") return "dBm";
|
|
40556
41379
|
if (lower === "dbfs") return "dBFS";
|
|
40557
41380
|
if (lower === "inhg") return "inHg";
|
|
40558
|
-
if (lower === "mm/h") return "mm/h";
|
|
41381
|
+
if (lower === "mm/h" || lower === "mm/hr") return "mm/h";
|
|
41382
|
+
if (lower === "in/h" || lower === "in/hr") return "in/h";
|
|
40559
41383
|
if (lower === "w/m²" || lower === "w/m2") return "W/m²";
|
|
40560
41384
|
if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
|
|
40561
41385
|
if (lower === "kbps") return "kbps";
|
|
@@ -42949,7 +43773,7 @@ function pruneDeletedEntities(selection, live, log, exporter) {
|
|
|
42949
43773
|
* rebuilds without manual reload.
|
|
42950
43774
|
*
|
|
42951
43775
|
* The hub-local builtin `addon-pages-aggregator` (see
|
|
42952
|
-
* `@camstack/system/builtins/addon-pages-aggregator`) registers the
|
|
43776
|
+
* `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
|
|
42953
43777
|
* provider. Splitting the public aggregator from the raw collection
|
|
42954
43778
|
* keeps both ends in codegen — there's no hand-written
|
|
42955
43779
|
* `addon-pages.router.ts` wrapper anymore.
|
|
@@ -43039,7 +43863,7 @@ var addonRoutesCapability = {
|
|
|
43039
43863
|
*/
|
|
43040
43864
|
invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
|
|
43041
43865
|
},
|
|
43042
|
-
/** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
|
|
43866
|
+
/** BIG PLAN 2: declarative mount hint — read by `@camstack/server/system` `buildCapRouters`. */
|
|
43043
43867
|
mount: { kind: "skip" }
|
|
43044
43868
|
};
|
|
43045
43869
|
var ConfigTabDeclarationSchema = object({
|
|
@@ -43268,7 +44092,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
|
|
|
43268
44092
|
* manual reload — same scheme used by `addon-pages`.
|
|
43269
44093
|
*
|
|
43270
44094
|
* The hub-local builtin `addon-widgets-aggregator` (see
|
|
43271
|
-
* `@camstack/system/builtins/addon-widgets-aggregator`) registers the
|
|
44095
|
+
* `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
|
|
43272
44096
|
* provider. Splitting the public aggregator from the raw collection
|
|
43273
44097
|
* keeps both ends in codegen — there's no hand-written wrapper.
|
|
43274
44098
|
*/
|
|
@@ -43629,329 +44453,6 @@ method(object({
|
|
|
43629
44453
|
password: string()
|
|
43630
44454
|
}), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
|
|
43631
44455
|
/**
|
|
43632
|
-
* Query filter for settings-store collections.
|
|
43633
|
-
*/
|
|
43634
|
-
var QueryFilterSchema = object({
|
|
43635
|
-
where: record(string(), unknown()).optional(),
|
|
43636
|
-
whereIn: record(string(), array(unknown())).optional(),
|
|
43637
|
-
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
43638
|
-
/** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
|
|
43639
|
-
whereNot: record(string(), unknown()).optional(),
|
|
43640
|
-
/** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
|
|
43641
|
-
* empty list excludes nothing. See `QueryFilter.whereNotIn`. */
|
|
43642
|
-
whereNotIn: record(string(), array(unknown())).optional(),
|
|
43643
|
-
orderBy: object({
|
|
43644
|
-
field: string(),
|
|
43645
|
-
direction: _enum(["asc", "desc"])
|
|
43646
|
-
}).optional(),
|
|
43647
|
-
limit: number().optional(),
|
|
43648
|
-
offset: number().optional()
|
|
43649
|
-
});
|
|
43650
|
-
/**
|
|
43651
|
-
* The predicate half of a filter, for BULK MUTATIONS.
|
|
43652
|
-
*
|
|
43653
|
-
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
43654
|
-
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
43655
|
-
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
43656
|
-
* only so the shape stays composable — the implementation REJECTS a filter
|
|
43657
|
-
* that compiles to no predicate, because that is the whole collection.
|
|
43658
|
-
*/
|
|
43659
|
-
var MutationFilterSchema = object({
|
|
43660
|
-
where: record(string(), unknown()).optional(),
|
|
43661
|
-
whereIn: record(string(), array(unknown())).optional(),
|
|
43662
|
-
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
43663
|
-
whereNot: record(string(), unknown()).optional(),
|
|
43664
|
-
whereNotIn: record(string(), array(unknown())).optional()
|
|
43665
|
-
});
|
|
43666
|
-
/**
|
|
43667
|
-
* One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
|
|
43668
|
-
*
|
|
43669
|
-
* `as` names the slot in the result, so the SAME column may be asked twice with
|
|
43670
|
-
* two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
|
|
43671
|
-
* a `Record<column, op>` shape could not express.
|
|
43672
|
-
*/
|
|
43673
|
-
var AggregateFieldSchema = object({
|
|
43674
|
-
/** Result key. */
|
|
43675
|
-
as: string().min(1),
|
|
43676
|
-
/** Column to aggregate. Must be a real column of a declared collection. */
|
|
43677
|
-
field: string().min(1),
|
|
43678
|
-
op: _enum([
|
|
43679
|
-
"sum",
|
|
43680
|
-
"min",
|
|
43681
|
-
"max"
|
|
43682
|
-
])
|
|
43683
|
-
});
|
|
43684
|
-
/**
|
|
43685
|
-
* `COUNT(*)` plus one number per requested field.
|
|
43686
|
-
*
|
|
43687
|
-
* `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
|
|
43688
|
-
* that really is 0 are different facts, and an accounting caller that renders
|
|
43689
|
-
* "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
|
|
43690
|
-
*/
|
|
43691
|
-
var AggregateResultSchema = object({
|
|
43692
|
-
count: number().int(),
|
|
43693
|
-
values: record(string(), number().nullable())
|
|
43694
|
-
});
|
|
43695
|
-
/** A single stored record: `{ id, data }`. */
|
|
43696
|
-
var SettingsRecordSchema = object({
|
|
43697
|
-
id: string(),
|
|
43698
|
-
data: record(string(), unknown())
|
|
43699
|
-
});
|
|
43700
|
-
/**
|
|
43701
|
-
* One record of a BULK insert — {@link SettingsRecordSchema} with the id made
|
|
43702
|
-
* optional.
|
|
43703
|
-
*
|
|
43704
|
-
* A separate schema rather than loosening the shared one: every other method
|
|
43705
|
-
* on this cap addresses a row BY its id, and making that field optional
|
|
43706
|
-
* everywhere would turn a forgotten key into a silently generated one on
|
|
43707
|
-
* `update` and `delete` as well.
|
|
43708
|
-
*/
|
|
43709
|
-
var BulkRecordSchema = object({
|
|
43710
|
-
id: string().optional(),
|
|
43711
|
-
data: record(string(), unknown())
|
|
43712
|
-
});
|
|
43713
|
-
/**
|
|
43714
|
-
* The database FILES one `sqlite-settings` engine process owns (D851).
|
|
43715
|
-
* Closed on purpose: a file is a thing the engine opens, backs up and gives a
|
|
43716
|
-
* serving thread, so a new one is a code change, never a string a caller
|
|
43717
|
-
* invents. `settings` is `camstack.db` and the default for every collection
|
|
43718
|
-
* that names no file.
|
|
43719
|
-
*/
|
|
43720
|
-
var DataStoreDatabaseSchema = _enum([
|
|
43721
|
-
"settings",
|
|
43722
|
-
"events",
|
|
43723
|
-
"history"
|
|
43724
|
-
]);
|
|
43725
|
-
DataStoreDatabaseSchema.options;
|
|
43726
|
-
/**
|
|
43727
|
-
* One write inside a {@link settingsStoreCapability.methods.transact} call.
|
|
43728
|
-
* The three that a "replace this set, bump that revision" write needs, plus
|
|
43729
|
-
* the batched upsert a rollup writer needs.
|
|
43730
|
-
*/
|
|
43731
|
-
var TransactOpSchema = discriminatedUnion("op", [
|
|
43732
|
-
object({
|
|
43733
|
-
op: literal("deleteWhere"),
|
|
43734
|
-
collection: string(),
|
|
43735
|
-
filter: MutationFilterSchema
|
|
43736
|
-
}),
|
|
43737
|
-
object({
|
|
43738
|
-
op: literal("insertMany"),
|
|
43739
|
-
collection: string(),
|
|
43740
|
-
records: array(BulkRecordSchema).readonly()
|
|
43741
|
-
}),
|
|
43742
|
-
object({
|
|
43743
|
-
op: literal("update"),
|
|
43744
|
-
collection: string(),
|
|
43745
|
-
id: string(),
|
|
43746
|
-
data: record(string(), unknown())
|
|
43747
|
-
}),
|
|
43748
|
-
object({
|
|
43749
|
-
op: literal("upsertMany"),
|
|
43750
|
-
collection: string(),
|
|
43751
|
-
conflict: array(string()).readonly(),
|
|
43752
|
-
records: array(BulkRecordSchema).readonly()
|
|
43753
|
-
})
|
|
43754
|
-
]);
|
|
43755
|
-
/**
|
|
43756
|
-
* The precondition of a transaction: the row `key` of `collection` must hold
|
|
43757
|
-
* `equals` in `column`, read INSIDE the transaction.
|
|
43758
|
-
*/
|
|
43759
|
-
var TransactGuardSchema = object({
|
|
43760
|
-
collection: string(),
|
|
43761
|
-
key: string(),
|
|
43762
|
-
column: string(),
|
|
43763
|
-
equals: unknown()
|
|
43764
|
-
});
|
|
43765
|
-
/**
|
|
43766
|
-
* Column declaration for a structured (SQL-backed) collection.
|
|
43767
|
-
*
|
|
43768
|
-
* Logical types — the backend translates each to the matching SQLite
|
|
43769
|
-
* storage class and handles per-type marshaling:
|
|
43770
|
-
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
43771
|
-
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
43772
|
-
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
43773
|
-
*/
|
|
43774
|
-
var CollectionColumnSchema = object({
|
|
43775
|
-
name: string(),
|
|
43776
|
-
type: _enum([
|
|
43777
|
-
"TEXT",
|
|
43778
|
-
"INTEGER",
|
|
43779
|
-
"REAL",
|
|
43780
|
-
"JSON",
|
|
43781
|
-
"BOOLEAN"
|
|
43782
|
-
]),
|
|
43783
|
-
primaryKey: boolean().optional(),
|
|
43784
|
-
notNull: boolean().optional(),
|
|
43785
|
-
unique: boolean().optional(),
|
|
43786
|
-
/**
|
|
43787
|
-
* Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
|
|
43788
|
-
* columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
|
|
43789
|
-
* collection surface simply cannot express their existing tables — which is
|
|
43790
|
-
* why they were still on the uncapped `table*` API.
|
|
43791
|
-
*/
|
|
43792
|
-
defaultValue: union([
|
|
43793
|
-
string(),
|
|
43794
|
-
number(),
|
|
43795
|
-
boolean()
|
|
43796
|
-
]).optional()
|
|
43797
|
-
});
|
|
43798
|
-
var CollectionIndexSchema = object({
|
|
43799
|
-
name: string(),
|
|
43800
|
-
columns: array(string()).readonly(),
|
|
43801
|
-
unique: boolean().optional()
|
|
43802
|
-
});
|
|
43803
|
-
/**
|
|
43804
|
-
* The input of `declareCollection` — ONE schema for the door and for the
|
|
43805
|
-
* engine behind it, so the two surfaces cannot drift (the dispatcher forwards
|
|
43806
|
-
* the payload untouched).
|
|
43807
|
-
*
|
|
43808
|
-
* - `database` — the file the collection lives in (D851). Absent means
|
|
43809
|
-
* `settings`. A declaration naming a file other than the one the table is
|
|
43810
|
-
* already in is REFUSED by the engine, never moved.
|
|
43811
|
-
* - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
|
|
43812
|
-
* as `primaryKey: true` on that column. A composite-keyed collection has no
|
|
43813
|
-
* `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
|
|
43814
|
-
* with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
|
|
43815
|
-
* address one row by id are refused.
|
|
43816
|
-
* - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
|
|
43817
|
-
*/
|
|
43818
|
-
var DeclareCollectionInputSchema = object({
|
|
43819
|
-
namespace: string().optional(),
|
|
43820
|
-
collection: string(),
|
|
43821
|
-
columns: array(CollectionColumnSchema).readonly(),
|
|
43822
|
-
indexes: array(CollectionIndexSchema).readonly().optional(),
|
|
43823
|
-
database: DataStoreDatabaseSchema.optional(),
|
|
43824
|
-
primaryKey: array(string()).readonly().optional(),
|
|
43825
|
-
withoutRowid: boolean().optional()
|
|
43826
|
-
});
|
|
43827
|
-
method(object({
|
|
43828
|
-
namespace: string().optional(),
|
|
43829
|
-
collection: string(),
|
|
43830
|
-
key: string()
|
|
43831
|
-
}), unknown(), { auth: "admin" }), method(object({
|
|
43832
|
-
namespace: string().optional(),
|
|
43833
|
-
collection: string(),
|
|
43834
|
-
key: string(),
|
|
43835
|
-
value: unknown()
|
|
43836
|
-
}), _void(), {
|
|
43837
|
-
kind: "mutation",
|
|
43838
|
-
auth: "admin"
|
|
43839
|
-
}), method(object({
|
|
43840
|
-
namespace: string().optional(),
|
|
43841
|
-
collection: string(),
|
|
43842
|
-
filter: QueryFilterSchema.optional(),
|
|
43843
|
-
columns: array(string()).readonly().optional()
|
|
43844
|
-
}), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
|
|
43845
|
-
namespace: string().optional(),
|
|
43846
|
-
collection: string(),
|
|
43847
|
-
/** Declared columns identifying the slot; must be covered by a UNIQUE
|
|
43848
|
-
* index on the collection. Never empty. */
|
|
43849
|
-
conflict: array(string()).readonly(),
|
|
43850
|
-
record: BulkRecordSchema
|
|
43851
|
-
}), object({
|
|
43852
|
-
/** The id the row HAS: assigned by SQLite on an insert, or the existing
|
|
43853
|
-
* row's own id on an update. */
|
|
43854
|
-
id: union([string(), number()]) }), {
|
|
43855
|
-
kind: "mutation",
|
|
43856
|
-
auth: "admin"
|
|
43857
|
-
}), method(object({
|
|
43858
|
-
namespace: string().optional(),
|
|
43859
|
-
collection: string(),
|
|
43860
|
-
conflict: array(string()).readonly(),
|
|
43861
|
-
records: array(BulkRecordSchema).readonly()
|
|
43862
|
-
}), object({ upserted: number().int() }), {
|
|
43863
|
-
kind: "mutation",
|
|
43864
|
-
auth: "admin"
|
|
43865
|
-
}), method(object({
|
|
43866
|
-
namespace: string().optional(),
|
|
43867
|
-
collection: string(),
|
|
43868
|
-
record: object({
|
|
43869
|
-
id: string().optional(),
|
|
43870
|
-
data: record(string(), unknown())
|
|
43871
|
-
})
|
|
43872
|
-
}), object({
|
|
43873
|
-
/**
|
|
43874
|
-
* The id the row ACTUALLY got (D473): the one supplied, the UUID minted
|
|
43875
|
-
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
43876
|
-
* primary key — which is the only place an auto key is knowable.
|
|
43877
|
-
*/
|
|
43878
|
-
id: union([string(), number()]) }), {
|
|
43879
|
-
kind: "mutation",
|
|
43880
|
-
auth: "admin"
|
|
43881
|
-
}), method(object({
|
|
43882
|
-
namespace: string().optional(),
|
|
43883
|
-
collection: string(),
|
|
43884
|
-
records: array(BulkRecordSchema).readonly()
|
|
43885
|
-
}), object({ inserted: number().int() }), {
|
|
43886
|
-
kind: "mutation",
|
|
43887
|
-
auth: "admin"
|
|
43888
|
-
}), method(object({
|
|
43889
|
-
namespace: string().optional(),
|
|
43890
|
-
guard: TransactGuardSchema.optional(),
|
|
43891
|
-
ops: array(TransactOpSchema).readonly()
|
|
43892
|
-
}), object({
|
|
43893
|
-
applied: boolean(),
|
|
43894
|
-
current: unknown().optional()
|
|
43895
|
-
}), {
|
|
43896
|
-
kind: "mutation",
|
|
43897
|
-
auth: "admin"
|
|
43898
|
-
}), method(object({
|
|
43899
|
-
namespace: string().optional(),
|
|
43900
|
-
collection: string(),
|
|
43901
|
-
id: string(),
|
|
43902
|
-
data: record(string(), unknown())
|
|
43903
|
-
}), _void(), {
|
|
43904
|
-
kind: "mutation",
|
|
43905
|
-
auth: "admin"
|
|
43906
|
-
}), method(object({
|
|
43907
|
-
namespace: string().optional(),
|
|
43908
|
-
collection: string(),
|
|
43909
|
-
key: string()
|
|
43910
|
-
}), _void(), {
|
|
43911
|
-
kind: "mutation",
|
|
43912
|
-
auth: "admin"
|
|
43913
|
-
}), method(object({
|
|
43914
|
-
namespace: string().optional(),
|
|
43915
|
-
collection: string(),
|
|
43916
|
-
filter: MutationFilterSchema
|
|
43917
|
-
}), object({ deleted: number().int() }), {
|
|
43918
|
-
kind: "mutation",
|
|
43919
|
-
auth: "admin"
|
|
43920
|
-
}), method(object({
|
|
43921
|
-
namespace: string().optional(),
|
|
43922
|
-
collection: string(),
|
|
43923
|
-
filter: MutationFilterSchema,
|
|
43924
|
-
data: record(string(), unknown())
|
|
43925
|
-
}), object({ updated: number().int() }), {
|
|
43926
|
-
kind: "mutation",
|
|
43927
|
-
auth: "admin"
|
|
43928
|
-
}), method(object({
|
|
43929
|
-
namespace: string().optional(),
|
|
43930
|
-
collection: string(),
|
|
43931
|
-
filter: QueryFilterSchema.optional()
|
|
43932
|
-
}), number(), { auth: "admin" }), method(object({
|
|
43933
|
-
namespace: string().optional(),
|
|
43934
|
-
collection: string(),
|
|
43935
|
-
fields: array(AggregateFieldSchema).readonly(),
|
|
43936
|
-
filter: QueryFilterSchema.optional()
|
|
43937
|
-
}), AggregateResultSchema, { auth: "admin" }), method(object({
|
|
43938
|
-
namespace: string().optional(),
|
|
43939
|
-
collection: string(),
|
|
43940
|
-
field: string(),
|
|
43941
|
-
bucketSize: number().int().positive(),
|
|
43942
|
-
origin: number().int(),
|
|
43943
|
-
filter: QueryFilterSchema.optional()
|
|
43944
|
-
}), array(object({
|
|
43945
|
-
bucket: number().int(),
|
|
43946
|
-
count: number().int()
|
|
43947
|
-
})).readonly(), { auth: "admin" }), method(object({
|
|
43948
|
-
namespace: string().optional(),
|
|
43949
|
-
collection: string()
|
|
43950
|
-
}), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
|
|
43951
|
-
kind: "mutation",
|
|
43952
|
-
auth: "admin"
|
|
43953
|
-
});
|
|
43954
|
-
/**
|
|
43955
44456
|
* How long one snapshot may take end to end: the pinned copy of the whole
|
|
43956
44457
|
* database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
|
|
43957
44458
|
* disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
|
|
@@ -45914,7 +46415,7 @@ method(object({
|
|
|
45914
46415
|
kind: "mutation",
|
|
45915
46416
|
auth: "admin"
|
|
45916
46417
|
}), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
|
|
45917
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
46418
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
45918
46419
|
var NUMBER = {
|
|
45919
46420
|
kind: "fixed",
|
|
45920
46421
|
result: ["number"]
|
|
@@ -46361,6 +46862,117 @@ function actuator(input) {
|
|
|
46361
46862
|
};
|
|
46362
46863
|
}
|
|
46363
46864
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
46865
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
46866
|
+
"no-power-source",
|
|
46867
|
+
"switch-missing",
|
|
46868
|
+
"switch-offline",
|
|
46869
|
+
"switch-unknown",
|
|
46870
|
+
"restart-in-progress",
|
|
46871
|
+
"switch-call-failed"
|
|
46872
|
+
]);
|
|
46873
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
46874
|
+
accepted: literal(false),
|
|
46875
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
46876
|
+
/** One line for the operator, naming the switch. */
|
|
46877
|
+
message: string()
|
|
46878
|
+
})]);
|
|
46879
|
+
var PowerSourceInfoSchema = object({
|
|
46880
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
46881
|
+
linked: boolean(),
|
|
46882
|
+
switchDeviceId: number().nullable(),
|
|
46883
|
+
switchName: string().nullable(),
|
|
46884
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
46885
|
+
switchOn: boolean().nullable(),
|
|
46886
|
+
switchOnline: boolean().nullable(),
|
|
46887
|
+
restartInProgress: boolean(),
|
|
46888
|
+
offSeconds: number().int()
|
|
46889
|
+
});
|
|
46890
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
46891
|
+
var PowerSourceStateSchema = object({
|
|
46892
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
46893
|
+
switchOn: boolean().nullable(),
|
|
46894
|
+
offSeconds: number().int(),
|
|
46895
|
+
lastFetchedAt: number()
|
|
46896
|
+
});
|
|
46897
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
46898
|
+
DeviceType.Gateway,
|
|
46899
|
+
DeviceType.Hub,
|
|
46900
|
+
DeviceType.Camera,
|
|
46901
|
+
DeviceType.Sensor,
|
|
46902
|
+
DeviceType.MediaPlayer,
|
|
46903
|
+
DeviceType.Vacuum,
|
|
46904
|
+
DeviceType.Generic,
|
|
46905
|
+
DeviceType.Washer,
|
|
46906
|
+
DeviceType.Dryer,
|
|
46907
|
+
DeviceType.Dishwasher,
|
|
46908
|
+
DeviceType.PetFountain,
|
|
46909
|
+
DeviceType.Fan,
|
|
46910
|
+
DeviceType.Humidifier,
|
|
46911
|
+
DeviceType.Climate,
|
|
46912
|
+
DeviceType.WaterHeater
|
|
46913
|
+
];
|
|
46914
|
+
var powerSourceCustomize = {
|
|
46915
|
+
version: 1,
|
|
46916
|
+
targets: [{
|
|
46917
|
+
id: "switch",
|
|
46918
|
+
cardinality: "scalar",
|
|
46919
|
+
vocabulary: {
|
|
46920
|
+
cap: "power-source",
|
|
46921
|
+
stateField: "switchOn"
|
|
46922
|
+
},
|
|
46923
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
46924
|
+
apply: "add",
|
|
46925
|
+
path: "switchOn",
|
|
46926
|
+
sources: [{
|
|
46927
|
+
kind: "switch",
|
|
46928
|
+
transforms: [{ kind: "identity" }]
|
|
46929
|
+
}],
|
|
46930
|
+
companions: [{
|
|
46931
|
+
kind: "constant",
|
|
46932
|
+
path: "offSeconds",
|
|
46933
|
+
value: 10,
|
|
46934
|
+
when: "add"
|
|
46935
|
+
}, {
|
|
46936
|
+
kind: "clock",
|
|
46937
|
+
path: "lastFetchedAt",
|
|
46938
|
+
when: "always"
|
|
46939
|
+
}],
|
|
46940
|
+
sourceReport: { status: "assignment-status" }
|
|
46941
|
+
}]
|
|
46942
|
+
};
|
|
46943
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
46944
|
+
var powerSourceCapability = {
|
|
46945
|
+
name: "power-source",
|
|
46946
|
+
scope: "device",
|
|
46947
|
+
mode: "singleton",
|
|
46948
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
46949
|
+
methods: {
|
|
46950
|
+
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
|
|
46951
|
+
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46952
|
+
kind: "mutation",
|
|
46953
|
+
auth: "admin"
|
|
46954
|
+
}),
|
|
46955
|
+
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46956
|
+
kind: "mutation",
|
|
46957
|
+
auth: "admin"
|
|
46958
|
+
}),
|
|
46959
|
+
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46960
|
+
kind: "mutation",
|
|
46961
|
+
auth: "admin"
|
|
46962
|
+
})
|
|
46963
|
+
},
|
|
46964
|
+
runtimeState: PowerSourceStateSchema,
|
|
46965
|
+
customize: powerSourceCustomize,
|
|
46966
|
+
/**
|
|
46967
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
46968
|
+
* its next report; a restored value would outlive the reading it names.
|
|
46969
|
+
*
|
|
46970
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
46971
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
46972
|
+
*/
|
|
46973
|
+
durability: "session",
|
|
46974
|
+
history: "none"
|
|
46975
|
+
};
|
|
46364
46976
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
46365
46977
|
DeviceType.Camera;
|
|
46366
46978
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -46925,6 +47537,57 @@ id: union([string(), number()]) }), {
|
|
|
46925
47537
|
kind: "mutation",
|
|
46926
47538
|
auth: "admin"
|
|
46927
47539
|
});
|
|
47540
|
+
method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
|
|
47541
|
+
id: string().min(1),
|
|
47542
|
+
error: string().min(1)
|
|
47543
|
+
}), MigrationLedgerEntrySchema, {
|
|
47544
|
+
kind: "mutation",
|
|
47545
|
+
auth: "admin"
|
|
47546
|
+
}), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
|
|
47547
|
+
kind: "mutation",
|
|
47548
|
+
auth: "admin"
|
|
47549
|
+
}), method(object({
|
|
47550
|
+
database: DataStoreDatabaseSchema,
|
|
47551
|
+
snapshotId: string().min(1)
|
|
47552
|
+
}), object({
|
|
47553
|
+
scheduled: boolean(),
|
|
47554
|
+
held: array(string()),
|
|
47555
|
+
restartRequired: boolean()
|
|
47556
|
+
}), {
|
|
47557
|
+
kind: "mutation",
|
|
47558
|
+
auth: "admin"
|
|
47559
|
+
}), method(object({
|
|
47560
|
+
owner: string().min(1),
|
|
47561
|
+
metas: array(MigrationMetaSchema)
|
|
47562
|
+
}), object({
|
|
47563
|
+
bootId: string(),
|
|
47564
|
+
snapshot: _enum([
|
|
47565
|
+
"none",
|
|
47566
|
+
"written",
|
|
47567
|
+
"refused"
|
|
47568
|
+
]),
|
|
47569
|
+
steps: array(MigrationOwnerStepSchema)
|
|
47570
|
+
}), {
|
|
47571
|
+
kind: "mutation",
|
|
47572
|
+
auth: "admin"
|
|
47573
|
+
}), method(object({ id: string().min(1) }), object({
|
|
47574
|
+
proceed: boolean(),
|
|
47575
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
47576
|
+
reason: string().nullable()
|
|
47577
|
+
}), {
|
|
47578
|
+
kind: "mutation",
|
|
47579
|
+
auth: "admin"
|
|
47580
|
+
}), method(object({
|
|
47581
|
+
id: string().min(1),
|
|
47582
|
+
result: MigrationOwnerResultSchema
|
|
47583
|
+
}), object({
|
|
47584
|
+
status: MigrationStatusSchema,
|
|
47585
|
+
error: string().nullable(),
|
|
47586
|
+
rowsChanged: number().int().nonnegative()
|
|
47587
|
+
}), {
|
|
47588
|
+
kind: "mutation",
|
|
47589
|
+
auth: "admin"
|
|
47590
|
+
});
|
|
46928
47591
|
/**
|
|
46929
47592
|
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
46930
47593
|
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
@@ -47735,6 +48398,101 @@ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly())
|
|
|
47735
48398
|
kind: "mutation",
|
|
47736
48399
|
auth: "admin"
|
|
47737
48400
|
});
|
|
48401
|
+
/**
|
|
48402
|
+
* device-runs - the durable record of ONE run of a device: a washing cycle, a
|
|
48403
|
+
* dishwasher cycle, later a mow or a clean (D992).
|
|
48404
|
+
*
|
|
48405
|
+
* A run is a DOCUMENT (opened at one instant, closed at another, carrying what
|
|
48406
|
+
* was measured over it), never a sample - device-history keeps the numbers over
|
|
48407
|
+
* time and its `device-runs` provider keeps these documents beside them, because
|
|
48408
|
+
* the energy of a run is a question about device-history's own series. The
|
|
48409
|
+
* record is written from the `running-state` flip on `DeviceStateChanged`; it
|
|
48410
|
+
* survives a restart (the open row is on disk) and a missed flip (boot compares
|
|
48411
|
+
* the open rows against the live snapshots).
|
|
48412
|
+
*
|
|
48413
|
+
* Every measurement is `number | null` and `null` means "could not be
|
|
48414
|
+
* measured", never `0` (D393). A run closed by the boot reconcile because the
|
|
48415
|
+
* device stopped while nobody was watching has `outcome: 'unknown'`.
|
|
48416
|
+
*/
|
|
48417
|
+
var DeviceRunKindSchema = _enum([
|
|
48418
|
+
"appliance",
|
|
48419
|
+
"mower",
|
|
48420
|
+
"vacuum"
|
|
48421
|
+
]);
|
|
48422
|
+
var DeviceRunStateSchema = _enum(["open", "closed"]);
|
|
48423
|
+
var DeviceRunOutcomeSchema = _enum([
|
|
48424
|
+
"completed",
|
|
48425
|
+
"aborted",
|
|
48426
|
+
"error",
|
|
48427
|
+
"unknown"
|
|
48428
|
+
]);
|
|
48429
|
+
/** What opened or closed the run. `reconcile` is the boot comparison against live state. */
|
|
48430
|
+
var DeviceRunSourceSchema = _enum([
|
|
48431
|
+
"running-state",
|
|
48432
|
+
"activity",
|
|
48433
|
+
"reconcile"
|
|
48434
|
+
]);
|
|
48435
|
+
/** How the energy of a run was obtained; `null` when it could not be. */
|
|
48436
|
+
var DeviceRunEnergyMethodSchema = _enum(["counter-delta", "history-integral"]);
|
|
48437
|
+
var DeviceRunPhaseSchema = object({
|
|
48438
|
+
phase: string(),
|
|
48439
|
+
at: number()
|
|
48440
|
+
});
|
|
48441
|
+
/** The kind-specific block; a later phase adds a branch, nothing else changes. */
|
|
48442
|
+
var DeviceRunDetailSchema = discriminatedUnion("kind", [object({
|
|
48443
|
+
kind: literal("appliance"),
|
|
48444
|
+
/** kWh used over the run. */
|
|
48445
|
+
energyKwh: number().nullable().meta(readingMeta("energy")),
|
|
48446
|
+
energyMethod: DeviceRunEnergyMethodSchema.nullable(),
|
|
48447
|
+
/** Highest power seen while the run was open. */
|
|
48448
|
+
peakW: number().nullable().meta(readingMeta("power")),
|
|
48449
|
+
/** Time-weighted mean power while the run was open. */
|
|
48450
|
+
avgW: number().nullable().meta(readingMeta("power")),
|
|
48451
|
+
/** The program the appliance said it ran when the run opened. */
|
|
48452
|
+
program: string().nullable(),
|
|
48453
|
+
remainingAtStartSec: number().nullable().meta(readingMeta("duration")),
|
|
48454
|
+
/** The last phase the device reported before the run closed. */
|
|
48455
|
+
finalPhase: string().nullable(),
|
|
48456
|
+
/**
|
|
48457
|
+
* The meter's energy counter when the run opened; `null` when the meter had
|
|
48458
|
+
* not said (or the run was opened late, by the boot reconcile). It lives in
|
|
48459
|
+
* the row so a run that outlives a restart still ends with `end - start`.
|
|
48460
|
+
*/
|
|
48461
|
+
energyStartKwh: number().nullable().meta(readingMeta("energy")),
|
|
48462
|
+
/** Milliseconds of power readings behind `avgW` - the weight a restart resumes the mean with. */
|
|
48463
|
+
powerObservedMs: number()
|
|
48464
|
+
})]);
|
|
48465
|
+
var DeviceRunSchema = object({
|
|
48466
|
+
/** `${deviceId}:${startedAt}`. */
|
|
48467
|
+
runId: string(),
|
|
48468
|
+
deviceId: number().int(),
|
|
48469
|
+
kind: DeviceRunKindSchema,
|
|
48470
|
+
state: DeviceRunStateSchema,
|
|
48471
|
+
startedAt: number(),
|
|
48472
|
+
endedAt: number().nullable(),
|
|
48473
|
+
durationMs: number().nullable(),
|
|
48474
|
+
outcome: DeviceRunOutcomeSchema.nullable(),
|
|
48475
|
+
startSource: DeviceRunSourceSchema,
|
|
48476
|
+
endSource: DeviceRunSourceSchema.nullable(),
|
|
48477
|
+
/** Where the device's running bit came from (`running-state.source`); `null` when it did not say. */
|
|
48478
|
+
runningSource: _enum(["native", "assigned"]).nullable(),
|
|
48479
|
+
phases: array(DeviceRunPhaseSchema),
|
|
48480
|
+
detail: DeviceRunDetailSchema
|
|
48481
|
+
});
|
|
48482
|
+
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, method(object({
|
|
48483
|
+
deviceId: number().int(),
|
|
48484
|
+
outcome: DeviceRunOutcomeSchema.optional(),
|
|
48485
|
+
atFrom: number().optional(),
|
|
48486
|
+
atTo: number().optional(),
|
|
48487
|
+
limit: number().int().positive().max(200).default(50),
|
|
48488
|
+
cursor: string().optional()
|
|
48489
|
+
}), object({
|
|
48490
|
+
runs: array(DeviceRunSchema).readonly(),
|
|
48491
|
+
nextCursor: string().nullable()
|
|
48492
|
+
})), method(object({
|
|
48493
|
+
deviceId: number().int(),
|
|
48494
|
+
runId: string()
|
|
48495
|
+
}), DeviceRunSchema.nullable()), method(object({ deviceId: number().int() }), DeviceRunSchema.nullable());
|
|
47738
48496
|
var DeviceCaStatusSchema = discriminatedUnion("state", [
|
|
47739
48497
|
object({ state: literal("absent") }),
|
|
47740
48498
|
object({
|
|
@@ -47870,8 +48628,8 @@ method(object({}), object({
|
|
|
47870
48628
|
});
|
|
47871
48629
|
var KeychainKeySourceSchema = object({
|
|
47872
48630
|
kind: _enum([
|
|
47873
|
-
"
|
|
47874
|
-
"previous-
|
|
48631
|
+
"mounted-file",
|
|
48632
|
+
"previous-mounted-file",
|
|
47875
48633
|
"platform",
|
|
47876
48634
|
"default-file"
|
|
47877
48635
|
]),
|
|
@@ -52430,7 +53188,8 @@ var PALETTE_ENTRY_IDS = [
|
|
|
52430
53188
|
"switch",
|
|
52431
53189
|
"siren",
|
|
52432
53190
|
"button",
|
|
52433
|
-
"doorbell"
|
|
53191
|
+
"doorbell",
|
|
53192
|
+
"appliance-cycle"
|
|
52434
53193
|
];
|
|
52435
53194
|
var AUDIO_ENTRY_ID = "audio";
|
|
52436
53195
|
/** Six rungs, quiet → loud. The opaque end is the entry's hex. */
|
|
@@ -52501,7 +53260,8 @@ var CAMSTACK_HEX = {
|
|
|
52501
53260
|
switch: "#7a812a",
|
|
52502
53261
|
siren: "#ac5d7f",
|
|
52503
53262
|
button: "#b276cd",
|
|
52504
|
-
doorbell: "#0087ab"
|
|
53263
|
+
doorbell: "#0087ab",
|
|
53264
|
+
"appliance-cycle": "#2a8c8c"
|
|
52505
53265
|
};
|
|
52506
53266
|
var CAMSTACK_FALLBACK = [
|
|
52507
53267
|
"#b34dc7",
|
|
@@ -52566,7 +53326,8 @@ var HIGH_CONTRAST_HEX = {
|
|
|
52566
53326
|
switch: "#d05259",
|
|
52567
53327
|
siren: "#cd67b3",
|
|
52568
53328
|
button: "#c9746a",
|
|
52569
|
-
doorbell: "#338eeb"
|
|
53329
|
+
doorbell: "#338eeb",
|
|
53330
|
+
"appliance-cycle": "#00848a"
|
|
52570
53331
|
};
|
|
52571
53332
|
var HIGH_CONTRAST_FALLBACK = [
|
|
52572
53333
|
"#817f3b",
|
|
@@ -52631,7 +53392,8 @@ var OKABE_ITO_HEX = {
|
|
|
52631
53392
|
switch: "#ad2c4e",
|
|
52632
53393
|
siren: "#348e08",
|
|
52633
53394
|
button: "#d27303",
|
|
52634
|
-
doorbell: "#55770f"
|
|
53395
|
+
doorbell: "#55770f",
|
|
53396
|
+
"appliance-cycle": "#00737d"
|
|
52635
53397
|
};
|
|
52636
53398
|
var OKABE_ITO_FALLBACK = [
|
|
52637
53399
|
"#9a3784",
|
|
@@ -52840,6 +53602,7 @@ sub("switch", "control", "control", "switch", "Switch");
|
|
|
52840
53602
|
sub("siren", "control", "control", "siren", "Siren");
|
|
52841
53603
|
sub("button", "control", "control", "button", "Button");
|
|
52842
53604
|
sub("doorbell", "control", "control", "doorbell", "Doorbell");
|
|
53605
|
+
sub("appliance-cycle", "control", "control", "switch", "Appliance cycle");
|
|
52843
53606
|
for (const l of AUDIO_MACRO_LABELS) {
|
|
52844
53607
|
const kind = `audio-${l.id}`;
|
|
52845
53608
|
if (entries.has(kind)) continue;
|
|
@@ -61487,7 +62250,8 @@ var CAP_TO_KIND = {
|
|
|
61487
62250
|
cover: "cover",
|
|
61488
62251
|
switch: "switch",
|
|
61489
62252
|
button: "button",
|
|
61490
|
-
doorbell: "doorbell"
|
|
62253
|
+
doorbell: "doorbell",
|
|
62254
|
+
"running-state": "appliance-cycle"
|
|
61491
62255
|
};
|
|
61492
62256
|
function buildDescriptor(capName, kind) {
|
|
61493
62257
|
const t = EVENT_TAXONOMY[kind];
|
|
@@ -61868,7 +62632,18 @@ var ServerUpdateActionResultSchema = object({
|
|
|
61868
62632
|
* store (D877), present when the pre-flight refused the update
|
|
61869
62633
|
* (`reason: 'addon-would-refuse'`). Optional for version skew.
|
|
61870
62634
|
*/
|
|
61871
|
-
blockers: array(UpdateBlockerSchema).optional()
|
|
62635
|
+
blockers: array(UpdateBlockerSchema).optional(),
|
|
62636
|
+
/**
|
|
62637
|
+
* The minimums the target server declares and this node does not meet
|
|
62638
|
+
* (D989), present when the update was refused with
|
|
62639
|
+
* `reason: 'requirements-unmet'`. Each names the versions on both sides.
|
|
62640
|
+
*/
|
|
62641
|
+
unmet: array(UnmetRequirementSchema).optional(),
|
|
62642
|
+
/**
|
|
62643
|
+
* The addons this node has below the minimum the staged server declares —
|
|
62644
|
+
* the ones to update WITH the server. Present on an accepted update.
|
|
62645
|
+
*/
|
|
62646
|
+
requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
|
|
61872
62647
|
});
|
|
61873
62648
|
var LaunchOverridesSchema = object({
|
|
61874
62649
|
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
@@ -64484,7 +65259,9 @@ var PackageUpdateSchema = object({
|
|
|
64484
65259
|
latestVersion: string(),
|
|
64485
65260
|
category: _enum(["addon", "core"]),
|
|
64486
65261
|
requiresRestart: boolean(),
|
|
64487
|
-
isSystem: boolean()
|
|
65262
|
+
isSystem: boolean(),
|
|
65263
|
+
/** Held: what the newer version needs and this node does not meet (D989). */
|
|
65264
|
+
blockedBy: array(UnmetRequirementSchema).readonly().optional()
|
|
64488
65265
|
});
|
|
64489
65266
|
var PackageVersionInfoSchema = object({
|
|
64490
65267
|
version: string(),
|
|
@@ -64676,10 +65453,7 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
64676
65453
|
}), InstallPackageResultSchema, {
|
|
64677
65454
|
kind: "mutation",
|
|
64678
65455
|
auth: "admin"
|
|
64679
|
-
}), method(object({
|
|
64680
|
-
kind: "mutation",
|
|
64681
|
-
auth: "admin"
|
|
64682
|
-
}), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({
|
|
65456
|
+
}), method(object({
|
|
64683
65457
|
packageName: string().min(1),
|
|
64684
65458
|
nodeId: string().optional(),
|
|
64685
65459
|
everywhere: boolean().optional()
|
|
@@ -65940,19 +66714,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
|
|
|
65940
66714
|
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
65941
66715
|
detail: string()
|
|
65942
66716
|
})]);
|
|
65943
|
-
var deviceInput
|
|
66717
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
65944
66718
|
DeviceType.MediaPlayer;
|
|
65945
|
-
method(deviceInput
|
|
66719
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
65946
66720
|
kind: "mutation",
|
|
65947
66721
|
auth: "admin"
|
|
65948
|
-
}), method(deviceInput
|
|
66722
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
65949
66723
|
role: RemoteRoleSchema,
|
|
65950
66724
|
/** Sends in a row (volume steps). */
|
|
65951
66725
|
repeat: number().int().min(1).max(50).default(1)
|
|
65952
66726
|
}), RemoteSendResultSchema, {
|
|
65953
66727
|
kind: "mutation",
|
|
65954
66728
|
auth: "protected"
|
|
65955
|
-
}), method(deviceInput
|
|
66729
|
+
}), method(deviceInput.extend({
|
|
65956
66730
|
command: RemoteMediaCommandSchema,
|
|
65957
66731
|
muted: boolean().optional(),
|
|
65958
66732
|
source: string().optional(),
|
|
@@ -69818,7 +70592,7 @@ var lawnMowerControlCapability = {
|
|
|
69818
70592
|
* clients — they reverse-connect to the hub. Exposing their interfaces
|
|
69819
70593
|
* via the same surface would leak internal topology with no upside.
|
|
69820
70594
|
*
|
|
69821
|
-
* Implementation in `@camstack/system/builtins/local-network/`.
|
|
70595
|
+
* Implementation in `@camstack/server/system/builtins/local-network/`.
|
|
69822
70596
|
*/
|
|
69823
70597
|
/** Coarse classification derived from the interface name + IP range. */
|
|
69824
70598
|
var InterfaceKindEnum = _enum([
|
|
@@ -70315,109 +71089,6 @@ var lockControlCapability = {
|
|
|
70315
71089
|
* whether persisting is worth a SQLite commit. */
|
|
70316
71090
|
volatileStateFields: ["lastChangedAt"]
|
|
70317
71091
|
};
|
|
70318
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
70319
|
-
"no-power-source",
|
|
70320
|
-
"switch-missing",
|
|
70321
|
-
"switch-offline",
|
|
70322
|
-
"switch-unknown",
|
|
70323
|
-
"restart-in-progress",
|
|
70324
|
-
"switch-call-failed"
|
|
70325
|
-
]);
|
|
70326
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
70327
|
-
accepted: literal(false),
|
|
70328
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
70329
|
-
/** One line for the operator, naming the switch. */
|
|
70330
|
-
message: string()
|
|
70331
|
-
})]);
|
|
70332
|
-
var PowerSourceInfoSchema = object({
|
|
70333
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
70334
|
-
linked: boolean(),
|
|
70335
|
-
switchDeviceId: number().nullable(),
|
|
70336
|
-
switchName: string().nullable(),
|
|
70337
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
70338
|
-
switchOn: boolean().nullable(),
|
|
70339
|
-
switchOnline: boolean().nullable(),
|
|
70340
|
-
restartInProgress: boolean(),
|
|
70341
|
-
offSeconds: number().int()
|
|
70342
|
-
});
|
|
70343
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
70344
|
-
var PowerSourceStateSchema = object({
|
|
70345
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
70346
|
-
switchOn: boolean().nullable(),
|
|
70347
|
-
offSeconds: number().int(),
|
|
70348
|
-
lastFetchedAt: number()
|
|
70349
|
-
});
|
|
70350
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
70351
|
-
DeviceType.Gateway,
|
|
70352
|
-
DeviceType.Hub,
|
|
70353
|
-
DeviceType.Camera,
|
|
70354
|
-
DeviceType.Sensor,
|
|
70355
|
-
DeviceType.MediaPlayer,
|
|
70356
|
-
DeviceType.Vacuum,
|
|
70357
|
-
DeviceType.Generic
|
|
70358
|
-
];
|
|
70359
|
-
var powerSourceCustomize = {
|
|
70360
|
-
version: 1,
|
|
70361
|
-
targets: [{
|
|
70362
|
-
id: "switch",
|
|
70363
|
-
cardinality: "scalar",
|
|
70364
|
-
vocabulary: {
|
|
70365
|
-
cap: "power-source",
|
|
70366
|
-
stateField: "switchOn"
|
|
70367
|
-
},
|
|
70368
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70369
|
-
apply: "add",
|
|
70370
|
-
path: "switchOn",
|
|
70371
|
-
sources: [{
|
|
70372
|
-
kind: "switch",
|
|
70373
|
-
transforms: [{ kind: "identity" }]
|
|
70374
|
-
}],
|
|
70375
|
-
companions: [{
|
|
70376
|
-
kind: "constant",
|
|
70377
|
-
path: "offSeconds",
|
|
70378
|
-
value: 10,
|
|
70379
|
-
when: "add"
|
|
70380
|
-
}, {
|
|
70381
|
-
kind: "clock",
|
|
70382
|
-
path: "lastFetchedAt",
|
|
70383
|
-
when: "always"
|
|
70384
|
-
}],
|
|
70385
|
-
sourceReport: { status: "assignment-status" }
|
|
70386
|
-
}]
|
|
70387
|
-
};
|
|
70388
|
-
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
70389
|
-
var powerSourceCapability = {
|
|
70390
|
-
name: "power-source",
|
|
70391
|
-
scope: "device",
|
|
70392
|
-
mode: "singleton",
|
|
70393
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70394
|
-
methods: {
|
|
70395
|
-
getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
|
|
70396
|
-
powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
70397
|
-
kind: "mutation",
|
|
70398
|
-
auth: "admin"
|
|
70399
|
-
}),
|
|
70400
|
-
powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
70401
|
-
kind: "mutation",
|
|
70402
|
-
auth: "admin"
|
|
70403
|
-
}),
|
|
70404
|
-
forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
70405
|
-
kind: "mutation",
|
|
70406
|
-
auth: "admin"
|
|
70407
|
-
})
|
|
70408
|
-
},
|
|
70409
|
-
runtimeState: PowerSourceStateSchema,
|
|
70410
|
-
customize: powerSourceCustomize,
|
|
70411
|
-
/**
|
|
70412
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
70413
|
-
* its next report; a restored value would outlive the reading it names.
|
|
70414
|
-
*
|
|
70415
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
70416
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
70417
|
-
*/
|
|
70418
|
-
durability: "session",
|
|
70419
|
-
history: "none"
|
|
70420
|
-
};
|
|
70421
71092
|
/**
|
|
70422
71093
|
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
70423
71094
|
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
@@ -71698,7 +72369,7 @@ onStatusChanged: { data: object({
|
|
|
71698
72369
|
* jitter, and observed/peak bandwidth per device + per client.
|
|
71699
72370
|
*
|
|
71700
72371
|
* Implementation lives in the server's `NetworkQualityService` (thin
|
|
71701
|
-
* wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
|
|
72372
|
+
* wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
|
|
71702
72373
|
* The provider is registered from `trpc.router.ts` against the existing
|
|
71703
72374
|
* service instance — no addon owns this state.
|
|
71704
72375
|
*
|
|
@@ -73849,7 +74520,7 @@ var ptzAutotrackCapability = {
|
|
|
73849
74520
|
* - **Native**: the camera's own firmware autotrack, registered per device by
|
|
73850
74521
|
* the driver when the probe says it exists (Reolink: yes; Hikvision:
|
|
73851
74522
|
* model-dependent).
|
|
73852
|
-
* - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
|
|
74523
|
+
* - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
|
|
73853
74524
|
* which follows a subject with `ptz.move` pulses driven by the detection
|
|
73854
74525
|
* pipeline. `defaultActive: false` — the operator binds it per camera in
|
|
73855
74526
|
* the bindings UI.
|
|
@@ -73967,6 +74638,10 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
|
|
|
73967
74638
|
/** Generic Customize target for the running boolean (D936). */
|
|
73968
74639
|
var runningCustomize = {
|
|
73969
74640
|
version: 1,
|
|
74641
|
+
overridable: [{
|
|
74642
|
+
path: "running",
|
|
74643
|
+
by: "running"
|
|
74644
|
+
}],
|
|
73970
74645
|
targets: [{
|
|
73971
74646
|
id: "running",
|
|
73972
74647
|
cardinality: "scalar",
|
|
@@ -74024,7 +74699,7 @@ var runningCustomize = {
|
|
|
74024
74699
|
kind: "constant",
|
|
74025
74700
|
path: "phase",
|
|
74026
74701
|
value: null,
|
|
74027
|
-
when: "
|
|
74702
|
+
when: "add"
|
|
74028
74703
|
},
|
|
74029
74704
|
{
|
|
74030
74705
|
kind: "clock",
|
|
@@ -77308,7 +77983,7 @@ var ExternalIdentityKeySchema = object({
|
|
|
77308
77983
|
provider: string().min(1),
|
|
77309
77984
|
subject: string().min(1)
|
|
77310
77985
|
});
|
|
77311
|
-
method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
|
|
77986
|
+
method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), object({ defaultPasswordInUse: boolean() }), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
|
|
77312
77987
|
kind: "mutation",
|
|
77313
77988
|
auth: "admin",
|
|
77314
77989
|
access: "create"
|
|
@@ -80684,13 +81359,6 @@ Object.freeze({
|
|
|
80684
81359
|
access: "view",
|
|
80685
81360
|
role: "admin"
|
|
80686
81361
|
},
|
|
80687
|
-
"addons.installFromWorkspace": {
|
|
80688
|
-
capName: "addons",
|
|
80689
|
-
capScope: "system",
|
|
80690
|
-
addonId: null,
|
|
80691
|
-
access: "create",
|
|
80692
|
-
role: "admin"
|
|
80693
|
-
},
|
|
80694
81362
|
"addons.installPackage": {
|
|
80695
81363
|
capName: "addons",
|
|
80696
81364
|
capScope: "system",
|
|
@@ -80698,13 +81366,6 @@ Object.freeze({
|
|
|
80698
81366
|
access: "create",
|
|
80699
81367
|
role: "admin"
|
|
80700
81368
|
},
|
|
80701
|
-
"addons.isWorkspaceAvailable": {
|
|
80702
|
-
capName: "addons",
|
|
80703
|
-
capScope: "system",
|
|
80704
|
-
addonId: null,
|
|
80705
|
-
access: "view",
|
|
80706
|
-
role: "admin"
|
|
80707
|
-
},
|
|
80708
81369
|
"addons.list": {
|
|
80709
81370
|
capName: "addons",
|
|
80710
81371
|
capScope: "system",
|
|
@@ -80754,13 +81415,6 @@ Object.freeze({
|
|
|
80754
81415
|
access: "view",
|
|
80755
81416
|
role: "admin"
|
|
80756
81417
|
},
|
|
80757
|
-
"addons.listWorkspacePackages": {
|
|
80758
|
-
capName: "addons",
|
|
80759
|
-
capScope: "system",
|
|
80760
|
-
addonId: null,
|
|
80761
|
-
access: "view",
|
|
80762
|
-
role: "admin"
|
|
80763
|
-
},
|
|
80764
81418
|
"addons.onAddonLogs": {
|
|
80765
81419
|
capName: "addons",
|
|
80766
81420
|
capScope: "system",
|
|
@@ -83428,6 +84082,27 @@ Object.freeze({
|
|
|
83428
84082
|
access: "create",
|
|
83429
84083
|
role: "admin"
|
|
83430
84084
|
},
|
|
84085
|
+
"deviceRuns.getCurrentRun": {
|
|
84086
|
+
capName: "device-runs",
|
|
84087
|
+
capScope: "device",
|
|
84088
|
+
addonId: null,
|
|
84089
|
+
access: "view",
|
|
84090
|
+
role: "viewer"
|
|
84091
|
+
},
|
|
84092
|
+
"deviceRuns.getRun": {
|
|
84093
|
+
capName: "device-runs",
|
|
84094
|
+
capScope: "device",
|
|
84095
|
+
addonId: null,
|
|
84096
|
+
access: "view",
|
|
84097
|
+
role: "viewer"
|
|
84098
|
+
},
|
|
84099
|
+
"deviceRuns.listRuns": {
|
|
84100
|
+
capName: "device-runs",
|
|
84101
|
+
capScope: "device",
|
|
84102
|
+
addonId: null,
|
|
84103
|
+
access: "view",
|
|
84104
|
+
role: "viewer"
|
|
84105
|
+
},
|
|
83431
84106
|
"deviceState.claimFields": {
|
|
83432
84107
|
capName: "device-state",
|
|
83433
84108
|
capScope: "system",
|
|
@@ -84968,6 +85643,69 @@ Object.freeze({
|
|
|
84968
85643
|
access: "view",
|
|
84969
85644
|
role: "admin"
|
|
84970
85645
|
},
|
|
85646
|
+
"migrations.declareOwner": {
|
|
85647
|
+
capName: "migrations",
|
|
85648
|
+
capScope: "system",
|
|
85649
|
+
addonId: null,
|
|
85650
|
+
access: "create",
|
|
85651
|
+
role: "admin"
|
|
85652
|
+
},
|
|
85653
|
+
"migrations.dryRun": {
|
|
85654
|
+
capName: "migrations",
|
|
85655
|
+
capScope: "system",
|
|
85656
|
+
addonId: null,
|
|
85657
|
+
access: "view",
|
|
85658
|
+
role: "admin"
|
|
85659
|
+
},
|
|
85660
|
+
"migrations.finishMigration": {
|
|
85661
|
+
capName: "migrations",
|
|
85662
|
+
capScope: "system",
|
|
85663
|
+
addonId: null,
|
|
85664
|
+
access: "create",
|
|
85665
|
+
role: "admin"
|
|
85666
|
+
},
|
|
85667
|
+
"migrations.listLedger": {
|
|
85668
|
+
capName: "migrations",
|
|
85669
|
+
capScope: "system",
|
|
85670
|
+
addonId: null,
|
|
85671
|
+
access: "view",
|
|
85672
|
+
role: "admin"
|
|
85673
|
+
},
|
|
85674
|
+
"migrations.listPending": {
|
|
85675
|
+
capName: "migrations",
|
|
85676
|
+
capScope: "system",
|
|
85677
|
+
addonId: null,
|
|
85678
|
+
access: "view",
|
|
85679
|
+
role: "admin"
|
|
85680
|
+
},
|
|
85681
|
+
"migrations.markFailed": {
|
|
85682
|
+
capName: "migrations",
|
|
85683
|
+
capScope: "system",
|
|
85684
|
+
addonId: null,
|
|
85685
|
+
access: "create",
|
|
85686
|
+
role: "admin"
|
|
85687
|
+
},
|
|
85688
|
+
"migrations.restorePreMigration": {
|
|
85689
|
+
capName: "migrations",
|
|
85690
|
+
capScope: "system",
|
|
85691
|
+
addonId: null,
|
|
85692
|
+
access: "create",
|
|
85693
|
+
role: "admin"
|
|
85694
|
+
},
|
|
85695
|
+
"migrations.retry": {
|
|
85696
|
+
capName: "migrations",
|
|
85697
|
+
capScope: "system",
|
|
85698
|
+
addonId: null,
|
|
85699
|
+
access: "create",
|
|
85700
|
+
role: "admin"
|
|
85701
|
+
},
|
|
85702
|
+
"migrations.startMigration": {
|
|
85703
|
+
capName: "migrations",
|
|
85704
|
+
capScope: "system",
|
|
85705
|
+
addonId: null,
|
|
85706
|
+
access: "create",
|
|
85707
|
+
role: "admin"
|
|
85708
|
+
},
|
|
84971
85709
|
"modelConvert.convert": {
|
|
84972
85710
|
capName: "model-convert",
|
|
84973
85711
|
capScope: "system",
|
|
@@ -90078,6 +90816,13 @@ Object.freeze({
|
|
|
90078
90816
|
access: "view",
|
|
90079
90817
|
role: "admin"
|
|
90080
90818
|
},
|
|
90819
|
+
"userManagement.getAdminBootstrapStatus": {
|
|
90820
|
+
capName: "user-management",
|
|
90821
|
+
capScope: "system",
|
|
90822
|
+
addonId: null,
|
|
90823
|
+
access: "view",
|
|
90824
|
+
role: "admin"
|
|
90825
|
+
},
|
|
90081
90826
|
"userManagement.getTotpStatus": {
|
|
90082
90827
|
capName: "user-management",
|
|
90083
90828
|
capScope: "system",
|
|
@@ -91574,6 +92319,21 @@ Object.freeze({
|
|
|
91574
92319
|
form: "single",
|
|
91575
92320
|
optional: false
|
|
91576
92321
|
}],
|
|
92322
|
+
"deviceRuns.getCurrentRun": [{
|
|
92323
|
+
name: "deviceId",
|
|
92324
|
+
form: "single",
|
|
92325
|
+
optional: false
|
|
92326
|
+
}],
|
|
92327
|
+
"deviceRuns.getRun": [{
|
|
92328
|
+
name: "deviceId",
|
|
92329
|
+
form: "single",
|
|
92330
|
+
optional: false
|
|
92331
|
+
}],
|
|
92332
|
+
"deviceRuns.listRuns": [{
|
|
92333
|
+
name: "deviceId",
|
|
92334
|
+
form: "single",
|
|
92335
|
+
optional: false
|
|
92336
|
+
}],
|
|
91577
92337
|
"deviceState.claimFields": [{
|
|
91578
92338
|
name: "deviceId",
|
|
91579
92339
|
form: "single",
|
|
@@ -94320,13 +95080,114 @@ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
|
94320
95080
|
*
|
|
94321
95081
|
* Pure, RN-safe: the viewer imports the support rule from here.
|
|
94322
95082
|
*/
|
|
95083
|
+
/**
|
|
95084
|
+
* Control caps that still have a raise and a clear an operator names: an
|
|
95085
|
+
* appliance cycle raises when it STARTS and clears when it FINISHES. Kept out
|
|
95086
|
+
* of `SOURCE_CAP_ACTIVE_FIELD` on purpose — that table also makes a device a
|
|
95087
|
+
* camera-sensor / recorder-trigger source, and a dishwasher is neither.
|
|
95088
|
+
*/
|
|
95089
|
+
var CYCLE_RAISED_FIELD_BY_CAP = { "running-state": "running" };
|
|
94323
95090
|
/** Event kind → the boolean field of its cap slice that means "raised". */
|
|
94324
95091
|
var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
|
|
95092
|
+
const cycleField = CYCLE_RAISED_FIELD_BY_CAP[capName];
|
|
95093
|
+
if (cycleField !== void 0) return [[descriptor.kind, cycleField]];
|
|
94325
95094
|
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
94326
95095
|
if (field === void 0 || descriptor.category !== "sensor") return [];
|
|
94327
95096
|
return [[descriptor.kind, field]];
|
|
94328
95097
|
})));
|
|
94329
95098
|
Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
|
|
95099
|
+
Object.freeze({
|
|
95100
|
+
contact: {
|
|
95101
|
+
starts: {
|
|
95102
|
+
en: "opened",
|
|
95103
|
+
it: "si apre"
|
|
95104
|
+
},
|
|
95105
|
+
finishes: {
|
|
95106
|
+
en: "closed",
|
|
95107
|
+
it: "si chiude"
|
|
95108
|
+
}
|
|
95109
|
+
},
|
|
95110
|
+
"motion-sensor": {
|
|
95111
|
+
starts: {
|
|
95112
|
+
en: "motion detected",
|
|
95113
|
+
it: "movimento rilevato"
|
|
95114
|
+
},
|
|
95115
|
+
finishes: {
|
|
95116
|
+
en: "motion stopped",
|
|
95117
|
+
it: "movimento terminato"
|
|
95118
|
+
}
|
|
95119
|
+
},
|
|
95120
|
+
smoke: {
|
|
95121
|
+
starts: {
|
|
95122
|
+
en: "smoke detected",
|
|
95123
|
+
it: "fumo rilevato"
|
|
95124
|
+
},
|
|
95125
|
+
finishes: {
|
|
95126
|
+
en: "smoke gone",
|
|
95127
|
+
it: "fumo scomparso"
|
|
95128
|
+
}
|
|
95129
|
+
},
|
|
95130
|
+
flood: {
|
|
95131
|
+
starts: {
|
|
95132
|
+
en: "water detected",
|
|
95133
|
+
it: "acqua rilevata"
|
|
95134
|
+
},
|
|
95135
|
+
finishes: {
|
|
95136
|
+
en: "dry again",
|
|
95137
|
+
it: "di nuovo asciutto"
|
|
95138
|
+
}
|
|
95139
|
+
},
|
|
95140
|
+
gas: {
|
|
95141
|
+
starts: {
|
|
95142
|
+
en: "gas detected",
|
|
95143
|
+
it: "gas rilevato"
|
|
95144
|
+
},
|
|
95145
|
+
finishes: {
|
|
95146
|
+
en: "gas gone",
|
|
95147
|
+
it: "gas scomparso"
|
|
95148
|
+
}
|
|
95149
|
+
},
|
|
95150
|
+
"carbon-monoxide": {
|
|
95151
|
+
starts: {
|
|
95152
|
+
en: "carbon monoxide detected",
|
|
95153
|
+
it: "monossido rilevato"
|
|
95154
|
+
},
|
|
95155
|
+
finishes: {
|
|
95156
|
+
en: "carbon monoxide gone",
|
|
95157
|
+
it: "monossido scomparso"
|
|
95158
|
+
}
|
|
95159
|
+
},
|
|
95160
|
+
vibration: {
|
|
95161
|
+
starts: {
|
|
95162
|
+
en: "vibration detected",
|
|
95163
|
+
it: "vibrazione rilevata"
|
|
95164
|
+
},
|
|
95165
|
+
finishes: {
|
|
95166
|
+
en: "vibration stopped",
|
|
95167
|
+
it: "vibrazione terminata"
|
|
95168
|
+
}
|
|
95169
|
+
},
|
|
95170
|
+
tamper: {
|
|
95171
|
+
starts: {
|
|
95172
|
+
en: "tampered with",
|
|
95173
|
+
it: "manomesso"
|
|
95174
|
+
},
|
|
95175
|
+
finishes: {
|
|
95176
|
+
en: "tamper cleared",
|
|
95177
|
+
it: "manomissione rientrata"
|
|
95178
|
+
}
|
|
95179
|
+
},
|
|
95180
|
+
"appliance-cycle": {
|
|
95181
|
+
starts: {
|
|
95182
|
+
en: "the cycle started",
|
|
95183
|
+
it: "il ciclo parte"
|
|
95184
|
+
},
|
|
95185
|
+
finishes: {
|
|
95186
|
+
en: "the cycle finished",
|
|
95187
|
+
it: "il ciclo è terminato"
|
|
95188
|
+
}
|
|
95189
|
+
}
|
|
95190
|
+
});
|
|
94330
95191
|
object({
|
|
94331
95192
|
phase: _enum(["raised", "resolved"]),
|
|
94332
95193
|
episodeId: string().min(1),
|
|
@@ -94533,6 +95394,10 @@ var STATE_SUMMARY_VARS = [
|
|
|
94533
95394
|
[...RULE_TEXT],
|
|
94534
95395
|
[...RULE_TEXT],
|
|
94535
95396
|
[...RULE_TEXT],
|
|
95397
|
+
[...RULE_TEXT],
|
|
95398
|
+
[...RULE_TEXT],
|
|
95399
|
+
[...RULE_TEXT],
|
|
95400
|
+
[...RULE_TEXT],
|
|
94536
95401
|
[...RULE_BODY_ONLY],
|
|
94537
95402
|
[...RULE_TEXT],
|
|
94538
95403
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
@@ -94550,6 +95415,7 @@ var STATE_SUMMARY_VARS = [
|
|
|
94550
95415
|
[...RULE_TEXT],
|
|
94551
95416
|
[...RULE_TEXT],
|
|
94552
95417
|
[...RULE_TEXT],
|
|
95418
|
+
[...RULE_TEXT],
|
|
94553
95419
|
([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
|
|
94554
95420
|
[...RULE_TEXT],
|
|
94555
95421
|
[...RULE_TEXT],
|
|
@@ -94794,6 +95660,7 @@ new Set([
|
|
|
94794
95660
|
"siren",
|
|
94795
95661
|
"button",
|
|
94796
95662
|
"doorbell",
|
|
95663
|
+
"appliance-cycle",
|
|
94797
95664
|
"audio-speech",
|
|
94798
95665
|
"audio-scream",
|
|
94799
95666
|
"audio-crying",
|