@camstack/addon-matter-broker 0.2.154 → 0.2.156
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 +1999 -681
- package/dist/addon.mjs +1999 -681
- package/package.json +6 -2
package/dist/addon.mjs
CHANGED
|
@@ -797,7 +797,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
797
797
|
return EventCategory;
|
|
798
798
|
}({});
|
|
799
799
|
//#endregion
|
|
800
|
-
//#region ../types/dist/device-type-
|
|
800
|
+
//#region ../types/dist/device-type-D1fH5kaC.mjs
|
|
801
801
|
/**
|
|
802
802
|
import { errMsg } from '@camstack/types'
|
|
803
803
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -1028,6 +1028,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
|
1028
1028
|
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
1029
1029
|
/** Media player exposes stop distinct from pause. */
|
|
1030
1030
|
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
1031
|
+
/** Media player exposes volume up / down in steps (an infrared TV has no level). */
|
|
1032
|
+
DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
|
|
1033
|
+
/** Media player exposes channel up / down in steps. */
|
|
1034
|
+
DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
|
|
1035
|
+
/** Media player exposes power on / off. */
|
|
1036
|
+
DeviceFeature["MediaPlayerPower"] = "media-player-power";
|
|
1031
1037
|
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
1032
1038
|
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
1033
1039
|
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
@@ -5987,7 +5993,7 @@ var ZodIssueCode = {
|
|
|
5987
5993
|
var ZodFirstPartyTypeKind;
|
|
5988
5994
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5989
5995
|
//#endregion
|
|
5990
|
-
//#region ../types/dist/sleep-
|
|
5996
|
+
//#region ../types/dist/sleep-BaWhXZFm.mjs
|
|
5991
5997
|
/**
|
|
5992
5998
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5993
5999
|
* window to float samples (D455).
|
|
@@ -6066,6 +6072,22 @@ function buildAlawTable() {
|
|
|
6066
6072
|
}
|
|
6067
6073
|
buildUlawTable();
|
|
6068
6074
|
buildAlawTable();
|
|
6075
|
+
/**
|
|
6076
|
+
* An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
|
|
6077
|
+
* so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
|
|
6078
|
+
* co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
|
|
6079
|
+
* bundles its own copy of this module, so the class the runner sees is not the one that threw.
|
|
6080
|
+
*/
|
|
6081
|
+
var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
|
|
6082
|
+
var AddonMigrationsBlockedError = class extends Error {
|
|
6083
|
+
/** The ledger ids that did not apply and block the owner. */
|
|
6084
|
+
migrationIds;
|
|
6085
|
+
constructor(migrationIds) {
|
|
6086
|
+
super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
|
|
6087
|
+
this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
|
|
6088
|
+
this.migrationIds = migrationIds;
|
|
6089
|
+
}
|
|
6090
|
+
};
|
|
6069
6091
|
/** Is this field's stored value a credential? */
|
|
6070
6092
|
function isSecretConfigField(field) {
|
|
6071
6093
|
if (field.type === "password") return true;
|
|
@@ -6119,9 +6141,16 @@ function isSecretMarker(value) {
|
|
|
6119
6141
|
const ref = value["$secret"];
|
|
6120
6142
|
return typeof ref === "string" && ref.length > 0;
|
|
6121
6143
|
}
|
|
6144
|
+
/** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
|
|
6145
|
+
function isSecretMarkerLike(value) {
|
|
6146
|
+
return isRecord$7(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
|
|
6147
|
+
}
|
|
6122
6148
|
function isSecretClear(value) {
|
|
6123
6149
|
return isRecord$7(value) && value["$secretClear"] === true;
|
|
6124
6150
|
}
|
|
6151
|
+
function isSecretFieldView(value) {
|
|
6152
|
+
return isRecord$7(value) && value["set"] === true && isSecretMarkerLike(value);
|
|
6153
|
+
}
|
|
6125
6154
|
/**
|
|
6126
6155
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
6127
6156
|
* plaintext becomes "set (inline)", nothing becomes `null`. A value never
|
|
@@ -6503,6 +6532,53 @@ function emitReadiness(bus, params) {
|
|
|
6503
6532
|
ts
|
|
6504
6533
|
}));
|
|
6505
6534
|
}
|
|
6535
|
+
/** Why a catalogue entry is not a runnable body of its declared kind. */
|
|
6536
|
+
var AddonMigrationDefinitionError = class extends Error {
|
|
6537
|
+
id;
|
|
6538
|
+
constructor(id, message) {
|
|
6539
|
+
super(`${id}: ${message}`);
|
|
6540
|
+
this.id = id;
|
|
6541
|
+
this.name = "AddonMigrationDefinitionError";
|
|
6542
|
+
}
|
|
6543
|
+
};
|
|
6544
|
+
/**
|
|
6545
|
+
* Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
|
|
6546
|
+
* an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
|
|
6547
|
+
* body). A throwing body never escapes `execute`: it is reported as a failed result.
|
|
6548
|
+
*/
|
|
6549
|
+
function defineAddonMigrations(catalogue, ports) {
|
|
6550
|
+
return catalogue.map((entry) => {
|
|
6551
|
+
const { meta } = entry;
|
|
6552
|
+
if ("plan" in entry) {
|
|
6553
|
+
if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
|
|
6554
|
+
return {
|
|
6555
|
+
meta,
|
|
6556
|
+
execute: () => guarded(async () => ({
|
|
6557
|
+
kind: "plan",
|
|
6558
|
+
plan: await entry.plan(ports)
|
|
6559
|
+
}))
|
|
6560
|
+
};
|
|
6561
|
+
}
|
|
6562
|
+
if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
|
|
6563
|
+
return {
|
|
6564
|
+
meta,
|
|
6565
|
+
execute: () => guarded(async () => ({
|
|
6566
|
+
kind: "procedure",
|
|
6567
|
+
result: await entry.procedure(ports)
|
|
6568
|
+
}))
|
|
6569
|
+
};
|
|
6570
|
+
});
|
|
6571
|
+
}
|
|
6572
|
+
async function guarded(work) {
|
|
6573
|
+
try {
|
|
6574
|
+
return await work();
|
|
6575
|
+
} catch (err) {
|
|
6576
|
+
return {
|
|
6577
|
+
kind: "failed",
|
|
6578
|
+
error: errMsg(err)
|
|
6579
|
+
};
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6506
6582
|
var NOT_RUN = {
|
|
6507
6583
|
declared: false,
|
|
6508
6584
|
snapshot: null,
|
|
@@ -6724,11 +6800,36 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6724
6800
|
}
|
|
6725
6801
|
return out;
|
|
6726
6802
|
}
|
|
6803
|
+
/**
|
|
6804
|
+
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6805
|
+
*
|
|
6806
|
+
* A ref is DETERMINISTIC from where the value lives — the owner addon, the
|
|
6807
|
+
* scope (global, one node, one device, one integration, one row of a business
|
|
6808
|
+
* collection) and the field path inside that scope — so saving the same field
|
|
6809
|
+
* twice updates the same ref, and the ref itself says which settings blob
|
|
6810
|
+
* points at it. It is also the AAD of the value's ciphertext: a ciphertext
|
|
6811
|
+
* copied onto another ref does not decrypt.
|
|
6812
|
+
*
|
|
6813
|
+
* Array items are addressed by ITEM ID, never by index
|
|
6814
|
+
* (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
|
|
6815
|
+
*
|
|
6816
|
+
* Pure and browser-safe: no node builtins, no I/O.
|
|
6817
|
+
*/
|
|
6818
|
+
/** The URI scheme of every keychain ref. */
|
|
6819
|
+
var SECRET_REF_SCHEME = "secret://";
|
|
6727
6820
|
var GLOBAL_SCOPE = { kind: "global" };
|
|
6728
6821
|
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6729
6822
|
function globalSecretScope() {
|
|
6730
6823
|
return GLOBAL_SCOPE;
|
|
6731
6824
|
}
|
|
6825
|
+
/**
|
|
6826
|
+
* An addon id as a ref owner. A hub in-process builtin's context id is
|
|
6827
|
+
* `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
|
|
6828
|
+
* addon running forked.
|
|
6829
|
+
*/
|
|
6830
|
+
function normalizeSecretOwner(addonId) {
|
|
6831
|
+
return addonId.startsWith("addon:") ? addonId.slice(6) : addonId;
|
|
6832
|
+
}
|
|
6732
6833
|
function encodeSegment(value) {
|
|
6733
6834
|
return encodeURIComponent(value);
|
|
6734
6835
|
}
|
|
@@ -6739,6 +6840,16 @@ function decodeSegment(value) {
|
|
|
6739
6840
|
return null;
|
|
6740
6841
|
}
|
|
6741
6842
|
}
|
|
6843
|
+
/** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
|
|
6844
|
+
function formatSecretScope(scope) {
|
|
6845
|
+
switch (scope.kind) {
|
|
6846
|
+
case "global": return "global";
|
|
6847
|
+
case "node": return `node/${encodeSegment(scope.nodeId)}`;
|
|
6848
|
+
case "device": return `device/${scope.deviceId}`;
|
|
6849
|
+
case "integration": return `integration/${encodeSegment(scope.integrationId)}`;
|
|
6850
|
+
case "row": return `row/${encodeSegment(scope.collection)}/${encodeSegment(scope.rowId)}`;
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6742
6853
|
/**
|
|
6743
6854
|
* The field path of a value inside an array item, addressed by the item's id:
|
|
6744
6855
|
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
@@ -6747,6 +6858,13 @@ function decodeSegment(value) {
|
|
|
6747
6858
|
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6748
6859
|
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6749
6860
|
}
|
|
6861
|
+
/** Build a ref. Throws on an empty owner or field path — both are bugs. */
|
|
6862
|
+
function buildSecretRef(parts) {
|
|
6863
|
+
const owner = normalizeSecretOwner(parts.owner);
|
|
6864
|
+
if (owner.length === 0) throw new Error("buildSecretRef: owner is empty");
|
|
6865
|
+
if (parts.fieldPath.length === 0) throw new Error("buildSecretRef: fieldPath is empty");
|
|
6866
|
+
return `${SECRET_REF_SCHEME}${encodeSegment(owner)}/${formatSecretScope(parts.scope)}/${parts.fieldPath}`;
|
|
6867
|
+
}
|
|
6750
6868
|
function parseScope(segments) {
|
|
6751
6869
|
switch (segments[0]) {
|
|
6752
6870
|
case "global": return {
|
|
@@ -7672,7 +7790,7 @@ var BaseAddon = class {
|
|
|
7672
7790
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7673
7791
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7674
7792
|
}, migrations, this.ctx.logger);
|
|
7675
|
-
if (report.blockOwner.length > 0) throw new
|
|
7793
|
+
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7676
7794
|
return report;
|
|
7677
7795
|
}
|
|
7678
7796
|
/**
|
|
@@ -8012,6 +8130,10 @@ var BaseAddon = class {
|
|
|
8012
8130
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8013
8131
|
const patch = {};
|
|
8014
8132
|
for (const key of keys) {
|
|
8133
|
+
if (tree.get(key)?.kind === "array") {
|
|
8134
|
+
patch[key] = [];
|
|
8135
|
+
continue;
|
|
8136
|
+
}
|
|
8015
8137
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8016
8138
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8017
8139
|
}
|
|
@@ -8700,11 +8822,17 @@ var RECORD_BATCH_MAX = 1e3;
|
|
|
8700
8822
|
var RECORD_QUERY_MAX = 1e3;
|
|
8701
8823
|
/** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
|
|
8702
8824
|
var RECORD_SETTINGS_MAX_ROWS = 5e4;
|
|
8825
|
+
/** The `Column`-free proof prefix: a ref collection for a kind's secret fields. */
|
|
8826
|
+
var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
8703
8827
|
/**
|
|
8704
8828
|
* Secret fields of a record, addressed the way a Zod schema declares them
|
|
8705
8829
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8706
8830
|
* (`auth.accessToken`, `headers[].value`).
|
|
8707
8831
|
*
|
|
8832
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8833
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8834
|
+
* key releases exactly that key's secret.
|
|
8835
|
+
*
|
|
8708
8836
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8709
8837
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8710
8838
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8717,11 +8845,13 @@ function parseSecretPath(path) {
|
|
|
8717
8845
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8718
8846
|
return path.split(".").map((raw) => {
|
|
8719
8847
|
const each = raw.endsWith("[]");
|
|
8720
|
-
const
|
|
8848
|
+
const map = raw.endsWith("{}");
|
|
8849
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8721
8850
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8722
8851
|
return {
|
|
8723
8852
|
key,
|
|
8724
|
-
each
|
|
8853
|
+
each,
|
|
8854
|
+
map
|
|
8725
8855
|
};
|
|
8726
8856
|
});
|
|
8727
8857
|
}
|
|
@@ -8736,6 +8866,128 @@ function secretArrayPrefixes(path) {
|
|
|
8736
8866
|
}
|
|
8737
8867
|
return prefixes;
|
|
8738
8868
|
}
|
|
8869
|
+
function isRecord$4$1(value) {
|
|
8870
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8871
|
+
}
|
|
8872
|
+
function elementId(element, idKey, declared, prefix) {
|
|
8873
|
+
const id = isRecord$4$1(element) ? element[idKey] : void 0;
|
|
8874
|
+
if (typeof id !== "string" || id.length === 0) throw new InvalidRecordError(`"${declared}": every element of "${prefix}" needs a non-empty string "${idKey}" to carry a secret`);
|
|
8875
|
+
return id;
|
|
8876
|
+
}
|
|
8877
|
+
/**
|
|
8878
|
+
* Visit every secret leaf PRESENT in `data` (an absent branch has nothing to
|
|
8879
|
+
* visit). `replace` returns the new leaf value, or {@link REMOVE_LEAF}; the
|
|
8880
|
+
* result is a new body, `data` is untouched.
|
|
8881
|
+
*/
|
|
8882
|
+
var REMOVE_LEAF = Symbol("remove-leaf");
|
|
8883
|
+
function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arrayKeys, visit) {
|
|
8884
|
+
if (!isRecord$4$1(node)) return node;
|
|
8885
|
+
const segment = segments[index];
|
|
8886
|
+
if (segment === void 0) return node;
|
|
8887
|
+
if (!Object.prototype.hasOwnProperty.call(node, segment.key)) return node;
|
|
8888
|
+
const child = node[segment.key];
|
|
8889
|
+
const keys = [...walkedKeys, segment.key];
|
|
8890
|
+
const last = index === segments.length - 1;
|
|
8891
|
+
if (segment.each) {
|
|
8892
|
+
if (!Array.isArray(child)) return node;
|
|
8893
|
+
const prefix = keys.join(".");
|
|
8894
|
+
const idKey = arrayKeys[prefix];
|
|
8895
|
+
if (idKey === void 0) throw new InvalidRecordError(`"${declared}": no id key declared for the array "${prefix}"`);
|
|
8896
|
+
const mapped = child.map((element) => {
|
|
8897
|
+
const id = elementId(element, idKey, declared, prefix);
|
|
8898
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}[${encodeURIComponent(id)}]`;
|
|
8899
|
+
return last ? element : mapAt(element, segments, index + 1, keys, concrete, declared, arrayKeys, visit);
|
|
8900
|
+
});
|
|
8901
|
+
return {
|
|
8902
|
+
...node,
|
|
8903
|
+
[segment.key]: mapped
|
|
8904
|
+
};
|
|
8905
|
+
}
|
|
8906
|
+
if (segment.map) {
|
|
8907
|
+
if (!isRecord$4$1(child)) return node;
|
|
8908
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8909
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8910
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8911
|
+
return [mapKey, visit({
|
|
8912
|
+
declared,
|
|
8913
|
+
concrete,
|
|
8914
|
+
value
|
|
8915
|
+
})];
|
|
8916
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8917
|
+
return {
|
|
8918
|
+
...node,
|
|
8919
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8920
|
+
};
|
|
8921
|
+
}
|
|
8922
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8923
|
+
if (last) {
|
|
8924
|
+
const replacement = visit({
|
|
8925
|
+
declared,
|
|
8926
|
+
concrete,
|
|
8927
|
+
value: child
|
|
8928
|
+
});
|
|
8929
|
+
if (replacement === REMOVE_LEAF) return Object.fromEntries(Object.entries(node).filter(([key]) => key !== segment.key));
|
|
8930
|
+
return {
|
|
8931
|
+
...node,
|
|
8932
|
+
[segment.key]: replacement
|
|
8933
|
+
};
|
|
8934
|
+
}
|
|
8935
|
+
return {
|
|
8936
|
+
...node,
|
|
8937
|
+
[segment.key]: mapAt(child, segments, index + 1, keys, concrete, declared, arrayKeys, visit)
|
|
8938
|
+
};
|
|
8939
|
+
}
|
|
8940
|
+
/** Replace/remove every present secret leaf; returns a NEW body. */
|
|
8941
|
+
function mapSecretLeaves(data, paths, arrayKeys, visit) {
|
|
8942
|
+
let current = data;
|
|
8943
|
+
for (const declared of paths) current = mapAt(current, parseSecretPath(declared), 0, [], "", declared, arrayKeys, visit);
|
|
8944
|
+
return current;
|
|
8945
|
+
}
|
|
8946
|
+
/**
|
|
8947
|
+
* The keychain collection name a kind's secrets live under. Prefixed so a
|
|
8948
|
+
* record's refs never collide with the refs a legacy collection of the same name
|
|
8949
|
+
* already holds during an adoption.
|
|
8950
|
+
*/
|
|
8951
|
+
function recordSecretCollection(kind) {
|
|
8952
|
+
return `${RECORD_SECRET_COLLECTION_PREFIX}${kind}`;
|
|
8953
|
+
}
|
|
8954
|
+
/** The ONE ref a row's secret field may carry: the engine and the facade both build it here. */
|
|
8955
|
+
function recordSecretRef(owner, kind, rowId, concretePath) {
|
|
8956
|
+
return buildSecretRef({
|
|
8957
|
+
owner,
|
|
8958
|
+
scope: {
|
|
8959
|
+
kind: "row",
|
|
8960
|
+
collection: recordSecretCollection(kind),
|
|
8961
|
+
rowId
|
|
8962
|
+
},
|
|
8963
|
+
fieldPath: concretePath
|
|
8964
|
+
});
|
|
8965
|
+
}
|
|
8966
|
+
/**
|
|
8967
|
+
* A stored secret, as an admin read shows it: a VIEW naming the row's own
|
|
8968
|
+
* keychain ref (`set: true`), never the value and no longer the bare redaction
|
|
8969
|
+
* token. The form hands it to the secret field, which reveals the value by ref
|
|
8970
|
+
* through the audited `keychain-admin.reveal`; sent back untouched it keeps the
|
|
8971
|
+
* stored secret (the records door reads a view as KEEP).
|
|
8972
|
+
*
|
|
8973
|
+
* ONE implementation for the entries service and for every addon that serves
|
|
8974
|
+
* its own instances (`RecordIntegrationInstances`).
|
|
8975
|
+
*/
|
|
8976
|
+
function isRecord$3$1(value) {
|
|
8977
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8978
|
+
}
|
|
8979
|
+
function secretViewsOf(owner, kind, id, decl, data) {
|
|
8980
|
+
if (decl.secretPaths.length === 0) return data;
|
|
8981
|
+
const shown = mapSecretLeaves(data, decl.secretPaths, decl.secretArrayKeys, (leaf) => {
|
|
8982
|
+
if (leaf.value !== "__camstack_redacted__") return leaf.value;
|
|
8983
|
+
return {
|
|
8984
|
+
$secret: recordSecretRef(owner, kind, id, leaf.concrete),
|
|
8985
|
+
v: 1,
|
|
8986
|
+
set: true
|
|
8987
|
+
};
|
|
8988
|
+
});
|
|
8989
|
+
return isRecord$3$1(shown) ? shown : data;
|
|
8990
|
+
}
|
|
8739
8991
|
/**
|
|
8740
8992
|
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
8741
8993
|
* and validates them (`ShareViewGrantSchema` builds its
|
|
@@ -8808,7 +9060,8 @@ function isZodSecret(schema) {
|
|
|
8808
9060
|
}
|
|
8809
9061
|
/**
|
|
8810
9062
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
8811
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9063
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9064
|
+
* map" (`applianceSecrets{}`). A declared
|
|
8812
9065
|
* object or array is reported as one path and not descended into — everything
|
|
8813
9066
|
* under it is secret.
|
|
8814
9067
|
*/
|
|
@@ -8819,6 +9072,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
8819
9072
|
const def = defOf(layer);
|
|
8820
9073
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
8821
9074
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9075
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
8822
9076
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
8823
9077
|
else if (def.type === "intersection") {
|
|
8824
9078
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -8958,7 +9212,7 @@ function defineRecordKinds(specs) {
|
|
|
8958
9212
|
retention: spec.retention,
|
|
8959
9213
|
deviceRemoval: spec.deviceRemoval,
|
|
8960
9214
|
maxDataBytes: spec.maxDataBytes,
|
|
8961
|
-
secretPaths: [...secretPaths],
|
|
9215
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
8962
9216
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
8963
9217
|
transactFamily: spec.transactFamily,
|
|
8964
9218
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9003,6 +9257,7 @@ function defineEntryKind(name, spec) {
|
|
|
9003
9257
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9004
9258
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9005
9259
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9260
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9006
9261
|
entry: {
|
|
9007
9262
|
title: spec.title,
|
|
9008
9263
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -9667,6 +9922,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9667
9922
|
"backup",
|
|
9668
9923
|
"battery",
|
|
9669
9924
|
"binary",
|
|
9925
|
+
"board-key-source",
|
|
9670
9926
|
"brightness",
|
|
9671
9927
|
"broker",
|
|
9672
9928
|
"button",
|
|
@@ -9788,6 +10044,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9788
10044
|
"privacy-mask",
|
|
9789
10045
|
"ptz",
|
|
9790
10046
|
"ptz-autotrack",
|
|
10047
|
+
"pump-cycle",
|
|
9791
10048
|
"reboot",
|
|
9792
10049
|
"record-entries",
|
|
9793
10050
|
"recording",
|
|
@@ -11080,18 +11337,6 @@ Object.freeze({
|
|
|
11080
11337
|
"path": "pumpRunning",
|
|
11081
11338
|
"valueType": "boolean"
|
|
11082
11339
|
},
|
|
11083
|
-
{
|
|
11084
|
-
"path": "pumpIntervalEnabled",
|
|
11085
|
-
"valueType": "boolean"
|
|
11086
|
-
},
|
|
11087
|
-
{
|
|
11088
|
-
"path": "pumpOnTime",
|
|
11089
|
-
"valueType": "number"
|
|
11090
|
-
},
|
|
11091
|
-
{
|
|
11092
|
-
"path": "pumpOffTime",
|
|
11093
|
-
"valueType": "number"
|
|
11094
|
-
},
|
|
11095
11340
|
{
|
|
11096
11341
|
"path": "lastDrink",
|
|
11097
11342
|
"valueType": "number"
|
|
@@ -11215,6 +11460,20 @@ Object.freeze({
|
|
|
11215
11460
|
"valueType": "number"
|
|
11216
11461
|
}
|
|
11217
11462
|
],
|
|
11463
|
+
"pump-cycle": [
|
|
11464
|
+
{
|
|
11465
|
+
"path": "enabled",
|
|
11466
|
+
"valueType": "boolean"
|
|
11467
|
+
},
|
|
11468
|
+
{
|
|
11469
|
+
"path": "onTime",
|
|
11470
|
+
"valueType": "number"
|
|
11471
|
+
},
|
|
11472
|
+
{
|
|
11473
|
+
"path": "offTime",
|
|
11474
|
+
"valueType": "number"
|
|
11475
|
+
}
|
|
11476
|
+
],
|
|
11218
11477
|
"recording-archive": [
|
|
11219
11478
|
{
|
|
11220
11479
|
"path": "deviceId",
|
|
@@ -11664,7 +11923,7 @@ object({
|
|
|
11664
11923
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11665
11924
|
});
|
|
11666
11925
|
//#endregion
|
|
11667
|
-
//#region ../types/dist/composition-
|
|
11926
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11668
11927
|
/**
|
|
11669
11928
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11670
11929
|
*
|
|
@@ -12427,38 +12686,6 @@ function validateExpressionSource(src) {
|
|
|
12427
12686
|
return null;
|
|
12428
12687
|
}
|
|
12429
12688
|
/**
|
|
12430
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12431
|
-
*
|
|
12432
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12433
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12434
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12435
|
-
*
|
|
12436
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12437
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12438
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12439
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12440
|
-
* can move later without moving the others.
|
|
12441
|
-
*/
|
|
12442
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12443
|
-
var APPLIANCE_KINDS = [
|
|
12444
|
-
"washer",
|
|
12445
|
-
"dryer",
|
|
12446
|
-
"dishwasher"
|
|
12447
|
-
];
|
|
12448
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12449
|
-
/**
|
|
12450
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12451
|
-
*
|
|
12452
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12453
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12454
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12455
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12456
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12457
|
-
* the smallest radiator.
|
|
12458
|
-
*/
|
|
12459
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12460
|
-
DeviceType.Heater;
|
|
12461
|
-
/**
|
|
12462
12689
|
* Statistics and integrals over a history read (D944).
|
|
12463
12690
|
*
|
|
12464
12691
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12602,34 +12829,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12602
12829
|
/** Actuator groups only. Readings leave this unset. */
|
|
12603
12830
|
active: ActiveCriterionSchema.optional()
|
|
12604
12831
|
}),
|
|
12605
|
-
object({
|
|
12606
|
-
kind: literal("appliance"),
|
|
12607
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12608
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12609
|
-
thresholdWatts: number().positive(),
|
|
12610
|
-
startDwellMs: number().int().nonnegative(),
|
|
12611
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12612
|
-
member: HelperMemberSchema,
|
|
12613
|
-
/**
|
|
12614
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12615
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12616
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12617
|
-
*/
|
|
12618
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12619
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12620
|
-
offSeconds: number().optional()
|
|
12621
|
-
}),
|
|
12622
|
-
object({
|
|
12623
|
-
kind: literal("heater"),
|
|
12624
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12625
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12626
|
-
plug: HelperMemberSchema,
|
|
12627
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12628
|
-
meter: HelperMemberSchema.optional(),
|
|
12629
|
-
thresholdWatts: number().positive(),
|
|
12630
|
-
startDwellMs: number().int().nonnegative(),
|
|
12631
|
-
stopDwellMs: number().int().nonnegative()
|
|
12632
|
-
}),
|
|
12633
12832
|
object({
|
|
12634
12833
|
kind: literal("statistic"),
|
|
12635
12834
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -12801,7 +13000,14 @@ var CompositionForwardCommandSchema = object({
|
|
|
12801
13000
|
method: string$2().min(1),
|
|
12802
13001
|
args: record(string$2().min(1), CompositionForwardArgSchema).optional()
|
|
12803
13002
|
});
|
|
12804
|
-
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
13003
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
13004
|
+
CompositionForwardCommandSchema,
|
|
13005
|
+
CompositionCodeSourceSchema,
|
|
13006
|
+
object({
|
|
13007
|
+
kind: literal("extension"),
|
|
13008
|
+
target: literal("remote")
|
|
13009
|
+
})
|
|
13010
|
+
]);
|
|
12805
13011
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12806
13012
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12807
13013
|
/**
|
|
@@ -12846,7 +13052,7 @@ var CompositionSchema = object({
|
|
|
12846
13052
|
kind: literal("existing"),
|
|
12847
13053
|
device: CompositionSourceRefSchema
|
|
12848
13054
|
})]),
|
|
12849
|
-
features: array(CompositionFeatureSchema).min(
|
|
13055
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
12850
13056
|
/**
|
|
12851
13057
|
* Present when the block is a helper template. Absent on a free
|
|
12852
13058
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -12878,6 +13084,73 @@ var NodeColorIdSchema = _enum([
|
|
|
12878
13084
|
"node-g",
|
|
12879
13085
|
"node-h"
|
|
12880
13086
|
]);
|
|
13087
|
+
//#endregion
|
|
13088
|
+
//#region ../types/dist/index.mjs
|
|
13089
|
+
var SMTP_INBOUND_DEFAULT_PORT = 2525;
|
|
13090
|
+
var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
|
|
13091
|
+
var SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES = 25 * 1024 * 1024;
|
|
13092
|
+
/** The text excerpt a message carries on the bus. */
|
|
13093
|
+
var SMTP_INBOUND_TEXT_MAX_CHARS = 4096;
|
|
13094
|
+
var PortSchema = number().int().min(1).max(65535);
|
|
13095
|
+
/** A recipient local-part: what stands before the `@`. */
|
|
13096
|
+
var SmtpRecipientSchema = string$2().min(1).max(64).regex(/^[A-Za-z0-9._+-]+$/);
|
|
13097
|
+
object({
|
|
13098
|
+
port: PortSchema.default(SMTP_INBOUND_DEFAULT_PORT),
|
|
13099
|
+
bindHost: string$2().min(1).default("0.0.0.0"),
|
|
13100
|
+
domain: string$2().min(1).default(SMTP_INBOUND_DEFAULT_DOMAIN),
|
|
13101
|
+
requireAuth: boolean().default(true),
|
|
13102
|
+
username: string$2().default(""),
|
|
13103
|
+
password: string$2().default("").meta({ secret: true }),
|
|
13104
|
+
starttls: boolean().default(false),
|
|
13105
|
+
maxMessageBytes: number().int().positive().default(SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES)
|
|
13106
|
+
});
|
|
13107
|
+
object({
|
|
13108
|
+
host: string$2().min(1).nullable(),
|
|
13109
|
+
hostReason: string$2().nullable(),
|
|
13110
|
+
port: PortSchema,
|
|
13111
|
+
domain: string$2().min(1),
|
|
13112
|
+
requireAuth: boolean(),
|
|
13113
|
+
username: string$2(),
|
|
13114
|
+
password: string$2(),
|
|
13115
|
+
starttls: boolean()
|
|
13116
|
+
});
|
|
13117
|
+
object({
|
|
13118
|
+
recipients: array(SmtpRecipientSchema).min(1),
|
|
13119
|
+
/**
|
|
13120
|
+
* Who is subscribing. A second `subscribe` with the same id REPLACES that subscriber's
|
|
13121
|
+
* recipients (the way a consumer that restarted, or lost a device, declares its set again);
|
|
13122
|
+
* a recipient held by another subscriber is refused with `smtp-inbound/recipient-claimed`.
|
|
13123
|
+
* Absent, the subscriber is anonymous and unique.
|
|
13124
|
+
*/
|
|
13125
|
+
subscriberId: string$2().min(1).max(64).optional()
|
|
13126
|
+
});
|
|
13127
|
+
var SmtpInboundAttachmentSchema = object({
|
|
13128
|
+
filename: string$2(),
|
|
13129
|
+
contentType: string$2(),
|
|
13130
|
+
bytes: number().int().nonnegative()
|
|
13131
|
+
});
|
|
13132
|
+
/** The payload of one `broker.message` (the key is the recipient local-part). */
|
|
13133
|
+
var SmtpInboundMessageSchema = object({
|
|
13134
|
+
/** Monotonic per mailbox; a gap tells a consumer it missed an event. */
|
|
13135
|
+
seq: number().int().positive(),
|
|
13136
|
+
messageId: string$2(),
|
|
13137
|
+
receivedAt: number().int().nonnegative(),
|
|
13138
|
+
from: string$2(),
|
|
13139
|
+
to: string$2(),
|
|
13140
|
+
subject: string$2(),
|
|
13141
|
+
text: string$2().max(SMTP_INBOUND_TEXT_MAX_CHARS),
|
|
13142
|
+
attachments: array(SmtpInboundAttachmentSchema)
|
|
13143
|
+
});
|
|
13144
|
+
object({
|
|
13145
|
+
/** The newest seq issued so far (0 before any mail). */
|
|
13146
|
+
lastSeq: number().int().nonnegative(),
|
|
13147
|
+
/** The oldest seq still retained; messages below it are gone. */
|
|
13148
|
+
oldestSeq: number().int().nonnegative(),
|
|
13149
|
+
messages: array(object({
|
|
13150
|
+
key: string$2(),
|
|
13151
|
+
message: SmtpInboundMessageSchema
|
|
13152
|
+
}))
|
|
13153
|
+
});
|
|
12881
13154
|
new Set([
|
|
12882
13155
|
"track",
|
|
12883
13156
|
"face",
|
|
@@ -12997,6 +13270,205 @@ var IntegrationInstanceRefSchema = object({
|
|
|
12997
13270
|
]),
|
|
12998
13271
|
candidateStableId: string$2().optional()
|
|
12999
13272
|
});
|
|
13273
|
+
/**
|
|
13274
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13275
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13276
|
+
*
|
|
13277
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13278
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13279
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13280
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13281
|
+
*
|
|
13282
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13283
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13284
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13285
|
+
*/
|
|
13286
|
+
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13287
|
+
var INSTANCES_LIST_MAX = 1e3;
|
|
13288
|
+
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13289
|
+
function topLevelSecretKeys(paths) {
|
|
13290
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13291
|
+
}
|
|
13292
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13293
|
+
function secretMapKeys(paths) {
|
|
13294
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13295
|
+
}
|
|
13296
|
+
/**
|
|
13297
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13298
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13299
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13300
|
+
*/
|
|
13301
|
+
function secretMapPatch(value) {
|
|
13302
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13303
|
+
const out = {};
|
|
13304
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13305
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13306
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13307
|
+
}
|
|
13308
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13309
|
+
function declaresEnabled(kind) {
|
|
13310
|
+
const json = toJSONSchema(kind.schema, {
|
|
13311
|
+
unrepresentable: "any",
|
|
13312
|
+
io: "input"
|
|
13313
|
+
});
|
|
13314
|
+
const properties = Reflect.get(json, "properties");
|
|
13315
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13316
|
+
}
|
|
13317
|
+
var RecordIntegrationInstances = class {
|
|
13318
|
+
records;
|
|
13319
|
+
kind;
|
|
13320
|
+
hooks;
|
|
13321
|
+
handle;
|
|
13322
|
+
secretKeys;
|
|
13323
|
+
secretMaps;
|
|
13324
|
+
nameField;
|
|
13325
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13326
|
+
registryOwnsEnabled;
|
|
13327
|
+
constructor(records, kind, hooks) {
|
|
13328
|
+
this.records = records;
|
|
13329
|
+
this.kind = kind;
|
|
13330
|
+
this.hooks = hooks;
|
|
13331
|
+
this.handle = records.kind(kind);
|
|
13332
|
+
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13333
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13334
|
+
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13335
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13336
|
+
if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
|
|
13337
|
+
}
|
|
13338
|
+
/**
|
|
13339
|
+
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
13340
|
+
* locked the row is served WITHOUT its secrets and says so (`secrets: 'unavailable'`), so the
|
|
13341
|
+
* caller can keep what it had instead of reconfiguring with an empty secret (D49).
|
|
13342
|
+
*/
|
|
13343
|
+
async readResolved(id) {
|
|
13344
|
+
try {
|
|
13345
|
+
const record = await this.handle.getResolved(id);
|
|
13346
|
+
return record === null ? null : {
|
|
13347
|
+
id,
|
|
13348
|
+
body: record.data,
|
|
13349
|
+
enabled: record.data.enabled !== false,
|
|
13350
|
+
secrets: "resolved"
|
|
13351
|
+
};
|
|
13352
|
+
} catch (err) {
|
|
13353
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
13354
|
+
const stored = await this.handle.get(id);
|
|
13355
|
+
if (stored === null) return null;
|
|
13356
|
+
return {
|
|
13357
|
+
id,
|
|
13358
|
+
body: Object.fromEntries(Object.entries(stored.data).filter(([key]) => !this.secretKeys.has(key))),
|
|
13359
|
+
enabled: stored.data.enabled !== false,
|
|
13360
|
+
secrets: "unavailable"
|
|
13361
|
+
};
|
|
13362
|
+
}
|
|
13363
|
+
}
|
|
13364
|
+
/** The body of {@link readResolved} alone; a locked keychain yields the row without secrets. */
|
|
13365
|
+
async getResolved(id) {
|
|
13366
|
+
return (await this.readResolved(id))?.body ?? null;
|
|
13367
|
+
}
|
|
13368
|
+
/** Every instance, secrets resolved where the keychain allows; an absent one is skipped. */
|
|
13369
|
+
async listResolved() {
|
|
13370
|
+
const page = await this.handle.query({ limit: INSTANCES_LIST_MAX });
|
|
13371
|
+
const out = [];
|
|
13372
|
+
for (const record of page.records) {
|
|
13373
|
+
const read = await this.readResolved(record.id);
|
|
13374
|
+
if (read !== null) out.push(read);
|
|
13375
|
+
}
|
|
13376
|
+
return out;
|
|
13377
|
+
}
|
|
13378
|
+
viewOf(record) {
|
|
13379
|
+
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13380
|
+
const rawName = record.data[this.nameField];
|
|
13381
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13382
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13383
|
+
return {
|
|
13384
|
+
id: record.id,
|
|
13385
|
+
name: typeof rawName === "string" ? rawName : record.id,
|
|
13386
|
+
configured: this.hooks.configured(record.data),
|
|
13387
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13388
|
+
formValues: shown,
|
|
13389
|
+
...kind !== void 0 ? { kind } : {},
|
|
13390
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13391
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13392
|
+
};
|
|
13393
|
+
}
|
|
13394
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13395
|
+
...this.hooks.description,
|
|
13396
|
+
mintsId: true
|
|
13397
|
+
};
|
|
13398
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13399
|
+
listInstances = async () => {
|
|
13400
|
+
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13401
|
+
};
|
|
13402
|
+
getInstance = async ({ id }) => {
|
|
13403
|
+
const record = await this.handle.get(id);
|
|
13404
|
+
return record === null ? null : this.viewOf(record);
|
|
13405
|
+
};
|
|
13406
|
+
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13407
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13408
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13409
|
+
name,
|
|
13410
|
+
...kind !== void 0 ? { kind } : {}
|
|
13411
|
+
});
|
|
13412
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13413
|
+
await this.handle.put({
|
|
13414
|
+
id: minted,
|
|
13415
|
+
data: this.hooks.initialBody(name, kind)
|
|
13416
|
+
}, { ifRev: 0 });
|
|
13417
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13418
|
+
return { id: minted };
|
|
13419
|
+
};
|
|
13420
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13421
|
+
const patch = {};
|
|
13422
|
+
for (const [key, value] of Object.entries(values)) {
|
|
13423
|
+
if (value === void 0) continue;
|
|
13424
|
+
if (this.secretMaps.has(key)) {
|
|
13425
|
+
const entries = secretMapPatch(value);
|
|
13426
|
+
if (entries !== null) patch[key] = entries;
|
|
13427
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13428
|
+
else if (isSecretClear(value)) patch[key] = null;
|
|
13429
|
+
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13430
|
+
}
|
|
13431
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13432
|
+
await this.handle.patch(id, patch);
|
|
13433
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13434
|
+
return { ok: true };
|
|
13435
|
+
};
|
|
13436
|
+
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13437
|
+
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13438
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13439
|
+
await this.handle.patch(id, { enabled });
|
|
13440
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13441
|
+
return { ok: true };
|
|
13442
|
+
};
|
|
13443
|
+
/** The row and its keychain refs go together (the records door). */
|
|
13444
|
+
deleteInstance = async ({ id }) => {
|
|
13445
|
+
await this.handle.delete(id);
|
|
13446
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13447
|
+
return { ok: true };
|
|
13448
|
+
};
|
|
13449
|
+
testInstance = async (input) => {
|
|
13450
|
+
if (this.hooks.test === void 0) return {
|
|
13451
|
+
success: false,
|
|
13452
|
+
status: "unsupported",
|
|
13453
|
+
error: "This integration declares no connection test"
|
|
13454
|
+
};
|
|
13455
|
+
return this.hooks.test(input);
|
|
13456
|
+
};
|
|
13457
|
+
/** The flat provider object to hand to `registerProvider`. */
|
|
13458
|
+
toProvider() {
|
|
13459
|
+
return {
|
|
13460
|
+
describe: this.describe,
|
|
13461
|
+
instanceFormSchema: this.instanceFormSchema,
|
|
13462
|
+
listInstances: this.listInstances,
|
|
13463
|
+
getInstance: this.getInstance,
|
|
13464
|
+
createInstance: this.createInstance,
|
|
13465
|
+
updateInstance: this.updateInstance,
|
|
13466
|
+
setInstanceEnabled: this.setInstanceEnabled,
|
|
13467
|
+
deleteInstance: this.deleteInstance,
|
|
13468
|
+
testInstance: this.testInstance
|
|
13469
|
+
};
|
|
13470
|
+
}
|
|
13471
|
+
};
|
|
13000
13472
|
var EncodeProfileSchema = object({
|
|
13001
13473
|
video: object({
|
|
13002
13474
|
codec: _enum([
|
|
@@ -14065,6 +14537,58 @@ var MigrationOwnerStepSchema = object({
|
|
|
14065
14537
|
/** The ledger state the engine found: `pending` has no row yet. */
|
|
14066
14538
|
state: union([literal("pending"), MigrationStatusSchema])
|
|
14067
14539
|
});
|
|
14540
|
+
function isPlain(value) {
|
|
14541
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14542
|
+
}
|
|
14543
|
+
async function flattenNestedRecords(ports, spec) {
|
|
14544
|
+
const ids = await ports.listNested(spec.legacyKey);
|
|
14545
|
+
if (ids.length === 0) return {
|
|
14546
|
+
outcome: "already-done",
|
|
14547
|
+
summary: { converted: 0 }
|
|
14548
|
+
};
|
|
14549
|
+
const rows = /* @__PURE__ */ new Map();
|
|
14550
|
+
for (const id of ids) try {
|
|
14551
|
+
rows.set(id, await ports.readResolved(id));
|
|
14552
|
+
} catch (err) {
|
|
14553
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
14554
|
+
throw new Error(`record '${id}': cannot be read with its secrets (${why}) - nothing moved`);
|
|
14555
|
+
}
|
|
14556
|
+
for (const [id, row] of rows) {
|
|
14557
|
+
const legacy = row[spec.legacyKey];
|
|
14558
|
+
if (!isPlain(legacy)) throw new Error(`record '${id}': '${spec.legacyKey}' is not an object - nothing moved`);
|
|
14559
|
+
const flat = spec.toFlat(legacy, row);
|
|
14560
|
+
await ports.patch(id, flat);
|
|
14561
|
+
const back = await ports.readResolved(id);
|
|
14562
|
+
const wrong = spec.flatSecretKeys.filter((key) => {
|
|
14563
|
+
const sent = flat[key];
|
|
14564
|
+
const got = back[key];
|
|
14565
|
+
return (typeof sent === "string" ? sent : "") !== (typeof got === "string" ? got : "");
|
|
14566
|
+
});
|
|
14567
|
+
if (wrong.length > 0) {
|
|
14568
|
+
await ports.patch(id, Object.fromEntries(Object.keys(flat).map((key) => [key, null])));
|
|
14569
|
+
throw new Error(`record '${id}': ${wrong.join(", ")} did not read back from its new home - the row is unchanged`);
|
|
14570
|
+
}
|
|
14571
|
+
await ports.patch(id, { [spec.legacyKey]: null });
|
|
14572
|
+
}
|
|
14573
|
+
return {
|
|
14574
|
+
outcome: "done",
|
|
14575
|
+
summary: { converted: rows.size }
|
|
14576
|
+
};
|
|
14577
|
+
}
|
|
14578
|
+
/** The ports over one kind's records handle. */
|
|
14579
|
+
function flattenPortsOver(handle) {
|
|
14580
|
+
return {
|
|
14581
|
+
listNested: async (legacyKey) => (await handle.query({ limit: 1e3 })).records.filter((r) => legacyKey in r.data).map((r) => r.id),
|
|
14582
|
+
readResolved: async (id) => {
|
|
14583
|
+
const row = await handle.getResolved(id);
|
|
14584
|
+
if (row === null) throw new Error("the record is gone");
|
|
14585
|
+
return row.data;
|
|
14586
|
+
},
|
|
14587
|
+
patch: async (id, patch) => {
|
|
14588
|
+
await handle.patch(id, patch);
|
|
14589
|
+
}
|
|
14590
|
+
};
|
|
14591
|
+
}
|
|
14068
14592
|
/**
|
|
14069
14593
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
14070
14594
|
* gets back (cloud training, D805+).
|
|
@@ -19150,24 +19674,52 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19150
19674
|
args: { on: false }
|
|
19151
19675
|
},
|
|
19152
19676
|
{
|
|
19153
|
-
id: "petFountain.
|
|
19154
|
-
method: "
|
|
19677
|
+
id: "petFountain.resetDailyConsumption",
|
|
19678
|
+
method: "resetDailyConsumption",
|
|
19679
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
19680
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
19681
|
+
icon: "rotate-ccw"
|
|
19682
|
+
}
|
|
19683
|
+
],
|
|
19684
|
+
state: [{
|
|
19685
|
+
kind: "boolean",
|
|
19686
|
+
field: "empty",
|
|
19687
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
19688
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
19689
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
19690
|
+
icon: "droplet"
|
|
19691
|
+
}, {
|
|
19692
|
+
kind: "numeric",
|
|
19693
|
+
field: "waterLevel",
|
|
19694
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
19695
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
19696
|
+
unit: "%",
|
|
19697
|
+
icon: "droplets"
|
|
19698
|
+
}]
|
|
19699
|
+
},
|
|
19700
|
+
"pump-cycle": {
|
|
19701
|
+
scope: "device",
|
|
19702
|
+
noun: t("Pump cycle", "Ciclo della pompa"),
|
|
19703
|
+
actions: [
|
|
19704
|
+
{
|
|
19705
|
+
id: "pumpCycle.on",
|
|
19706
|
+
method: "setEnabled",
|
|
19155
19707
|
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
19156
19708
|
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
19157
19709
|
icon: "timer",
|
|
19158
19710
|
args: { on: true }
|
|
19159
19711
|
},
|
|
19160
19712
|
{
|
|
19161
|
-
id: "
|
|
19162
|
-
method: "
|
|
19713
|
+
id: "pumpCycle.off",
|
|
19714
|
+
method: "setEnabled",
|
|
19163
19715
|
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
19164
19716
|
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
19165
19717
|
icon: "timer-off",
|
|
19166
19718
|
args: { on: false }
|
|
19167
19719
|
},
|
|
19168
19720
|
{
|
|
19169
|
-
id: "
|
|
19170
|
-
method: "
|
|
19721
|
+
id: "pumpCycle.onMinutes",
|
|
19722
|
+
method: "setOnMinutes",
|
|
19171
19723
|
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
19172
19724
|
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
19173
19725
|
icon: "timer",
|
|
@@ -19183,8 +19735,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19183
19735
|
}]
|
|
19184
19736
|
},
|
|
19185
19737
|
{
|
|
19186
|
-
id: "
|
|
19187
|
-
method: "
|
|
19738
|
+
id: "pumpCycle.offMinutes",
|
|
19739
|
+
method: "setOffMinutes",
|
|
19188
19740
|
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
19189
19741
|
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
19190
19742
|
icon: "timer-off",
|
|
@@ -19198,35 +19750,63 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19198
19750
|
unit: "min"
|
|
19199
19751
|
}
|
|
19200
19752
|
}]
|
|
19201
|
-
},
|
|
19202
|
-
{
|
|
19203
|
-
id: "petFountain.resetDailyConsumption",
|
|
19204
|
-
method: "resetDailyConsumption",
|
|
19205
|
-
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
19206
|
-
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
19207
|
-
icon: "rotate-ccw"
|
|
19208
19753
|
}
|
|
19209
19754
|
],
|
|
19210
19755
|
state: [{
|
|
19211
19756
|
kind: "boolean",
|
|
19212
|
-
field: "
|
|
19213
|
-
label: t("
|
|
19214
|
-
whenTrue: t("When
|
|
19215
|
-
whenFalse: t("When
|
|
19216
|
-
icon: "
|
|
19217
|
-
}, {
|
|
19218
|
-
kind: "numeric",
|
|
19219
|
-
field: "waterLevel",
|
|
19220
|
-
quantity: t("the water level", "il livello dell’acqua"),
|
|
19221
|
-
label: t("Water level", "Livello dell’acqua"),
|
|
19222
|
-
unit: "%",
|
|
19223
|
-
icon: "droplets"
|
|
19757
|
+
field: "enabled",
|
|
19758
|
+
label: t("Pump cycle", "Ciclo pompa"),
|
|
19759
|
+
whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
|
|
19760
|
+
whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
|
|
19761
|
+
icon: "timer"
|
|
19224
19762
|
}]
|
|
19225
19763
|
},
|
|
19226
19764
|
"media-player": {
|
|
19227
19765
|
scope: "device",
|
|
19228
19766
|
noun: t("Media player", "Lettore multimediale"),
|
|
19229
19767
|
actions: [
|
|
19768
|
+
{
|
|
19769
|
+
id: "media.turnOn",
|
|
19770
|
+
method: "turnOn",
|
|
19771
|
+
label: t("Turn on", "Accendi"),
|
|
19772
|
+
phrase: t("Turn on {device}", "Accendi {device}"),
|
|
19773
|
+
icon: "power"
|
|
19774
|
+
},
|
|
19775
|
+
{
|
|
19776
|
+
id: "media.turnOff",
|
|
19777
|
+
method: "turnOff",
|
|
19778
|
+
label: t("Turn off", "Spegni"),
|
|
19779
|
+
phrase: t("Turn off {device}", "Spegni {device}"),
|
|
19780
|
+
icon: "power"
|
|
19781
|
+
},
|
|
19782
|
+
{
|
|
19783
|
+
id: "media.volumeUp",
|
|
19784
|
+
method: "volumeUp",
|
|
19785
|
+
label: t("Volume up", "Alza il volume"),
|
|
19786
|
+
phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
|
|
19787
|
+
icon: "volume-2"
|
|
19788
|
+
},
|
|
19789
|
+
{
|
|
19790
|
+
id: "media.volumeDown",
|
|
19791
|
+
method: "volumeDown",
|
|
19792
|
+
label: t("Volume down", "Abbassa il volume"),
|
|
19793
|
+
phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
|
|
19794
|
+
icon: "volume-2"
|
|
19795
|
+
},
|
|
19796
|
+
{
|
|
19797
|
+
id: "media.channelUp",
|
|
19798
|
+
method: "channelUp",
|
|
19799
|
+
label: t("Channel up", "Canale successivo"),
|
|
19800
|
+
phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
|
|
19801
|
+
icon: "skip-forward"
|
|
19802
|
+
},
|
|
19803
|
+
{
|
|
19804
|
+
id: "media.channelDown",
|
|
19805
|
+
method: "channelDown",
|
|
19806
|
+
label: t("Channel down", "Canale precedente"),
|
|
19807
|
+
phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
|
|
19808
|
+
icon: "skip-back"
|
|
19809
|
+
},
|
|
19230
19810
|
{
|
|
19231
19811
|
id: "media.play",
|
|
19232
19812
|
method: "play",
|
|
@@ -21113,6 +21693,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
21113
21693
|
"audio-analyzer": system(PIPELINE),
|
|
21114
21694
|
"audio-codec": system(INTERNAL$1),
|
|
21115
21695
|
"auth-provider": system(IDENTITY),
|
|
21696
|
+
"board-key-source": system(INTERNAL$1),
|
|
21116
21697
|
backup: system("system backups — run and restored from the Backups page"),
|
|
21117
21698
|
broker: system("message-broker connections — configured, not actuated"),
|
|
21118
21699
|
"calendar-source": system(INTERNAL$1),
|
|
@@ -46022,6 +46603,49 @@ var binaryCapability = {
|
|
|
46022
46603
|
volatileStateFields: ["lastChangedAt"]
|
|
46023
46604
|
};
|
|
46024
46605
|
/**
|
|
46606
|
+
* Why a board key could not be read. Typed so a caller reports the cause and
|
|
46607
|
+
* never treats "could not read" as "has no key" (D49).
|
|
46608
|
+
*/
|
|
46609
|
+
var BoardKeyFailureReasonSchema = _enum([
|
|
46610
|
+
"unknown-source",
|
|
46611
|
+
"not-admin",
|
|
46612
|
+
"no-builder",
|
|
46613
|
+
"no-ingress-url",
|
|
46614
|
+
"unreachable",
|
|
46615
|
+
"unknown-board",
|
|
46616
|
+
"no-encryption-key",
|
|
46617
|
+
"unreadable-config"
|
|
46618
|
+
]);
|
|
46619
|
+
var BoardKeySourceSchema = object({
|
|
46620
|
+
sourceId: string$2(),
|
|
46621
|
+
name: string$2()
|
|
46622
|
+
});
|
|
46623
|
+
var BoardKeyBoardSchema = object({
|
|
46624
|
+
/** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
|
|
46625
|
+
configuration: string$2(),
|
|
46626
|
+
/** The node name the builder gives the board. */
|
|
46627
|
+
name: string$2(),
|
|
46628
|
+
/** The address the builder knows the board by, when it has one. */
|
|
46629
|
+
address: string$2().nullable()
|
|
46630
|
+
});
|
|
46631
|
+
var BoardKeyFailureSchema = object({
|
|
46632
|
+
ok: literal(false),
|
|
46633
|
+
reason: BoardKeyFailureReasonSchema,
|
|
46634
|
+
detail: string$2().optional()
|
|
46635
|
+
});
|
|
46636
|
+
var ListBoardsResultSchema = discriminatedUnion("ok", [object({
|
|
46637
|
+
ok: literal(true),
|
|
46638
|
+
boards: array(BoardKeyBoardSchema).readonly()
|
|
46639
|
+
}), BoardKeyFailureSchema]);
|
|
46640
|
+
var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
|
|
46641
|
+
ok: literal(true),
|
|
46642
|
+
key: string$2()
|
|
46643
|
+
}), BoardKeyFailureSchema]);
|
|
46644
|
+
method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string$2().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
|
|
46645
|
+
sourceId: string$2().min(1),
|
|
46646
|
+
configuration: string$2().min(1)
|
|
46647
|
+
}), GetBoardKeyResultSchema, { auth: "admin" });
|
|
46648
|
+
/**
|
|
46025
46649
|
* Dimmable-light brightness control. Co-exists with `switch` on the
|
|
46026
46650
|
* same device — the switch toggles on/off, this cap sets the level
|
|
46027
46651
|
* applied when the light is on. Drivers map their per-vendor dim
|
|
@@ -48769,6 +49393,244 @@ var controlCapability = {
|
|
|
48769
49393
|
*/
|
|
48770
49394
|
history: "none"
|
|
48771
49395
|
};
|
|
49396
|
+
/**
|
|
49397
|
+
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
49398
|
+
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
49399
|
+
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
49400
|
+
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
49401
|
+
*
|
|
49402
|
+
* The cap declares a wide method surface; not every backend supports
|
|
49403
|
+
* every method. Drivers signal sub-capability via the
|
|
49404
|
+
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
49405
|
+
* corresponding method) so the UI gates buttons without a slice
|
|
49406
|
+
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
49407
|
+
*/
|
|
49408
|
+
var MediaPlayerStateSchema = _enum([
|
|
49409
|
+
"off",
|
|
49410
|
+
"on",
|
|
49411
|
+
"idle",
|
|
49412
|
+
"playing",
|
|
49413
|
+
"paused",
|
|
49414
|
+
"buffering",
|
|
49415
|
+
"standby"
|
|
49416
|
+
]);
|
|
49417
|
+
var MediaPlayerRepeatSchema = _enum([
|
|
49418
|
+
"off",
|
|
49419
|
+
"all",
|
|
49420
|
+
"one"
|
|
49421
|
+
]);
|
|
49422
|
+
var MediaInfoSchema = object({
|
|
49423
|
+
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
49424
|
+
* `podcast`, …). */
|
|
49425
|
+
type: string$2(),
|
|
49426
|
+
/** Human-readable title. */
|
|
49427
|
+
title: string$2(),
|
|
49428
|
+
/** Optional artist / channel / station label. */
|
|
49429
|
+
artist: string$2().optional(),
|
|
49430
|
+
/** Optional album / season / show name. */
|
|
49431
|
+
album: string$2().optional(),
|
|
49432
|
+
/** Optional cover-art / thumbnail URL. */
|
|
49433
|
+
imageUrl: string$2().optional()
|
|
49434
|
+
});
|
|
49435
|
+
/**
|
|
49436
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
49437
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
49438
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
49439
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
49440
|
+
* guess, never as a reading.
|
|
49441
|
+
*/
|
|
49442
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
49443
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
49444
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
49445
|
+
"state",
|
|
49446
|
+
"volumeLevel",
|
|
49447
|
+
"isMuted",
|
|
49448
|
+
"source"
|
|
49449
|
+
]);
|
|
49450
|
+
var MediaPlayerStatusSchema = object({
|
|
49451
|
+
/** Playback lifecycle state. */
|
|
49452
|
+
state: MediaPlayerStateSchema,
|
|
49453
|
+
/**
|
|
49454
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
49455
|
+
* A slice written before this field existed parses as `reported`.
|
|
49456
|
+
*/
|
|
49457
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
49458
|
+
/**
|
|
49459
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
49460
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
49461
|
+
* from a running-state source, mute guessed from the last send).
|
|
49462
|
+
*/
|
|
49463
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
49464
|
+
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
49465
|
+
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
49466
|
+
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
49467
|
+
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
49468
|
+
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
49469
|
+
isMuted: boolean().nullable(),
|
|
49470
|
+
/** Active source / input. Empty when no source surface. */
|
|
49471
|
+
source: string$2(),
|
|
49472
|
+
/** Selectable sources. Empty when no source surface.
|
|
49473
|
+
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
49474
|
+
availableSources: array(string$2()),
|
|
49475
|
+
/** Currently-playing media info. Null when nothing is playing. */
|
|
49476
|
+
currentMedia: MediaInfoSchema.nullable(),
|
|
49477
|
+
/** Current playback position in ms. Null when not seekable or
|
|
49478
|
+
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
49479
|
+
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49480
|
+
/** Total duration of the current media in ms. Null when unknown
|
|
49481
|
+
* (live stream) or nothing is playing. */
|
|
49482
|
+
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49483
|
+
/** Shuffle toggle. Null when no shuffle surface.
|
|
49484
|
+
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
49485
|
+
shuffle: boolean().nullable(),
|
|
49486
|
+
/** Repeat mode. Null when no repeat surface.
|
|
49487
|
+
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
49488
|
+
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
49489
|
+
/** Ms epoch when the slice was last updated. */
|
|
49490
|
+
lastChangedAt: number()
|
|
49491
|
+
});
|
|
49492
|
+
var stepInput = object({
|
|
49493
|
+
deviceId: number().int().nonnegative(),
|
|
49494
|
+
/** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
|
|
49495
|
+
steps: number().int().min(1).max(50).default(1)
|
|
49496
|
+
});
|
|
49497
|
+
var deviceOnly = object({ deviceId: number().int().nonnegative() });
|
|
49498
|
+
var mediaPlayerCapability = {
|
|
49499
|
+
name: "media-player",
|
|
49500
|
+
scope: "device",
|
|
49501
|
+
deviceNative: true,
|
|
49502
|
+
mode: "singleton",
|
|
49503
|
+
deviceTypes: [DeviceType.MediaPlayer],
|
|
49504
|
+
methods: {
|
|
49505
|
+
turnOn: method(deviceOnly, _void(), {
|
|
49506
|
+
kind: "mutation",
|
|
49507
|
+
auth: "protected"
|
|
49508
|
+
}),
|
|
49509
|
+
turnOff: method(deviceOnly, _void(), {
|
|
49510
|
+
kind: "mutation",
|
|
49511
|
+
auth: "protected"
|
|
49512
|
+
}),
|
|
49513
|
+
volumeUp: method(stepInput, _void(), {
|
|
49514
|
+
kind: "mutation",
|
|
49515
|
+
auth: "protected"
|
|
49516
|
+
}),
|
|
49517
|
+
volumeDown: method(stepInput, _void(), {
|
|
49518
|
+
kind: "mutation",
|
|
49519
|
+
auth: "protected"
|
|
49520
|
+
}),
|
|
49521
|
+
channelUp: method(stepInput, _void(), {
|
|
49522
|
+
kind: "mutation",
|
|
49523
|
+
auth: "protected"
|
|
49524
|
+
}),
|
|
49525
|
+
channelDown: method(stepInput, _void(), {
|
|
49526
|
+
kind: "mutation",
|
|
49527
|
+
auth: "protected"
|
|
49528
|
+
}),
|
|
49529
|
+
play: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49530
|
+
kind: "mutation",
|
|
49531
|
+
auth: "protected"
|
|
49532
|
+
}),
|
|
49533
|
+
pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49534
|
+
kind: "mutation",
|
|
49535
|
+
auth: "protected"
|
|
49536
|
+
}),
|
|
49537
|
+
stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49538
|
+
kind: "mutation",
|
|
49539
|
+
auth: "protected"
|
|
49540
|
+
}),
|
|
49541
|
+
next: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49542
|
+
kind: "mutation",
|
|
49543
|
+
auth: "protected"
|
|
49544
|
+
}),
|
|
49545
|
+
previous: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49546
|
+
kind: "mutation",
|
|
49547
|
+
auth: "protected"
|
|
49548
|
+
}),
|
|
49549
|
+
seek: method(object({
|
|
49550
|
+
deviceId: number().int().nonnegative(),
|
|
49551
|
+
position: number().int().nonnegative()
|
|
49552
|
+
}), _void(), {
|
|
49553
|
+
kind: "mutation",
|
|
49554
|
+
auth: "protected"
|
|
49555
|
+
}),
|
|
49556
|
+
setVolume: method(object({
|
|
49557
|
+
deviceId: number().int().nonnegative(),
|
|
49558
|
+
volumeLevel: number().min(0).max(100)
|
|
49559
|
+
}), _void(), {
|
|
49560
|
+
kind: "mutation",
|
|
49561
|
+
auth: "protected"
|
|
49562
|
+
}),
|
|
49563
|
+
setMute: method(object({
|
|
49564
|
+
deviceId: number().int().nonnegative(),
|
|
49565
|
+
muted: boolean()
|
|
49566
|
+
}), _void(), {
|
|
49567
|
+
kind: "mutation",
|
|
49568
|
+
auth: "protected"
|
|
49569
|
+
}),
|
|
49570
|
+
setShuffle: method(object({
|
|
49571
|
+
deviceId: number().int().nonnegative(),
|
|
49572
|
+
shuffle: boolean()
|
|
49573
|
+
}), _void(), {
|
|
49574
|
+
kind: "mutation",
|
|
49575
|
+
auth: "protected"
|
|
49576
|
+
}),
|
|
49577
|
+
setRepeat: method(object({
|
|
49578
|
+
deviceId: number().int().nonnegative(),
|
|
49579
|
+
repeat: MediaPlayerRepeatSchema
|
|
49580
|
+
}), _void(), {
|
|
49581
|
+
kind: "mutation",
|
|
49582
|
+
auth: "protected"
|
|
49583
|
+
}),
|
|
49584
|
+
selectSource: method(object({
|
|
49585
|
+
deviceId: number().int().nonnegative(),
|
|
49586
|
+
source: string$2().min(1)
|
|
49587
|
+
}), _void(), {
|
|
49588
|
+
kind: "mutation",
|
|
49589
|
+
auth: "protected"
|
|
49590
|
+
}),
|
|
49591
|
+
playMedia: method(object({
|
|
49592
|
+
deviceId: number().int().nonnegative(),
|
|
49593
|
+
/** Media identifier / URL. */
|
|
49594
|
+
mediaId: string$2().min(1),
|
|
49595
|
+
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
49596
|
+
* passes it through to the upstream service. */
|
|
49597
|
+
mediaType: string$2().min(1)
|
|
49598
|
+
}), _void(), {
|
|
49599
|
+
kind: "mutation",
|
|
49600
|
+
auth: "protected"
|
|
49601
|
+
})
|
|
49602
|
+
},
|
|
49603
|
+
status: {
|
|
49604
|
+
schema: MediaPlayerStatusSchema,
|
|
49605
|
+
kind: "push"
|
|
49606
|
+
},
|
|
49607
|
+
/**
|
|
49608
|
+
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
49609
|
+
* full slice for live now-playing, volume, and progress updates
|
|
49610
|
+
* without polling.
|
|
49611
|
+
*/
|
|
49612
|
+
runtimeState: MediaPlayerStatusSchema,
|
|
49613
|
+
/**
|
|
49614
|
+
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
49615
|
+
*
|
|
49616
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
49617
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
49618
|
+
*/
|
|
49619
|
+
durability: "session",
|
|
49620
|
+
/**
|
|
49621
|
+
* History (D850): volume (a level an operator or an automation sets; the
|
|
49622
|
+
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
49623
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
49624
|
+
*/
|
|
49625
|
+
history: { fields: {
|
|
49626
|
+
volumeLevel: {
|
|
49627
|
+
kind: "gauge",
|
|
49628
|
+
unit: "%",
|
|
49629
|
+
deadband: 1
|
|
49630
|
+
},
|
|
49631
|
+
isMuted: { kind: "boolean" }
|
|
49632
|
+
} }
|
|
49633
|
+
};
|
|
48772
49634
|
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.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Fan, 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.Heater, 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.Heater, 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.Heater, 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.Heater, 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, DeviceType.Heater;
|
|
48773
49635
|
var NUMBER = {
|
|
48774
49636
|
kind: "fixed",
|
|
@@ -49216,117 +50078,6 @@ function actuator(input) {
|
|
|
49216
50078
|
};
|
|
49217
50079
|
}
|
|
49218
50080
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
49219
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
49220
|
-
"no-power-source",
|
|
49221
|
-
"switch-missing",
|
|
49222
|
-
"switch-offline",
|
|
49223
|
-
"switch-unknown",
|
|
49224
|
-
"restart-in-progress",
|
|
49225
|
-
"switch-call-failed"
|
|
49226
|
-
]);
|
|
49227
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
49228
|
-
accepted: literal(false),
|
|
49229
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
49230
|
-
/** One line for the operator, naming the switch. */
|
|
49231
|
-
message: string$2()
|
|
49232
|
-
})]);
|
|
49233
|
-
var PowerSourceInfoSchema = object({
|
|
49234
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
49235
|
-
linked: boolean(),
|
|
49236
|
-
switchDeviceId: number().nullable(),
|
|
49237
|
-
switchName: string$2().nullable(),
|
|
49238
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
49239
|
-
switchOn: boolean().nullable(),
|
|
49240
|
-
switchOnline: boolean().nullable(),
|
|
49241
|
-
restartInProgress: boolean(),
|
|
49242
|
-
offSeconds: number().int()
|
|
49243
|
-
});
|
|
49244
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
49245
|
-
var PowerSourceStateSchema = object({
|
|
49246
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
49247
|
-
switchOn: boolean().nullable(),
|
|
49248
|
-
offSeconds: number().int(),
|
|
49249
|
-
lastFetchedAt: number()
|
|
49250
|
-
});
|
|
49251
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
49252
|
-
DeviceType.Gateway,
|
|
49253
|
-
DeviceType.Hub,
|
|
49254
|
-
DeviceType.Camera,
|
|
49255
|
-
DeviceType.Sensor,
|
|
49256
|
-
DeviceType.MediaPlayer,
|
|
49257
|
-
DeviceType.Vacuum,
|
|
49258
|
-
DeviceType.Generic,
|
|
49259
|
-
DeviceType.Washer,
|
|
49260
|
-
DeviceType.Dryer,
|
|
49261
|
-
DeviceType.Dishwasher,
|
|
49262
|
-
DeviceType.PetFountain,
|
|
49263
|
-
DeviceType.Fan,
|
|
49264
|
-
DeviceType.Humidifier,
|
|
49265
|
-
DeviceType.Climate,
|
|
49266
|
-
DeviceType.WaterHeater
|
|
49267
|
-
];
|
|
49268
|
-
var powerSourceCustomize = {
|
|
49269
|
-
version: 1,
|
|
49270
|
-
targets: [{
|
|
49271
|
-
id: "switch",
|
|
49272
|
-
cardinality: "scalar",
|
|
49273
|
-
vocabulary: {
|
|
49274
|
-
cap: "power-source",
|
|
49275
|
-
stateField: "switchOn"
|
|
49276
|
-
},
|
|
49277
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
49278
|
-
apply: "add",
|
|
49279
|
-
path: "switchOn",
|
|
49280
|
-
sources: [{
|
|
49281
|
-
kind: "switch",
|
|
49282
|
-
transforms: [{ kind: "identity" }]
|
|
49283
|
-
}],
|
|
49284
|
-
companions: [{
|
|
49285
|
-
kind: "constant",
|
|
49286
|
-
path: "offSeconds",
|
|
49287
|
-
value: 10,
|
|
49288
|
-
when: "add"
|
|
49289
|
-
}, {
|
|
49290
|
-
kind: "clock",
|
|
49291
|
-
path: "lastFetchedAt",
|
|
49292
|
-
when: "always"
|
|
49293
|
-
}],
|
|
49294
|
-
sourceReport: { status: "assignment-status" }
|
|
49295
|
-
}]
|
|
49296
|
-
};
|
|
49297
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
49298
|
-
var powerSourceCapability = {
|
|
49299
|
-
name: "power-source",
|
|
49300
|
-
scope: "device",
|
|
49301
|
-
mode: "singleton",
|
|
49302
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
49303
|
-
methods: {
|
|
49304
|
-
getStatus: systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }),
|
|
49305
|
-
powerOn: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49306
|
-
kind: "mutation",
|
|
49307
|
-
auth: "admin"
|
|
49308
|
-
}),
|
|
49309
|
-
powerOff: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49310
|
-
kind: "mutation",
|
|
49311
|
-
auth: "admin"
|
|
49312
|
-
}),
|
|
49313
|
-
forceRestart: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49314
|
-
kind: "mutation",
|
|
49315
|
-
auth: "admin"
|
|
49316
|
-
})
|
|
49317
|
-
},
|
|
49318
|
-
runtimeState: PowerSourceStateSchema,
|
|
49319
|
-
customize: powerSourceCustomize,
|
|
49320
|
-
/**
|
|
49321
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
49322
|
-
* its next report; a restored value would outlive the reading it names.
|
|
49323
|
-
*
|
|
49324
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
49325
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
49326
|
-
*/
|
|
49327
|
-
durability: "session",
|
|
49328
|
-
history: "none"
|
|
49329
|
-
};
|
|
49330
50081
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
49331
50082
|
DeviceType.Camera;
|
|
49332
50083
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -49354,6 +50105,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
49354
50105
|
}).optional()
|
|
49355
50106
|
})) });
|
|
49356
50107
|
/**
|
|
50108
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
50109
|
+
* `z.infer`, they do not restate the shape.
|
|
50110
|
+
*/
|
|
50111
|
+
var OverrideSourceSchema = object({
|
|
50112
|
+
addonId: string$2().min(1),
|
|
50113
|
+
stableId: string$2().min(1),
|
|
50114
|
+
cap: string$2().min(1),
|
|
50115
|
+
fieldPath: string$2().min(1)
|
|
50116
|
+
});
|
|
50117
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
50118
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
50119
|
+
var ThresholdTransformDraftSchema = object({
|
|
50120
|
+
kind: literal("threshold"),
|
|
50121
|
+
comparison: _enum(["gte", "lte"]),
|
|
50122
|
+
threshold: number(),
|
|
50123
|
+
startDwellMs: number().nonnegative(),
|
|
50124
|
+
stopDwellMs: number().nonnegative()
|
|
50125
|
+
});
|
|
50126
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
50127
|
+
IdentityTransformDraftSchema,
|
|
50128
|
+
InvertTransformDraftSchema,
|
|
50129
|
+
ThresholdTransformDraftSchema,
|
|
50130
|
+
object({
|
|
50131
|
+
kind: literal("linear"),
|
|
50132
|
+
scale: number(),
|
|
50133
|
+
offset: number(),
|
|
50134
|
+
clampMin: number().optional(),
|
|
50135
|
+
clampMax: number().optional()
|
|
50136
|
+
}),
|
|
50137
|
+
object({
|
|
50138
|
+
kind: literal("enum-map"),
|
|
50139
|
+
mapping: record(string$2(), string$2().nullable())
|
|
50140
|
+
}),
|
|
50141
|
+
object({ kind: literal("number") })
|
|
50142
|
+
]);
|
|
50143
|
+
var ScalarOverrideDraftSchema = object({
|
|
50144
|
+
kind: literal("scalar"),
|
|
50145
|
+
cap: string$2().min(1),
|
|
50146
|
+
targetId: string$2().min(1),
|
|
50147
|
+
source: OverrideSourceSchema,
|
|
50148
|
+
transform: CustomizeTransformDraftSchema
|
|
50149
|
+
});
|
|
50150
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
50151
|
+
kind: literal("keyedArray"),
|
|
50152
|
+
cap: string$2().min(1),
|
|
50153
|
+
targetId: string$2().min(1),
|
|
50154
|
+
key: string$2().min(1),
|
|
50155
|
+
label: string$2().min(1),
|
|
50156
|
+
fieldPath: string$2().min(1),
|
|
50157
|
+
source: OverrideSourceSchema,
|
|
50158
|
+
transform: CustomizeTransformDraftSchema
|
|
50159
|
+
});
|
|
50160
|
+
var PulseOverrideDraftSchema = object({
|
|
50161
|
+
kind: literal("pulse"),
|
|
50162
|
+
cap: string$2().min(1),
|
|
50163
|
+
targetId: string$2().min(1),
|
|
50164
|
+
enabled: boolean(),
|
|
50165
|
+
debounceMs: number().nonnegative(),
|
|
50166
|
+
edges: array(_enum([
|
|
50167
|
+
"rising",
|
|
50168
|
+
"button",
|
|
50169
|
+
"native-event"
|
|
50170
|
+
])).min(1).optional(),
|
|
50171
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
50172
|
+
});
|
|
50173
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
50174
|
+
"in-addition",
|
|
50175
|
+
"confirm",
|
|
50176
|
+
"replace"
|
|
50177
|
+
]).optional() });
|
|
50178
|
+
discriminatedUnion("kind", [
|
|
50179
|
+
ScalarOverrideDraftSchema,
|
|
50180
|
+
KeyedArrayOverrideDraftSchema,
|
|
50181
|
+
PulseOverrideDraftSchema,
|
|
50182
|
+
object({
|
|
50183
|
+
kind: literal("span"),
|
|
50184
|
+
cap: string$2().min(1),
|
|
50185
|
+
targetId: string$2().min(1),
|
|
50186
|
+
timeoutMs: number().nonnegative(),
|
|
50187
|
+
cooldownMs: number().nonnegative().optional(),
|
|
50188
|
+
sources: array(SpanSourceDraftSchema)
|
|
50189
|
+
})
|
|
50190
|
+
]);
|
|
50191
|
+
object({
|
|
50192
|
+
targetId: string$2().min(1),
|
|
50193
|
+
configured: boolean(),
|
|
50194
|
+
source: _enum(["native", "assigned"]),
|
|
50195
|
+
state: _enum([
|
|
50196
|
+
"active",
|
|
50197
|
+
"stale",
|
|
50198
|
+
"released",
|
|
50199
|
+
"unavailable"
|
|
50200
|
+
]),
|
|
50201
|
+
reason: string$2().nullable()
|
|
50202
|
+
});
|
|
50203
|
+
/**
|
|
50204
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
50205
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
50206
|
+
*
|
|
50207
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
50208
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
50209
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
50210
|
+
*/
|
|
50211
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
50212
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
50213
|
+
"high",
|
|
50214
|
+
"medium",
|
|
50215
|
+
"low"
|
|
50216
|
+
]);
|
|
50217
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
50218
|
+
"no-history",
|
|
50219
|
+
"span-too-short",
|
|
50220
|
+
"truncated",
|
|
50221
|
+
"no-idle-floor",
|
|
50222
|
+
"no-runs",
|
|
50223
|
+
"no-separation"
|
|
50224
|
+
]);
|
|
50225
|
+
var RunningProposalReplaySchema = object({
|
|
50226
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
50227
|
+
intervals: number().int().nonnegative(),
|
|
50228
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
50229
|
+
extra: number().int().nonnegative(),
|
|
50230
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
50231
|
+
missed: number().int().nonnegative()
|
|
50232
|
+
});
|
|
50233
|
+
var RunningProposalEvidenceSchema = object({
|
|
50234
|
+
tier: RunningProposalTierSchema,
|
|
50235
|
+
spanMs: number().nonnegative(),
|
|
50236
|
+
from: number(),
|
|
50237
|
+
to: number(),
|
|
50238
|
+
points: number().int().nonnegative(),
|
|
50239
|
+
runsFound: number().int().nonnegative(),
|
|
50240
|
+
pausesFound: number().int().nonnegative(),
|
|
50241
|
+
longestPauseMs: number().nonnegative(),
|
|
50242
|
+
idleFloorW: number(),
|
|
50243
|
+
noiseCeilingW: number(),
|
|
50244
|
+
plateauW: number(),
|
|
50245
|
+
replay: RunningProposalReplaySchema
|
|
50246
|
+
});
|
|
50247
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
50248
|
+
spanMs: number().nonnegative(),
|
|
50249
|
+
points: number().int().nonnegative(),
|
|
50250
|
+
runsFound: number().int().nonnegative()
|
|
50251
|
+
});
|
|
50252
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
50253
|
+
state: literal("proposal"),
|
|
50254
|
+
transform: ThresholdTransformDraftSchema,
|
|
50255
|
+
evidence: RunningProposalEvidenceSchema,
|
|
50256
|
+
confidence: RunningProposalConfidenceSchema,
|
|
50257
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
50258
|
+
notes: array(string$2())
|
|
50259
|
+
}), object({
|
|
50260
|
+
state: literal("insufficient"),
|
|
50261
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
50262
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
50263
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
50264
|
+
fallback: ThresholdTransformDraftSchema
|
|
50265
|
+
})]);
|
|
50266
|
+
/**
|
|
49357
50267
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
49358
50268
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
49359
50269
|
*
|
|
@@ -49643,6 +50553,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
49643
50553
|
kind: "mutation",
|
|
49644
50554
|
auth: "admin",
|
|
49645
50555
|
access: "view"
|
|
50556
|
+
}), method(object({
|
|
50557
|
+
deviceId: number().int(),
|
|
50558
|
+
cap: string$2().min(1),
|
|
50559
|
+
fieldPath: string$2().min(1),
|
|
50560
|
+
deviceType: _enum(DeviceType).optional()
|
|
50561
|
+
}), RunningThresholdProposalSchema, {
|
|
50562
|
+
auth: "admin",
|
|
50563
|
+
access: "view"
|
|
49646
50564
|
}), method(object({
|
|
49647
50565
|
blockId: string$2().min(1),
|
|
49648
50566
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -50598,7 +51516,27 @@ method(object({
|
|
|
50598
51516
|
* exposes only the COMMON surface: link state, supported device
|
|
50599
51517
|
* kinds (so the UI can filter the device picker), and per-device
|
|
50600
51518
|
* expose/unexpose.
|
|
50601
|
-
|
|
51519
|
+
*
|
|
51520
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
51521
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
51522
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
51523
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
51524
|
+
* default instance's); present = that instance; an id the provider does not
|
|
51525
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
51526
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
51527
|
+
* change always names the target it changes. A single-instance provider
|
|
51528
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
51529
|
+
*/
|
|
51530
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
51531
|
+
var InstanceIdSchema$1 = string$2().min(1);
|
|
51532
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
51533
|
+
/** One independent export target of an addon. */
|
|
51534
|
+
var ExportInstanceSchema = object({
|
|
51535
|
+
instanceId: InstanceIdSchema$1,
|
|
51536
|
+
name: string$2(),
|
|
51537
|
+
enabled: boolean(),
|
|
51538
|
+
kind: string$2().optional()
|
|
51539
|
+
});
|
|
50602
51540
|
var LinkStateSchema = _enum([
|
|
50603
51541
|
"unlinked",
|
|
50604
51542
|
"linked",
|
|
@@ -50649,10 +51587,14 @@ var ExposedDeviceSchema = object({
|
|
|
50649
51587
|
capabilities: array(string$2()).optional()
|
|
50650
51588
|
});
|
|
50651
51589
|
var ExposeInputSchema = object({
|
|
51590
|
+
instanceId: InstanceIdSchema$1,
|
|
50652
51591
|
deviceId: string$2(),
|
|
50653
51592
|
capabilities: array(string$2()).optional()
|
|
50654
51593
|
});
|
|
50655
|
-
var UnexposeInputSchema = object({
|
|
51594
|
+
var UnexposeInputSchema = object({
|
|
51595
|
+
instanceId: InstanceIdSchema$1,
|
|
51596
|
+
deviceId: string$2()
|
|
51597
|
+
});
|
|
50656
51598
|
/**
|
|
50657
51599
|
* What an exporter may expose besides devices (D968). An action and a
|
|
50658
51600
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -50681,15 +51623,21 @@ var ExposedEntitySchema = object({
|
|
|
50681
51623
|
entity: ExportEntityRefSchema,
|
|
50682
51624
|
exposedAs: string$2().optional()
|
|
50683
51625
|
});
|
|
50684
|
-
var ExposeEntityInputSchema = object({
|
|
50685
|
-
|
|
50686
|
-
|
|
51626
|
+
var ExposeEntityInputSchema = object({
|
|
51627
|
+
instanceId: InstanceIdSchema$1,
|
|
51628
|
+
entity: ExportEntityRefSchema
|
|
51629
|
+
});
|
|
51630
|
+
var UnexposeEntityInputSchema = object({
|
|
51631
|
+
instanceId: InstanceIdSchema$1,
|
|
51632
|
+
entity: ExportEntityRefSchema
|
|
51633
|
+
});
|
|
51634
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
50687
51635
|
kind: "mutation",
|
|
50688
51636
|
auth: "admin"
|
|
50689
51637
|
}), method(UnexposeInputSchema, _void(), {
|
|
50690
51638
|
kind: "mutation",
|
|
50691
51639
|
auth: "admin"
|
|
50692
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(
|
|
51640
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
50693
51641
|
kind: "mutation",
|
|
50694
51642
|
auth: "admin"
|
|
50695
51643
|
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
@@ -52691,7 +53639,7 @@ var DishwasherStatusSchema = object({
|
|
|
52691
53639
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
52692
53640
|
lastFetchedAt: number()
|
|
52693
53641
|
});
|
|
52694
|
-
var DeviceIdSchema$
|
|
53642
|
+
var DeviceIdSchema$2 = number().int().nonnegative();
|
|
52695
53643
|
/** A value a program option or setting can take. */
|
|
52696
53644
|
var DishwasherValueSchema = union([
|
|
52697
53645
|
boolean(),
|
|
@@ -52766,77 +53714,77 @@ var dishwasherCapability = {
|
|
|
52766
53714
|
* option feature keys; each is checked against the program's constraints.
|
|
52767
53715
|
*/
|
|
52768
53716
|
start: method(object({
|
|
52769
|
-
deviceId: DeviceIdSchema$
|
|
53717
|
+
deviceId: DeviceIdSchema$2,
|
|
52770
53718
|
program: string$2().min(1).optional(),
|
|
52771
53719
|
options: record(string$2(), DishwasherValueSchema).optional()
|
|
52772
53720
|
}), _void(), {
|
|
52773
53721
|
kind: "mutation",
|
|
52774
53722
|
auth: "protected"
|
|
52775
53723
|
}),
|
|
52776
|
-
pause: method(object({ deviceId: DeviceIdSchema$
|
|
53724
|
+
pause: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52777
53725
|
kind: "mutation",
|
|
52778
53726
|
auth: "protected"
|
|
52779
53727
|
}),
|
|
52780
|
-
resume: method(object({ deviceId: DeviceIdSchema$
|
|
53728
|
+
resume: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52781
53729
|
kind: "mutation",
|
|
52782
53730
|
auth: "protected"
|
|
52783
53731
|
}),
|
|
52784
|
-
stop: method(object({ deviceId: DeviceIdSchema$
|
|
53732
|
+
stop: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52785
53733
|
kind: "mutation",
|
|
52786
53734
|
auth: "protected"
|
|
52787
53735
|
}),
|
|
52788
53736
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
52789
53737
|
setProgram: method(object({
|
|
52790
|
-
deviceId: DeviceIdSchema$
|
|
53738
|
+
deviceId: DeviceIdSchema$2,
|
|
52791
53739
|
program: string$2().min(1)
|
|
52792
53740
|
}), _void(), {
|
|
52793
53741
|
kind: "mutation",
|
|
52794
53742
|
auth: "protected"
|
|
52795
53743
|
}),
|
|
52796
53744
|
setPower: method(object({
|
|
52797
|
-
deviceId: DeviceIdSchema$
|
|
53745
|
+
deviceId: DeviceIdSchema$2,
|
|
52798
53746
|
on: boolean()
|
|
52799
53747
|
}), _void(), {
|
|
52800
53748
|
kind: "mutation",
|
|
52801
53749
|
auth: "protected"
|
|
52802
53750
|
}),
|
|
52803
53751
|
setHalfLoad: method(object({
|
|
52804
|
-
deviceId: DeviceIdSchema$
|
|
53752
|
+
deviceId: DeviceIdSchema$2,
|
|
52805
53753
|
on: boolean()
|
|
52806
53754
|
}), _void(), {
|
|
52807
53755
|
kind: "mutation",
|
|
52808
53756
|
auth: "protected"
|
|
52809
53757
|
}),
|
|
52810
53758
|
setHygienePlus: method(object({
|
|
52811
|
-
deviceId: DeviceIdSchema$
|
|
53759
|
+
deviceId: DeviceIdSchema$2,
|
|
52812
53760
|
on: boolean()
|
|
52813
53761
|
}), _void(), {
|
|
52814
53762
|
kind: "mutation",
|
|
52815
53763
|
auth: "protected"
|
|
52816
53764
|
}),
|
|
52817
53765
|
setExtraDry: method(object({
|
|
52818
|
-
deviceId: DeviceIdSchema$
|
|
53766
|
+
deviceId: DeviceIdSchema$2,
|
|
52819
53767
|
on: boolean()
|
|
52820
53768
|
}), _void(), {
|
|
52821
53769
|
kind: "mutation",
|
|
52822
53770
|
auth: "protected"
|
|
52823
53771
|
}),
|
|
52824
53772
|
setIntensiveZone: method(object({
|
|
52825
|
-
deviceId: DeviceIdSchema$
|
|
53773
|
+
deviceId: DeviceIdSchema$2,
|
|
52826
53774
|
on: boolean()
|
|
52827
53775
|
}), _void(), {
|
|
52828
53776
|
kind: "mutation",
|
|
52829
53777
|
auth: "protected"
|
|
52830
53778
|
}),
|
|
52831
53779
|
setVarioSpeedPlus: method(object({
|
|
52832
|
-
deviceId: DeviceIdSchema$
|
|
53780
|
+
deviceId: DeviceIdSchema$2,
|
|
52833
53781
|
on: boolean()
|
|
52834
53782
|
}), _void(), {
|
|
52835
53783
|
kind: "mutation",
|
|
52836
53784
|
auth: "protected"
|
|
52837
53785
|
}),
|
|
52838
53786
|
setChildLock: method(object({
|
|
52839
|
-
deviceId: DeviceIdSchema$
|
|
53787
|
+
deviceId: DeviceIdSchema$2,
|
|
52840
53788
|
on: boolean()
|
|
52841
53789
|
}), _void(), {
|
|
52842
53790
|
kind: "mutation",
|
|
@@ -52848,7 +53796,7 @@ var dishwasherCapability = {
|
|
|
52848
53796
|
* its declared range / members.
|
|
52849
53797
|
*/
|
|
52850
53798
|
setOption: method(object({
|
|
52851
|
-
deviceId: DeviceIdSchema$
|
|
53799
|
+
deviceId: DeviceIdSchema$2,
|
|
52852
53800
|
key: string$2().min(1),
|
|
52853
53801
|
value: DishwasherValueSchema
|
|
52854
53802
|
}), _void(), {
|
|
@@ -52856,7 +53804,7 @@ var dishwasherCapability = {
|
|
|
52856
53804
|
auth: "protected"
|
|
52857
53805
|
}),
|
|
52858
53806
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
52859
|
-
getOptions: method(object({ deviceId: DeviceIdSchema$
|
|
53807
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
52860
53808
|
},
|
|
52861
53809
|
status: {
|
|
52862
53810
|
schema: DishwasherStatusSchema,
|
|
@@ -54280,15 +55228,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
54280
55228
|
items: array(FanCaptureItemSchema),
|
|
54281
55229
|
omitted: array(FanCaptureOmissionSchema)
|
|
54282
55230
|
});
|
|
54283
|
-
var deviceInput$
|
|
55231
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
54284
55232
|
DeviceType.Fan;
|
|
54285
|
-
method(deviceInput$
|
|
55233
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
54286
55234
|
kind: "mutation",
|
|
54287
55235
|
auth: "admin"
|
|
54288
|
-
}), method(deviceInput$
|
|
55236
|
+
}), method(deviceInput$2.extend({ presetId: string$2().min(1) }), FanPresetApplyResultSchema, {
|
|
54289
55237
|
kind: "mutation",
|
|
54290
55238
|
auth: "protected"
|
|
54291
|
-
}), method(deviceInput$
|
|
55239
|
+
}), method(deviceInput$2.extend({
|
|
54292
55240
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
54293
55241
|
steps: array(ActionStepSchema).max(20).default([])
|
|
54294
55242
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -55199,13 +56147,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
55199
56147
|
*/
|
|
55200
56148
|
var ConfigSchemaPassthrough$3 = custom();
|
|
55201
56149
|
var IntegrationInstanceIdSchema = string$2().min(1);
|
|
56150
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56151
|
+
/**
|
|
56152
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56153
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56154
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
56155
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
56156
|
+
* instance id.
|
|
56157
|
+
*/
|
|
56158
|
+
var IntegrationInstanceKindSchema = object({
|
|
56159
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56160
|
+
id: string$2().min(1),
|
|
56161
|
+
label: string$2().min(1),
|
|
56162
|
+
/** The kind signs in through a server-held interactive session. */
|
|
56163
|
+
signIn: object({
|
|
56164
|
+
basePath: string$2().min(1),
|
|
56165
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
56166
|
+
}).optional(),
|
|
56167
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
56168
|
+
connectPath: string$2().min(1).optional(),
|
|
56169
|
+
/** Relative hub path to disconnect that account. */
|
|
56170
|
+
disconnectPath: string$2().min(1).optional()
|
|
56171
|
+
});
|
|
55202
56172
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56173
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56174
|
+
role: _enum([
|
|
56175
|
+
"devices",
|
|
56176
|
+
"services",
|
|
56177
|
+
"both"
|
|
56178
|
+
]),
|
|
56179
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56180
|
+
discovery: object({
|
|
56181
|
+
mode: _enum([
|
|
56182
|
+
"none",
|
|
56183
|
+
"lan",
|
|
56184
|
+
"account"
|
|
56185
|
+
]),
|
|
56186
|
+
scopedToInstance: boolean()
|
|
56187
|
+
}).optional(),
|
|
55203
56188
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
55204
|
-
instanceMode:
|
|
56189
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
55205
56190
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
55206
56191
|
kind: string$2(),
|
|
55207
56192
|
/** True when `testInstance` really probes the remote. */
|
|
55208
|
-
canTest: boolean()
|
|
56193
|
+
canTest: boolean(),
|
|
56194
|
+
/**
|
|
56195
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
56196
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
56197
|
+
*/
|
|
56198
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
56199
|
+
/**
|
|
56200
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
56201
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
56202
|
+
*/
|
|
56203
|
+
allowRestoreId: boolean().optional(),
|
|
56204
|
+
/**
|
|
56205
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56206
|
+
* send an id (the UI then mints an opaque one).
|
|
56207
|
+
*/
|
|
56208
|
+
mintsId: boolean().optional()
|
|
55209
56209
|
});
|
|
55210
56210
|
var IntegrationInstanceStatusSchema = object({
|
|
55211
56211
|
state: _enum([
|
|
@@ -55214,16 +56214,28 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
55214
56214
|
"error",
|
|
55215
56215
|
"unknown"
|
|
55216
56216
|
]),
|
|
55217
|
-
detail: string$2().optional()
|
|
56217
|
+
detail: string$2().optional(),
|
|
56218
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
56219
|
+
label: string$2().optional()
|
|
55218
56220
|
});
|
|
55219
56221
|
var IntegrationInstanceSchema = object({
|
|
55220
56222
|
id: IntegrationInstanceIdSchema,
|
|
55221
56223
|
name: string$2(),
|
|
55222
56224
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
55223
56225
|
configured: boolean(),
|
|
56226
|
+
/**
|
|
56227
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
56228
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
56229
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
56230
|
+
*/
|
|
56231
|
+
enabled: boolean().optional(),
|
|
56232
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
56233
|
+
kind: string$2().optional(),
|
|
55224
56234
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
55225
56235
|
formValues: record(string$2(), unknown()),
|
|
55226
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
56236
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
56237
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
56238
|
+
lastCheckedAt: number().optional()
|
|
55227
56239
|
});
|
|
55228
56240
|
var IntegrationInstancePatchSchema = object({
|
|
55229
56241
|
id: IntegrationInstanceIdSchema,
|
|
@@ -55231,25 +56243,57 @@ var IntegrationInstancePatchSchema = object({
|
|
|
55231
56243
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
55232
56244
|
values: record(string$2(), unknown())
|
|
55233
56245
|
});
|
|
55234
|
-
|
|
55235
|
-
|
|
55236
|
-
|
|
55237
|
-
|
|
55238
|
-
|
|
55239
|
-
|
|
55240
|
-
|
|
55241
|
-
|
|
55242
|
-
|
|
55243
|
-
|
|
55244
|
-
|
|
55245
|
-
|
|
55246
|
-
|
|
55247
|
-
|
|
55248
|
-
|
|
55249
|
-
|
|
55250
|
-
|
|
55251
|
-
|
|
55252
|
-
})
|
|
56246
|
+
var integrationInstancesCapability = {
|
|
56247
|
+
name: "integration-instances",
|
|
56248
|
+
scope: "system",
|
|
56249
|
+
mode: "collection",
|
|
56250
|
+
methods: {
|
|
56251
|
+
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56252
|
+
/** The form; `id: null` is the create form. */
|
|
56253
|
+
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56254
|
+
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56255
|
+
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56256
|
+
/**
|
|
56257
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
56258
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
56259
|
+
*/
|
|
56260
|
+
createInstance: method(object({
|
|
56261
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
56262
|
+
name: string$2().min(1),
|
|
56263
|
+
kind: string$2().min(1).optional()
|
|
56264
|
+
}), object({ id: string$2() }), {
|
|
56265
|
+
kind: "mutation",
|
|
56266
|
+
auth: "admin"
|
|
56267
|
+
}),
|
|
56268
|
+
updateInstance: method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
56269
|
+
kind: "mutation",
|
|
56270
|
+
auth: "admin"
|
|
56271
|
+
}),
|
|
56272
|
+
/**
|
|
56273
|
+
* Switch ONE instance on or off. A row control, not a form field: `updateInstance.values` is the
|
|
56274
|
+
* form patch (D62, D996). An off instance keeps its record and secrets and does no work.
|
|
56275
|
+
*/
|
|
56276
|
+
setInstanceEnabled: method(object({
|
|
56277
|
+
id: IntegrationInstanceIdSchema,
|
|
56278
|
+
enabled: boolean()
|
|
56279
|
+
}), object({ ok: literal(true) }), {
|
|
56280
|
+
kind: "mutation",
|
|
56281
|
+
auth: "admin"
|
|
56282
|
+
}),
|
|
56283
|
+
/** Delete the record and its keychain refs together. */
|
|
56284
|
+
deleteInstance: method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
56285
|
+
kind: "mutation",
|
|
56286
|
+
auth: "admin"
|
|
56287
|
+
}),
|
|
56288
|
+
testInstance: optionalMethod(object({
|
|
56289
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56290
|
+
values: record(string$2(), unknown())
|
|
56291
|
+
}), TestConnectionResultSchema$1, {
|
|
56292
|
+
kind: "mutation",
|
|
56293
|
+
auth: "admin"
|
|
56294
|
+
})
|
|
56295
|
+
}
|
|
56296
|
+
};
|
|
55253
56297
|
/**
|
|
55254
56298
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
55255
56299
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -57267,6 +58311,11 @@ var LogEntrySchema = object({
|
|
|
57267
58311
|
* flight, and not asking for the next slice ends the scan.
|
|
57268
58312
|
*/
|
|
57269
58313
|
var LogReadQuerySchema = object({
|
|
58314
|
+
/**
|
|
58315
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
58316
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
58317
|
+
*/
|
|
58318
|
+
instanceId: string$2().min(1).max(200).optional(),
|
|
57270
58319
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
57271
58320
|
requestId: string$2().min(1).max(64).optional(),
|
|
57272
58321
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -57329,6 +58378,8 @@ var LogReadResultSchema = object({
|
|
|
57329
58378
|
/** True when `cancelRead` ended this slice early. */
|
|
57330
58379
|
cancelled: boolean().optional()
|
|
57331
58380
|
});
|
|
58381
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
58382
|
+
var LogInstanceSelectorSchema = object({ instanceId: string$2().min(1).max(200).optional() });
|
|
57332
58383
|
var LogReadCapabilitiesSchema = object({
|
|
57333
58384
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
57334
58385
|
readable: boolean(),
|
|
@@ -57347,7 +58398,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
57347
58398
|
/** How far back the store reaches, when the provider knows (days). */
|
|
57348
58399
|
retentionDays: number().nullable()
|
|
57349
58400
|
});
|
|
57350
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
58401
|
+
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string$2().min(1).max(64) }), object({ cancelled: boolean() }), {
|
|
57351
58402
|
kind: "mutation",
|
|
57352
58403
|
auth: "admin"
|
|
57353
58404
|
});
|
|
@@ -57451,214 +58502,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
|
|
|
57451
58502
|
]);
|
|
57452
58503
|
method(_void(), array(LoginMethodContributionSchema).readonly());
|
|
57453
58504
|
/**
|
|
57454
|
-
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
57455
|
-
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
57456
|
-
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
57457
|
-
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
57458
|
-
*
|
|
57459
|
-
* The cap declares a wide method surface; not every backend supports
|
|
57460
|
-
* every method. Drivers signal sub-capability via the
|
|
57461
|
-
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
57462
|
-
* corresponding method) so the UI gates buttons without a slice
|
|
57463
|
-
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
57464
|
-
*/
|
|
57465
|
-
var MediaPlayerStateSchema = _enum([
|
|
57466
|
-
"off",
|
|
57467
|
-
"on",
|
|
57468
|
-
"idle",
|
|
57469
|
-
"playing",
|
|
57470
|
-
"paused",
|
|
57471
|
-
"buffering",
|
|
57472
|
-
"standby"
|
|
57473
|
-
]);
|
|
57474
|
-
var MediaPlayerRepeatSchema = _enum([
|
|
57475
|
-
"off",
|
|
57476
|
-
"all",
|
|
57477
|
-
"one"
|
|
57478
|
-
]);
|
|
57479
|
-
var MediaInfoSchema = object({
|
|
57480
|
-
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
57481
|
-
* `podcast`, …). */
|
|
57482
|
-
type: string$2(),
|
|
57483
|
-
/** Human-readable title. */
|
|
57484
|
-
title: string$2(),
|
|
57485
|
-
/** Optional artist / channel / station label. */
|
|
57486
|
-
artist: string$2().optional(),
|
|
57487
|
-
/** Optional album / season / show name. */
|
|
57488
|
-
album: string$2().optional(),
|
|
57489
|
-
/** Optional cover-art / thumbnail URL. */
|
|
57490
|
-
imageUrl: string$2().optional()
|
|
57491
|
-
});
|
|
57492
|
-
/**
|
|
57493
|
-
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
57494
|
-
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
57495
|
-
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
57496
|
-
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
57497
|
-
* guess, never as a reading.
|
|
57498
|
-
*/
|
|
57499
|
-
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
57500
|
-
/** The fields of a media-player status a driver can only guess. */
|
|
57501
|
-
var MediaPlayerAssumedFieldSchema = _enum([
|
|
57502
|
-
"state",
|
|
57503
|
-
"volumeLevel",
|
|
57504
|
-
"isMuted",
|
|
57505
|
-
"source"
|
|
57506
|
-
]);
|
|
57507
|
-
var MediaPlayerStatusSchema = object({
|
|
57508
|
-
/** Playback lifecycle state. */
|
|
57509
|
-
state: MediaPlayerStateSchema,
|
|
57510
|
-
/**
|
|
57511
|
-
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
57512
|
-
* A slice written before this field existed parses as `reported`.
|
|
57513
|
-
*/
|
|
57514
|
-
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
57515
|
-
/**
|
|
57516
|
-
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
57517
|
-
* this is not empty; a field not listed here is a reading even then (power
|
|
57518
|
-
* from a running-state source, mute guessed from the last send).
|
|
57519
|
-
*/
|
|
57520
|
-
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
57521
|
-
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
57522
|
-
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
57523
|
-
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
57524
|
-
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
57525
|
-
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
57526
|
-
isMuted: boolean().nullable(),
|
|
57527
|
-
/** Active source / input. Empty when no source surface. */
|
|
57528
|
-
source: string$2(),
|
|
57529
|
-
/** Selectable sources. Empty when no source surface.
|
|
57530
|
-
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
57531
|
-
availableSources: array(string$2()),
|
|
57532
|
-
/** Currently-playing media info. Null when nothing is playing. */
|
|
57533
|
-
currentMedia: MediaInfoSchema.nullable(),
|
|
57534
|
-
/** Current playback position in ms. Null when not seekable or
|
|
57535
|
-
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
57536
|
-
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57537
|
-
/** Total duration of the current media in ms. Null when unknown
|
|
57538
|
-
* (live stream) or nothing is playing. */
|
|
57539
|
-
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57540
|
-
/** Shuffle toggle. Null when no shuffle surface.
|
|
57541
|
-
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
57542
|
-
shuffle: boolean().nullable(),
|
|
57543
|
-
/** Repeat mode. Null when no repeat surface.
|
|
57544
|
-
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
57545
|
-
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
57546
|
-
/** Ms epoch when the slice was last updated. */
|
|
57547
|
-
lastChangedAt: number()
|
|
57548
|
-
});
|
|
57549
|
-
var mediaPlayerCapability = {
|
|
57550
|
-
name: "media-player",
|
|
57551
|
-
scope: "device",
|
|
57552
|
-
deviceNative: true,
|
|
57553
|
-
mode: "singleton",
|
|
57554
|
-
deviceTypes: [DeviceType.MediaPlayer],
|
|
57555
|
-
methods: {
|
|
57556
|
-
play: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57557
|
-
kind: "mutation",
|
|
57558
|
-
auth: "protected"
|
|
57559
|
-
}),
|
|
57560
|
-
pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57561
|
-
kind: "mutation",
|
|
57562
|
-
auth: "protected"
|
|
57563
|
-
}),
|
|
57564
|
-
stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57565
|
-
kind: "mutation",
|
|
57566
|
-
auth: "protected"
|
|
57567
|
-
}),
|
|
57568
|
-
next: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57569
|
-
kind: "mutation",
|
|
57570
|
-
auth: "protected"
|
|
57571
|
-
}),
|
|
57572
|
-
previous: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57573
|
-
kind: "mutation",
|
|
57574
|
-
auth: "protected"
|
|
57575
|
-
}),
|
|
57576
|
-
seek: method(object({
|
|
57577
|
-
deviceId: number().int().nonnegative(),
|
|
57578
|
-
position: number().int().nonnegative()
|
|
57579
|
-
}), _void(), {
|
|
57580
|
-
kind: "mutation",
|
|
57581
|
-
auth: "protected"
|
|
57582
|
-
}),
|
|
57583
|
-
setVolume: method(object({
|
|
57584
|
-
deviceId: number().int().nonnegative(),
|
|
57585
|
-
volumeLevel: number().min(0).max(100)
|
|
57586
|
-
}), _void(), {
|
|
57587
|
-
kind: "mutation",
|
|
57588
|
-
auth: "protected"
|
|
57589
|
-
}),
|
|
57590
|
-
setMute: method(object({
|
|
57591
|
-
deviceId: number().int().nonnegative(),
|
|
57592
|
-
muted: boolean()
|
|
57593
|
-
}), _void(), {
|
|
57594
|
-
kind: "mutation",
|
|
57595
|
-
auth: "protected"
|
|
57596
|
-
}),
|
|
57597
|
-
setShuffle: method(object({
|
|
57598
|
-
deviceId: number().int().nonnegative(),
|
|
57599
|
-
shuffle: boolean()
|
|
57600
|
-
}), _void(), {
|
|
57601
|
-
kind: "mutation",
|
|
57602
|
-
auth: "protected"
|
|
57603
|
-
}),
|
|
57604
|
-
setRepeat: method(object({
|
|
57605
|
-
deviceId: number().int().nonnegative(),
|
|
57606
|
-
repeat: MediaPlayerRepeatSchema
|
|
57607
|
-
}), _void(), {
|
|
57608
|
-
kind: "mutation",
|
|
57609
|
-
auth: "protected"
|
|
57610
|
-
}),
|
|
57611
|
-
selectSource: method(object({
|
|
57612
|
-
deviceId: number().int().nonnegative(),
|
|
57613
|
-
source: string$2().min(1)
|
|
57614
|
-
}), _void(), {
|
|
57615
|
-
kind: "mutation",
|
|
57616
|
-
auth: "protected"
|
|
57617
|
-
}),
|
|
57618
|
-
playMedia: method(object({
|
|
57619
|
-
deviceId: number().int().nonnegative(),
|
|
57620
|
-
/** Media identifier / URL. */
|
|
57621
|
-
mediaId: string$2().min(1),
|
|
57622
|
-
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
57623
|
-
* passes it through to the upstream service. */
|
|
57624
|
-
mediaType: string$2().min(1)
|
|
57625
|
-
}), _void(), {
|
|
57626
|
-
kind: "mutation",
|
|
57627
|
-
auth: "protected"
|
|
57628
|
-
})
|
|
57629
|
-
},
|
|
57630
|
-
status: {
|
|
57631
|
-
schema: MediaPlayerStatusSchema,
|
|
57632
|
-
kind: "push"
|
|
57633
|
-
},
|
|
57634
|
-
/**
|
|
57635
|
-
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
57636
|
-
* full slice for live now-playing, volume, and progress updates
|
|
57637
|
-
* without polling.
|
|
57638
|
-
*/
|
|
57639
|
-
runtimeState: MediaPlayerStatusSchema,
|
|
57640
|
-
/**
|
|
57641
|
-
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
57642
|
-
*
|
|
57643
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
57644
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
57645
|
-
*/
|
|
57646
|
-
durability: "session",
|
|
57647
|
-
/**
|
|
57648
|
-
* History (D850): volume (a level an operator or an automation sets; the
|
|
57649
|
-
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
57650
|
-
* `position` is not recorded: it moves continuously while playing.
|
|
57651
|
-
*/
|
|
57652
|
-
history: { fields: {
|
|
57653
|
-
volumeLevel: {
|
|
57654
|
-
kind: "gauge",
|
|
57655
|
-
unit: "%",
|
|
57656
|
-
deadband: 1
|
|
57657
|
-
},
|
|
57658
|
-
isMuted: { kind: "boolean" }
|
|
57659
|
-
} }
|
|
57660
|
-
};
|
|
57661
|
-
/**
|
|
57662
58505
|
* mesh-network — collection cap for mesh-VPN providers.
|
|
57663
58506
|
*
|
|
57664
58507
|
* Different shape from `network-access` (public tunnels):
|
|
@@ -66417,6 +67260,21 @@ var NcTemplateVarKindSchema = _enum([
|
|
|
66417
67260
|
"sound",
|
|
66418
67261
|
"system"
|
|
66419
67262
|
]);
|
|
67263
|
+
/**
|
|
67264
|
+
* The default title/body a rule of one SITUATION is delivered with when the
|
|
67265
|
+
* operator wrote no template — served so an editor can pre-fill it (and the
|
|
67266
|
+
* operator can rebuild it from placeholders) instead of starting from a blank
|
|
67267
|
+
* box that, once saved, silently replaces it. `id` names the situation inside
|
|
67268
|
+
* a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
|
|
67269
|
+
* appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
|
|
67270
|
+
* the hub's current language and with its overrides applied.
|
|
67271
|
+
*/
|
|
67272
|
+
var NcTemplateDefaultSchema = object({
|
|
67273
|
+
id: _enum(["appliance-cycle"]),
|
|
67274
|
+
kind: NcRuleKindSchema,
|
|
67275
|
+
title: string$2(),
|
|
67276
|
+
body: string$2()
|
|
67277
|
+
});
|
|
66420
67278
|
/** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
|
|
66421
67279
|
var NcTemplateFamilySchema = _enum([
|
|
66422
67280
|
"rule",
|
|
@@ -67272,7 +68130,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
67272
68130
|
}), method(object({}), object({
|
|
67273
68131
|
catalog: array(NcConditionDescriptorSchema),
|
|
67274
68132
|
taxonomy: NcTaxonomySchema.optional()
|
|
67275
|
-
})), method(object({}), object({
|
|
68133
|
+
})), method(object({}), object({
|
|
68134
|
+
vars: array(NcTemplateVarDescriptorSchema),
|
|
68135
|
+
/** Per-situation default texts; absent on a hub that predates them. */
|
|
68136
|
+
defaults: array(NcTemplateDefaultSchema)
|
|
68137
|
+
})), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
|
|
67276
68138
|
kind: "mutation",
|
|
67277
68139
|
access: "view"
|
|
67278
68140
|
}), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string$2().min(1) }), object({ url: string$2().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
@@ -68168,7 +69030,7 @@ var petFeederCapability = {
|
|
|
68168
69030
|
/**
|
|
68169
69031
|
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
68170
69032
|
* circulating drinking fountain: how much water is in the tank, how much the
|
|
68171
|
-
* pet drank, whether the pump runs
|
|
69033
|
+
* pet drank, whether the pump runs.
|
|
68172
69034
|
*
|
|
68173
69035
|
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
68174
69036
|
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
@@ -68184,8 +69046,10 @@ var petFeederCapability = {
|
|
|
68184
69046
|
* never here. Every reading may be `null`: a source that is offline or does
|
|
68185
69047
|
* not report is unknown, never zero.
|
|
68186
69048
|
*
|
|
68187
|
-
* The
|
|
68188
|
-
* cap
|
|
69049
|
+
* The pump's on/off duty cycle is model-specific and lives in the optional
|
|
69050
|
+
* `pump-cycle` cap. The filter, the cleaning and the water change are not
|
|
69051
|
+
* fields of this cap: they are items of the `consumables` cap on the same
|
|
69052
|
+
* device (kind `wear`), reset by the same forward mechanism.
|
|
68189
69053
|
*/
|
|
68190
69054
|
var PetFountainStatusSchema = object({
|
|
68191
69055
|
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
@@ -68196,27 +69060,20 @@ var PetFountainStatusSchema = object({
|
|
|
68196
69060
|
empty: boolean().nullable(),
|
|
68197
69061
|
/** True while the pump is running. Null while unknown. */
|
|
68198
69062
|
pumpRunning: boolean().nullable(),
|
|
68199
|
-
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
68200
|
-
pumpIntervalEnabled: boolean().nullable(),
|
|
68201
|
-
/** How long the pump runs per cycle, in seconds. */
|
|
68202
|
-
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
68203
|
-
/** How long the pump rests per cycle, in seconds. */
|
|
68204
|
-
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
68205
69063
|
/** Water drunk in the last drink, in litres. */
|
|
68206
69064
|
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68207
69065
|
/** Water drunk in the current hour, in litres. */
|
|
68208
69066
|
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68209
69067
|
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
68210
69068
|
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68211
|
-
/** Water drunk since local midnight, in litres. */
|
|
69069
|
+
/** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
|
|
68212
69070
|
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68213
69071
|
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
68214
69072
|
error: string$2().nullable(),
|
|
68215
69073
|
/** Ms epoch when the slice was last refreshed. */
|
|
68216
69074
|
lastFetchedAt: number()
|
|
68217
69075
|
});
|
|
68218
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
68219
|
-
var MinutesSchema = number().int().min(0);
|
|
69076
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
68220
69077
|
var petFountainCapability = {
|
|
68221
69078
|
name: "pet-fountain",
|
|
68222
69079
|
scope: "device",
|
|
@@ -68226,38 +69083,14 @@ var petFountainCapability = {
|
|
|
68226
69083
|
methods: {
|
|
68227
69084
|
/** Start or stop the pump. */
|
|
68228
69085
|
setPump: method(object({
|
|
68229
|
-
deviceId: DeviceIdSchema,
|
|
68230
|
-
on: boolean()
|
|
68231
|
-
}), _void(), {
|
|
68232
|
-
kind: "mutation",
|
|
68233
|
-
auth: "protected"
|
|
68234
|
-
}),
|
|
68235
|
-
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
68236
|
-
setPumpInterval: method(object({
|
|
68237
|
-
deviceId: DeviceIdSchema,
|
|
69086
|
+
deviceId: DeviceIdSchema$1,
|
|
68238
69087
|
on: boolean()
|
|
68239
69088
|
}), _void(), {
|
|
68240
69089
|
kind: "mutation",
|
|
68241
69090
|
auth: "protected"
|
|
68242
69091
|
}),
|
|
68243
|
-
/** Set how many minutes the pump runs per cycle. */
|
|
68244
|
-
setPumpOnMinutes: method(object({
|
|
68245
|
-
deviceId: DeviceIdSchema,
|
|
68246
|
-
minutes: MinutesSchema
|
|
68247
|
-
}), _void(), {
|
|
68248
|
-
kind: "mutation",
|
|
68249
|
-
auth: "protected"
|
|
68250
|
-
}),
|
|
68251
|
-
/** Set how many minutes the pump rests per cycle. */
|
|
68252
|
-
setPumpOffMinutes: method(object({
|
|
68253
|
-
deviceId: DeviceIdSchema,
|
|
68254
|
-
minutes: MinutesSchema
|
|
68255
|
-
}), _void(), {
|
|
68256
|
-
kind: "mutation",
|
|
68257
|
-
auth: "protected"
|
|
68258
|
-
}),
|
|
68259
69092
|
/** Clear the rolling daily consumption counters on the device. */
|
|
68260
|
-
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69093
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
68261
69094
|
kind: "mutation",
|
|
68262
69095
|
auth: "protected"
|
|
68263
69096
|
})
|
|
@@ -69892,6 +70725,14 @@ var powerMeterCapability = {
|
|
|
69892
70725
|
deviceNative: true,
|
|
69893
70726
|
mode: "singleton",
|
|
69894
70727
|
deviceTypes: POWER_METER_DEVICE_TYPES,
|
|
70728
|
+
deviceConfig: { ui: {
|
|
70729
|
+
kind: "widget",
|
|
70730
|
+
widgetId: "host/power-meter-line",
|
|
70731
|
+
tab: "power",
|
|
70732
|
+
placement: "hero",
|
|
70733
|
+
label: "Power",
|
|
70734
|
+
order: 4
|
|
70735
|
+
} },
|
|
69895
70736
|
methods: {},
|
|
69896
70737
|
status: {
|
|
69897
70738
|
schema: PowerMeterStatusSchema,
|
|
@@ -69951,6 +70792,117 @@ var powerMeterCapability = {
|
|
|
69951
70792
|
volatileStateFields: ["lastFetchedAt"],
|
|
69952
70793
|
customize: powerMeterCustomize
|
|
69953
70794
|
};
|
|
70795
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
70796
|
+
"no-power-source",
|
|
70797
|
+
"switch-missing",
|
|
70798
|
+
"switch-offline",
|
|
70799
|
+
"switch-unknown",
|
|
70800
|
+
"restart-in-progress",
|
|
70801
|
+
"switch-call-failed"
|
|
70802
|
+
]);
|
|
70803
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
70804
|
+
accepted: literal(false),
|
|
70805
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
70806
|
+
/** One line for the operator, naming the switch. */
|
|
70807
|
+
message: string$2()
|
|
70808
|
+
})]);
|
|
70809
|
+
var PowerSourceInfoSchema = object({
|
|
70810
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
70811
|
+
linked: boolean(),
|
|
70812
|
+
switchDeviceId: number().nullable(),
|
|
70813
|
+
switchName: string$2().nullable(),
|
|
70814
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
70815
|
+
switchOn: boolean().nullable(),
|
|
70816
|
+
switchOnline: boolean().nullable(),
|
|
70817
|
+
restartInProgress: boolean(),
|
|
70818
|
+
offSeconds: number().int()
|
|
70819
|
+
});
|
|
70820
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
70821
|
+
var PowerSourceStateSchema = object({
|
|
70822
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
70823
|
+
switchOn: boolean().nullable(),
|
|
70824
|
+
offSeconds: number().int(),
|
|
70825
|
+
lastFetchedAt: number()
|
|
70826
|
+
});
|
|
70827
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
70828
|
+
DeviceType.Gateway,
|
|
70829
|
+
DeviceType.Hub,
|
|
70830
|
+
DeviceType.Camera,
|
|
70831
|
+
DeviceType.Sensor,
|
|
70832
|
+
DeviceType.MediaPlayer,
|
|
70833
|
+
DeviceType.Vacuum,
|
|
70834
|
+
DeviceType.Generic,
|
|
70835
|
+
DeviceType.Washer,
|
|
70836
|
+
DeviceType.Dryer,
|
|
70837
|
+
DeviceType.Dishwasher,
|
|
70838
|
+
DeviceType.PetFountain,
|
|
70839
|
+
DeviceType.Fan,
|
|
70840
|
+
DeviceType.Humidifier,
|
|
70841
|
+
DeviceType.Climate,
|
|
70842
|
+
DeviceType.WaterHeater
|
|
70843
|
+
];
|
|
70844
|
+
var powerSourceCustomize = {
|
|
70845
|
+
version: 1,
|
|
70846
|
+
targets: [{
|
|
70847
|
+
id: "switch",
|
|
70848
|
+
cardinality: "scalar",
|
|
70849
|
+
vocabulary: {
|
|
70850
|
+
cap: "power-source",
|
|
70851
|
+
stateField: "switchOn"
|
|
70852
|
+
},
|
|
70853
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70854
|
+
apply: "add",
|
|
70855
|
+
path: "switchOn",
|
|
70856
|
+
sources: [{
|
|
70857
|
+
kind: "switch",
|
|
70858
|
+
transforms: [{ kind: "identity" }]
|
|
70859
|
+
}],
|
|
70860
|
+
companions: [{
|
|
70861
|
+
kind: "constant",
|
|
70862
|
+
path: "offSeconds",
|
|
70863
|
+
value: 10,
|
|
70864
|
+
when: "add"
|
|
70865
|
+
}, {
|
|
70866
|
+
kind: "clock",
|
|
70867
|
+
path: "lastFetchedAt",
|
|
70868
|
+
when: "always"
|
|
70869
|
+
}],
|
|
70870
|
+
sourceReport: { status: "assignment-status" }
|
|
70871
|
+
}]
|
|
70872
|
+
};
|
|
70873
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
70874
|
+
var powerSourceCapability = {
|
|
70875
|
+
name: "power-source",
|
|
70876
|
+
scope: "device",
|
|
70877
|
+
mode: "singleton",
|
|
70878
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70879
|
+
methods: {
|
|
70880
|
+
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
|
|
70881
|
+
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70882
|
+
kind: "mutation",
|
|
70883
|
+
auth: "admin"
|
|
70884
|
+
}),
|
|
70885
|
+
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70886
|
+
kind: "mutation",
|
|
70887
|
+
auth: "admin"
|
|
70888
|
+
}),
|
|
70889
|
+
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70890
|
+
kind: "mutation",
|
|
70891
|
+
auth: "admin"
|
|
70892
|
+
})
|
|
70893
|
+
},
|
|
70894
|
+
runtimeState: PowerSourceStateSchema,
|
|
70895
|
+
customize: powerSourceCustomize,
|
|
70896
|
+
/**
|
|
70897
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
70898
|
+
* its next report; a restored value would outlive the reading it names.
|
|
70899
|
+
*
|
|
70900
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
70901
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
70902
|
+
*/
|
|
70903
|
+
durability: "session",
|
|
70904
|
+
history: "none"
|
|
70905
|
+
};
|
|
69954
70906
|
/**
|
|
69955
70907
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
69956
70908
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -70590,6 +71542,79 @@ var ptzAutotrackCapability = {
|
|
|
70590
71542
|
*/
|
|
70591
71543
|
history: "none"
|
|
70592
71544
|
};
|
|
71545
|
+
/**
|
|
71546
|
+
* The on/off duty cycle of a pet fountain's pump. Some models run the pump
|
|
71547
|
+
* continuously and have nothing to configure; others can alternate a run time
|
|
71548
|
+
* and a rest time. That is model-specific, so it is its own optional cap on the
|
|
71549
|
+
* fountain device: a fountain without cycle entities simply never composes it
|
|
71550
|
+
* (planning#366), and the `pet-fountain` cap stays the model-independent core.
|
|
71551
|
+
*
|
|
71552
|
+
* Like `pet-fountain` it has no native vendor provider: an operator BUILDS the
|
|
71553
|
+
* cycle from loose entities (a cycle switch and two number boxes) and every
|
|
71554
|
+
* method is a forward to one source entity (D659, D967). Durations are stored
|
|
71555
|
+
* in SECONDS (D944); a number box that cannot be read back leaves them `null`
|
|
71556
|
+
* — unknown, never zero (D393).
|
|
71557
|
+
*/
|
|
71558
|
+
var PumpCycleStatusSchema = object({
|
|
71559
|
+
/** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
|
|
71560
|
+
enabled: boolean().nullable(),
|
|
71561
|
+
/** How long the pump runs per cycle, in seconds. Null while unknown. */
|
|
71562
|
+
onTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
71563
|
+
/** How long the pump rests per cycle, in seconds. Null while unknown. */
|
|
71564
|
+
offTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
71565
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
71566
|
+
lastFetchedAt: number()
|
|
71567
|
+
});
|
|
71568
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
71569
|
+
var MinutesSchema = number().int().min(0);
|
|
71570
|
+
var pumpCycleCapability = {
|
|
71571
|
+
name: "pump-cycle",
|
|
71572
|
+
scope: "device",
|
|
71573
|
+
deviceNative: true,
|
|
71574
|
+
mode: "singleton",
|
|
71575
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
71576
|
+
methods: {
|
|
71577
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
71578
|
+
setEnabled: method(object({
|
|
71579
|
+
deviceId: DeviceIdSchema,
|
|
71580
|
+
on: boolean()
|
|
71581
|
+
}), _void(), {
|
|
71582
|
+
kind: "mutation",
|
|
71583
|
+
auth: "protected"
|
|
71584
|
+
}),
|
|
71585
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
71586
|
+
setOnMinutes: method(object({
|
|
71587
|
+
deviceId: DeviceIdSchema,
|
|
71588
|
+
minutes: MinutesSchema
|
|
71589
|
+
}), _void(), {
|
|
71590
|
+
kind: "mutation",
|
|
71591
|
+
auth: "protected"
|
|
71592
|
+
}),
|
|
71593
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
71594
|
+
setOffMinutes: method(object({
|
|
71595
|
+
deviceId: DeviceIdSchema,
|
|
71596
|
+
minutes: MinutesSchema
|
|
71597
|
+
}), _void(), {
|
|
71598
|
+
kind: "mutation",
|
|
71599
|
+
auth: "protected"
|
|
71600
|
+
})
|
|
71601
|
+
},
|
|
71602
|
+
status: {
|
|
71603
|
+
schema: PumpCycleStatusSchema,
|
|
71604
|
+
kind: "push"
|
|
71605
|
+
},
|
|
71606
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
71607
|
+
runtimeState: PumpCycleStatusSchema,
|
|
71608
|
+
/**
|
|
71609
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
71610
|
+
*
|
|
71611
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
71612
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
71613
|
+
*/
|
|
71614
|
+
durability: "session",
|
|
71615
|
+
/** History (D850): a duty-cycle setting is not a series worth recording. */
|
|
71616
|
+
history: "none"
|
|
71617
|
+
};
|
|
70593
71618
|
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
70594
71619
|
kind: "mutation",
|
|
70595
71620
|
auth: "admin"
|
|
@@ -73380,7 +74405,13 @@ var RemoteMediaCommandSchema = _enum([
|
|
|
73380
74405
|
"next",
|
|
73381
74406
|
"previous",
|
|
73382
74407
|
"setMute",
|
|
73383
|
-
"selectSource"
|
|
74408
|
+
"selectSource",
|
|
74409
|
+
"turnOn",
|
|
74410
|
+
"turnOff",
|
|
74411
|
+
"volumeUp",
|
|
74412
|
+
"volumeDown",
|
|
74413
|
+
"channelUp",
|
|
74414
|
+
"channelDown"
|
|
73384
74415
|
]);
|
|
73385
74416
|
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
73386
74417
|
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
@@ -73401,7 +74432,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
|
|
|
73401
74432
|
}), RemoteSendResultSchema, {
|
|
73402
74433
|
kind: "mutation",
|
|
73403
74434
|
auth: "protected"
|
|
73404
|
-
}), method(deviceInput.extend({
|
|
74435
|
+
}), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
|
|
73405
74436
|
command: RemoteMediaCommandSchema,
|
|
73406
74437
|
muted: boolean().optional(),
|
|
73407
74438
|
source: string$2().optional(),
|
|
@@ -75647,7 +76678,11 @@ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
|
|
|
75647
76678
|
* `getSettingsUISchema()` surface.
|
|
75648
76679
|
*/
|
|
75649
76680
|
var SwitchStatusSchema = object({
|
|
75650
|
-
|
|
76681
|
+
/**
|
|
76682
|
+
* `null` = the device has never reported its state (a measurement that
|
|
76683
|
+
* has not happened is unknown, never `false`).
|
|
76684
|
+
*/
|
|
76685
|
+
on: boolean().nullable().meta({ label: "On" }),
|
|
75651
76686
|
/** Ms epoch of the last state change. Useful for UI "X minutes ago". */
|
|
75652
76687
|
lastChangedAt: number()
|
|
75653
76688
|
});
|
|
@@ -75672,7 +76707,7 @@ var switchCapability = {
|
|
|
75672
76707
|
}) },
|
|
75673
76708
|
events: { onStateChanged: { data: object({
|
|
75674
76709
|
deviceId: number(),
|
|
75675
|
-
on: boolean(),
|
|
76710
|
+
on: boolean().nullable(),
|
|
75676
76711
|
lastChangedAt: number()
|
|
75677
76712
|
}) } },
|
|
75678
76713
|
status: {
|
|
@@ -79229,6 +80264,26 @@ function buildDescriptor(capName, kind) {
|
|
|
79229
80264
|
* contributes one entry per bound cap present in this map.
|
|
79230
80265
|
*/
|
|
79231
80266
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
80267
|
+
/**
|
|
80268
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
80269
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
80270
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
80271
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
80272
|
+
* server sends an id, a client maps it to its own icon set.
|
|
80273
|
+
*
|
|
80274
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
80275
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
80276
|
+
* to `icon`.
|
|
80277
|
+
*/
|
|
80278
|
+
function digit(n) {
|
|
80279
|
+
return {
|
|
80280
|
+
label: t(n, n),
|
|
80281
|
+
icon: "hash",
|
|
80282
|
+
legend: n
|
|
80283
|
+
};
|
|
80284
|
+
}
|
|
80285
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
80286
|
+
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
79232
80287
|
var btn = (idSuffix, row, col, init) => ({
|
|
79233
80288
|
idSuffix,
|
|
79234
80289
|
row,
|
|
@@ -79355,6 +80410,34 @@ discriminatedUnion("kind", [object({
|
|
|
79355
80410
|
kind: literal("pronto"),
|
|
79356
80411
|
code: string$2()
|
|
79357
80412
|
})]);
|
|
80413
|
+
/**
|
|
80414
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
80415
|
+
*
|
|
80416
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
80417
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
80418
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
80419
|
+
* count of samples.
|
|
80420
|
+
*
|
|
80421
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80422
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
80423
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80424
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
80425
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
80426
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80427
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
80428
|
+
* covers the longest standby blip above T.
|
|
80429
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80430
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
80431
|
+
*/
|
|
80432
|
+
var SECOND_MS = 1e3;
|
|
80433
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
80434
|
+
24 * (60 * MINUTE_MS$1);
|
|
80435
|
+
10 * MINUTE_MS$1;
|
|
80436
|
+
30 * SECOND_MS;
|
|
80437
|
+
60 * SECOND_MS;
|
|
80438
|
+
20 * MINUTE_MS$1;
|
|
80439
|
+
10 * SECOND_MS;
|
|
80440
|
+
10 * SECOND_MS;
|
|
79358
80441
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
79359
80442
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
79360
80443
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -79715,6 +80798,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
79715
80798
|
pressureSensor: pressureSensorCapability,
|
|
79716
80799
|
privacyMask: privacyMaskCapability,
|
|
79717
80800
|
ptzAutotrack: ptzAutotrackCapability,
|
|
80801
|
+
pumpCycle: pumpCycleCapability,
|
|
79718
80802
|
recordingOnboard: recordingOnboardCapability,
|
|
79719
80803
|
recordingSignal: recordingSignalCapability,
|
|
79720
80804
|
runningState: runningStateCapability,
|
|
@@ -81899,6 +82983,27 @@ Object.freeze({
|
|
|
81899
82983
|
access: "view",
|
|
81900
82984
|
role: "viewer"
|
|
81901
82985
|
},
|
|
82986
|
+
"boardKeySource.getBoardKey": {
|
|
82987
|
+
capName: "board-key-source",
|
|
82988
|
+
capScope: "system",
|
|
82989
|
+
addonId: null,
|
|
82990
|
+
access: "view",
|
|
82991
|
+
role: "admin"
|
|
82992
|
+
},
|
|
82993
|
+
"boardKeySource.listBoards": {
|
|
82994
|
+
capName: "board-key-source",
|
|
82995
|
+
capScope: "system",
|
|
82996
|
+
addonId: null,
|
|
82997
|
+
access: "view",
|
|
82998
|
+
role: "admin"
|
|
82999
|
+
},
|
|
83000
|
+
"boardKeySource.listSources": {
|
|
83001
|
+
capName: "board-key-source",
|
|
83002
|
+
capScope: "system",
|
|
83003
|
+
addonId: null,
|
|
83004
|
+
access: "view",
|
|
83005
|
+
role: "admin"
|
|
83006
|
+
},
|
|
81902
83007
|
"brightness.getStatus": {
|
|
81903
83008
|
capName: "brightness",
|
|
81904
83009
|
capScope: "device",
|
|
@@ -82438,6 +83543,13 @@ Object.freeze({
|
|
|
82438
83543
|
access: "create",
|
|
82439
83544
|
role: "admin"
|
|
82440
83545
|
},
|
|
83546
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
83547
|
+
capName: "core-blocks",
|
|
83548
|
+
capScope: "system",
|
|
83549
|
+
addonId: null,
|
|
83550
|
+
access: "view",
|
|
83551
|
+
role: "admin"
|
|
83552
|
+
},
|
|
82441
83553
|
"coreBlocks.reportAutomationRun": {
|
|
82442
83554
|
capName: "core-blocks",
|
|
82443
83555
|
capScope: "system",
|
|
@@ -82949,6 +84061,13 @@ Object.freeze({
|
|
|
82949
84061
|
access: "view",
|
|
82950
84062
|
role: "admin"
|
|
82951
84063
|
},
|
|
84064
|
+
"deviceExport.listInstances": {
|
|
84065
|
+
capName: "device-export",
|
|
84066
|
+
capScope: "system",
|
|
84067
|
+
addonId: null,
|
|
84068
|
+
access: "view",
|
|
84069
|
+
role: "admin"
|
|
84070
|
+
},
|
|
82952
84071
|
"deviceExport.listSupportedDeviceKinds": {
|
|
82953
84072
|
capName: "device-export",
|
|
82954
84073
|
capScope: "system",
|
|
@@ -84580,6 +85699,13 @@ Object.freeze({
|
|
|
84580
85699
|
access: "view",
|
|
84581
85700
|
role: "admin"
|
|
84582
85701
|
},
|
|
85702
|
+
"integrationInstances.setInstanceEnabled": {
|
|
85703
|
+
capName: "integration-instances",
|
|
85704
|
+
capScope: "system",
|
|
85705
|
+
addonId: null,
|
|
85706
|
+
access: "create",
|
|
85707
|
+
role: "admin"
|
|
85708
|
+
},
|
|
84583
85709
|
"integrationInstances.testInstance": {
|
|
84584
85710
|
capName: "integration-instances",
|
|
84585
85711
|
capScope: "system",
|
|
@@ -85378,6 +86504,20 @@ Object.freeze({
|
|
|
85378
86504
|
access: "view",
|
|
85379
86505
|
role: "admin"
|
|
85380
86506
|
},
|
|
86507
|
+
"mediaPlayer.channelDown": {
|
|
86508
|
+
capName: "media-player",
|
|
86509
|
+
capScope: "device",
|
|
86510
|
+
addonId: null,
|
|
86511
|
+
access: "create",
|
|
86512
|
+
role: "viewer"
|
|
86513
|
+
},
|
|
86514
|
+
"mediaPlayer.channelUp": {
|
|
86515
|
+
capName: "media-player",
|
|
86516
|
+
capScope: "device",
|
|
86517
|
+
addonId: null,
|
|
86518
|
+
access: "create",
|
|
86519
|
+
role: "viewer"
|
|
86520
|
+
},
|
|
85381
86521
|
"mediaPlayer.getStatus": {
|
|
85382
86522
|
capName: "media-player",
|
|
85383
86523
|
capScope: "device",
|
|
@@ -85469,6 +86609,34 @@ Object.freeze({
|
|
|
85469
86609
|
access: "create",
|
|
85470
86610
|
role: "viewer"
|
|
85471
86611
|
},
|
|
86612
|
+
"mediaPlayer.turnOff": {
|
|
86613
|
+
capName: "media-player",
|
|
86614
|
+
capScope: "device",
|
|
86615
|
+
addonId: null,
|
|
86616
|
+
access: "create",
|
|
86617
|
+
role: "viewer"
|
|
86618
|
+
},
|
|
86619
|
+
"mediaPlayer.turnOn": {
|
|
86620
|
+
capName: "media-player",
|
|
86621
|
+
capScope: "device",
|
|
86622
|
+
addonId: null,
|
|
86623
|
+
access: "create",
|
|
86624
|
+
role: "viewer"
|
|
86625
|
+
},
|
|
86626
|
+
"mediaPlayer.volumeDown": {
|
|
86627
|
+
capName: "media-player",
|
|
86628
|
+
capScope: "device",
|
|
86629
|
+
addonId: null,
|
|
86630
|
+
access: "create",
|
|
86631
|
+
role: "viewer"
|
|
86632
|
+
},
|
|
86633
|
+
"mediaPlayer.volumeUp": {
|
|
86634
|
+
capName: "media-player",
|
|
86635
|
+
capScope: "device",
|
|
86636
|
+
addonId: null,
|
|
86637
|
+
access: "create",
|
|
86638
|
+
role: "viewer"
|
|
86639
|
+
},
|
|
85472
86640
|
"meshNetwork.getStatus": {
|
|
85473
86641
|
capName: "mesh-network",
|
|
85474
86642
|
capScope: "system",
|
|
@@ -86715,27 +87883,6 @@ Object.freeze({
|
|
|
86715
87883
|
access: "create",
|
|
86716
87884
|
role: "viewer"
|
|
86717
87885
|
},
|
|
86718
|
-
"petFountain.setPumpInterval": {
|
|
86719
|
-
capName: "pet-fountain",
|
|
86720
|
-
capScope: "device",
|
|
86721
|
-
addonId: null,
|
|
86722
|
-
access: "create",
|
|
86723
|
-
role: "viewer"
|
|
86724
|
-
},
|
|
86725
|
-
"petFountain.setPumpOffMinutes": {
|
|
86726
|
-
capName: "pet-fountain",
|
|
86727
|
-
capScope: "device",
|
|
86728
|
-
addonId: null,
|
|
86729
|
-
access: "create",
|
|
86730
|
-
role: "viewer"
|
|
86731
|
-
},
|
|
86732
|
-
"petFountain.setPumpOnMinutes": {
|
|
86733
|
-
capName: "pet-fountain",
|
|
86734
|
-
capScope: "device",
|
|
86735
|
-
addonId: null,
|
|
86736
|
-
access: "create",
|
|
86737
|
-
role: "viewer"
|
|
86738
|
-
},
|
|
86739
87886
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
86740
87887
|
capName: "pipeline-analytics",
|
|
86741
87888
|
capScope: "device",
|
|
@@ -88514,6 +89661,34 @@ Object.freeze({
|
|
|
88514
89661
|
access: "create",
|
|
88515
89662
|
role: "admin"
|
|
88516
89663
|
},
|
|
89664
|
+
"pumpCycle.getStatus": {
|
|
89665
|
+
capName: "pump-cycle",
|
|
89666
|
+
capScope: "device",
|
|
89667
|
+
addonId: null,
|
|
89668
|
+
access: "view",
|
|
89669
|
+
role: "viewer"
|
|
89670
|
+
},
|
|
89671
|
+
"pumpCycle.setEnabled": {
|
|
89672
|
+
capName: "pump-cycle",
|
|
89673
|
+
capScope: "device",
|
|
89674
|
+
addonId: null,
|
|
89675
|
+
access: "create",
|
|
89676
|
+
role: "viewer"
|
|
89677
|
+
},
|
|
89678
|
+
"pumpCycle.setOffMinutes": {
|
|
89679
|
+
capName: "pump-cycle",
|
|
89680
|
+
capScope: "device",
|
|
89681
|
+
addonId: null,
|
|
89682
|
+
access: "create",
|
|
89683
|
+
role: "viewer"
|
|
89684
|
+
},
|
|
89685
|
+
"pumpCycle.setOnMinutes": {
|
|
89686
|
+
capName: "pump-cycle",
|
|
89687
|
+
capScope: "device",
|
|
89688
|
+
addonId: null,
|
|
89689
|
+
access: "create",
|
|
89690
|
+
role: "viewer"
|
|
89691
|
+
},
|
|
88517
89692
|
"reboot.reboot": {
|
|
88518
89693
|
capName: "reboot",
|
|
88519
89694
|
capScope: "device",
|
|
@@ -89018,6 +90193,13 @@ Object.freeze({
|
|
|
89018
90193
|
access: "create",
|
|
89019
90194
|
role: "admin"
|
|
89020
90195
|
},
|
|
90196
|
+
"remote.getAssumedStatus": {
|
|
90197
|
+
capName: "remote",
|
|
90198
|
+
capScope: "system",
|
|
90199
|
+
addonId: null,
|
|
90200
|
+
access: "view",
|
|
90201
|
+
role: "admin"
|
|
90202
|
+
},
|
|
89021
90203
|
"remote.getConfig": {
|
|
89022
90204
|
capName: "remote",
|
|
89023
90205
|
capScope: "system",
|
|
@@ -91833,6 +93015,11 @@ Object.freeze({
|
|
|
91833
93015
|
form: "single",
|
|
91834
93016
|
optional: false
|
|
91835
93017
|
}],
|
|
93018
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
93019
|
+
name: "deviceId",
|
|
93020
|
+
form: "single",
|
|
93021
|
+
optional: false
|
|
93022
|
+
}],
|
|
91836
93023
|
"cover.close": [{
|
|
91837
93024
|
name: "deviceId",
|
|
91838
93025
|
form: "single",
|
|
@@ -92684,6 +93871,16 @@ Object.freeze({
|
|
|
92684
93871
|
form: "single",
|
|
92685
93872
|
optional: true
|
|
92686
93873
|
}],
|
|
93874
|
+
"mediaPlayer.channelDown": [{
|
|
93875
|
+
name: "deviceId",
|
|
93876
|
+
form: "single",
|
|
93877
|
+
optional: false
|
|
93878
|
+
}],
|
|
93879
|
+
"mediaPlayer.channelUp": [{
|
|
93880
|
+
name: "deviceId",
|
|
93881
|
+
form: "single",
|
|
93882
|
+
optional: false
|
|
93883
|
+
}],
|
|
92687
93884
|
"mediaPlayer.getStatus": [{
|
|
92688
93885
|
name: "deviceId",
|
|
92689
93886
|
form: "single",
|
|
@@ -92749,6 +93946,26 @@ Object.freeze({
|
|
|
92749
93946
|
form: "single",
|
|
92750
93947
|
optional: false
|
|
92751
93948
|
}],
|
|
93949
|
+
"mediaPlayer.turnOff": [{
|
|
93950
|
+
name: "deviceId",
|
|
93951
|
+
form: "single",
|
|
93952
|
+
optional: false
|
|
93953
|
+
}],
|
|
93954
|
+
"mediaPlayer.turnOn": [{
|
|
93955
|
+
name: "deviceId",
|
|
93956
|
+
form: "single",
|
|
93957
|
+
optional: false
|
|
93958
|
+
}],
|
|
93959
|
+
"mediaPlayer.volumeDown": [{
|
|
93960
|
+
name: "deviceId",
|
|
93961
|
+
form: "single",
|
|
93962
|
+
optional: false
|
|
93963
|
+
}],
|
|
93964
|
+
"mediaPlayer.volumeUp": [{
|
|
93965
|
+
name: "deviceId",
|
|
93966
|
+
form: "single",
|
|
93967
|
+
optional: false
|
|
93968
|
+
}],
|
|
92752
93969
|
"meter.getStatus": [{
|
|
92753
93970
|
name: "deviceId",
|
|
92754
93971
|
form: "single",
|
|
@@ -93058,21 +94275,6 @@ Object.freeze({
|
|
|
93058
94275
|
form: "single",
|
|
93059
94276
|
optional: false
|
|
93060
94277
|
}],
|
|
93061
|
-
"petFountain.setPumpInterval": [{
|
|
93062
|
-
name: "deviceId",
|
|
93063
|
-
form: "single",
|
|
93064
|
-
optional: false
|
|
93065
|
-
}],
|
|
93066
|
-
"petFountain.setPumpOffMinutes": [{
|
|
93067
|
-
name: "deviceId",
|
|
93068
|
-
form: "single",
|
|
93069
|
-
optional: false
|
|
93070
|
-
}],
|
|
93071
|
-
"petFountain.setPumpOnMinutes": [{
|
|
93072
|
-
name: "deviceId",
|
|
93073
|
-
form: "single",
|
|
93074
|
-
optional: false
|
|
93075
|
-
}],
|
|
93076
94278
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
93077
94279
|
name: "deviceId",
|
|
93078
94280
|
form: "single",
|
|
@@ -93708,6 +94910,26 @@ Object.freeze({
|
|
|
93708
94910
|
form: "single",
|
|
93709
94911
|
optional: false
|
|
93710
94912
|
}],
|
|
94913
|
+
"pumpCycle.getStatus": [{
|
|
94914
|
+
name: "deviceId",
|
|
94915
|
+
form: "single",
|
|
94916
|
+
optional: false
|
|
94917
|
+
}],
|
|
94918
|
+
"pumpCycle.setEnabled": [{
|
|
94919
|
+
name: "deviceId",
|
|
94920
|
+
form: "single",
|
|
94921
|
+
optional: false
|
|
94922
|
+
}],
|
|
94923
|
+
"pumpCycle.setOffMinutes": [{
|
|
94924
|
+
name: "deviceId",
|
|
94925
|
+
form: "single",
|
|
94926
|
+
optional: false
|
|
94927
|
+
}],
|
|
94928
|
+
"pumpCycle.setOnMinutes": [{
|
|
94929
|
+
name: "deviceId",
|
|
94930
|
+
form: "single",
|
|
94931
|
+
optional: false
|
|
94932
|
+
}],
|
|
93711
94933
|
"reboot.reboot": [{
|
|
93712
94934
|
name: "deviceId",
|
|
93713
94935
|
form: "single",
|
|
@@ -93909,6 +95131,11 @@ Object.freeze({
|
|
|
93909
95131
|
form: "single",
|
|
93910
95132
|
optional: true
|
|
93911
95133
|
}],
|
|
95134
|
+
"remote.getAssumedStatus": [{
|
|
95135
|
+
name: "deviceId",
|
|
95136
|
+
form: "single",
|
|
95137
|
+
optional: false
|
|
95138
|
+
}],
|
|
93912
95139
|
"remote.getConfig": [{
|
|
93913
95140
|
name: "deviceId",
|
|
93914
95141
|
form: "single",
|
|
@@ -94456,99 +95683,6 @@ Object.freeze({
|
|
|
94456
95683
|
"network-access": "ingress",
|
|
94457
95684
|
"smtp-provider": "email"
|
|
94458
95685
|
});
|
|
94459
|
-
/**
|
|
94460
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
94461
|
-
* `z.infer`, they do not restate the shape.
|
|
94462
|
-
*/
|
|
94463
|
-
var OverrideSourceSchema = object({
|
|
94464
|
-
addonId: string$2().min(1),
|
|
94465
|
-
stableId: string$2().min(1),
|
|
94466
|
-
cap: string$2().min(1),
|
|
94467
|
-
fieldPath: string$2().min(1)
|
|
94468
|
-
});
|
|
94469
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
94470
|
-
object({ kind: literal("identity") }),
|
|
94471
|
-
object({ kind: literal("invert") }),
|
|
94472
|
-
object({
|
|
94473
|
-
kind: literal("threshold"),
|
|
94474
|
-
comparison: _enum(["gte", "lte"]),
|
|
94475
|
-
threshold: number(),
|
|
94476
|
-
startDwellMs: number().nonnegative(),
|
|
94477
|
-
stopDwellMs: number().nonnegative()
|
|
94478
|
-
}),
|
|
94479
|
-
object({
|
|
94480
|
-
kind: literal("linear"),
|
|
94481
|
-
scale: number(),
|
|
94482
|
-
offset: number(),
|
|
94483
|
-
clampMin: number().optional(),
|
|
94484
|
-
clampMax: number().optional()
|
|
94485
|
-
}),
|
|
94486
|
-
object({
|
|
94487
|
-
kind: literal("enum-map"),
|
|
94488
|
-
mapping: record(string$2(), string$2().nullable())
|
|
94489
|
-
}),
|
|
94490
|
-
object({ kind: literal("number") })
|
|
94491
|
-
]);
|
|
94492
|
-
var ScalarOverrideDraftSchema = object({
|
|
94493
|
-
kind: literal("scalar"),
|
|
94494
|
-
cap: string$2().min(1),
|
|
94495
|
-
targetId: string$2().min(1),
|
|
94496
|
-
source: OverrideSourceSchema,
|
|
94497
|
-
transform: CustomizeTransformDraftSchema
|
|
94498
|
-
});
|
|
94499
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
94500
|
-
kind: literal("keyedArray"),
|
|
94501
|
-
cap: string$2().min(1),
|
|
94502
|
-
targetId: string$2().min(1),
|
|
94503
|
-
key: string$2().min(1),
|
|
94504
|
-
label: string$2().min(1),
|
|
94505
|
-
fieldPath: string$2().min(1),
|
|
94506
|
-
source: OverrideSourceSchema,
|
|
94507
|
-
transform: CustomizeTransformDraftSchema
|
|
94508
|
-
});
|
|
94509
|
-
var PulseOverrideDraftSchema = object({
|
|
94510
|
-
kind: literal("pulse"),
|
|
94511
|
-
cap: string$2().min(1),
|
|
94512
|
-
targetId: string$2().min(1),
|
|
94513
|
-
enabled: boolean(),
|
|
94514
|
-
debounceMs: number().nonnegative(),
|
|
94515
|
-
edges: array(_enum([
|
|
94516
|
-
"rising",
|
|
94517
|
-
"button",
|
|
94518
|
-
"native-event"
|
|
94519
|
-
])).min(1).optional(),
|
|
94520
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
94521
|
-
});
|
|
94522
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
94523
|
-
"in-addition",
|
|
94524
|
-
"confirm",
|
|
94525
|
-
"replace"
|
|
94526
|
-
]).optional() });
|
|
94527
|
-
discriminatedUnion("kind", [
|
|
94528
|
-
ScalarOverrideDraftSchema,
|
|
94529
|
-
KeyedArrayOverrideDraftSchema,
|
|
94530
|
-
PulseOverrideDraftSchema,
|
|
94531
|
-
object({
|
|
94532
|
-
kind: literal("span"),
|
|
94533
|
-
cap: string$2().min(1),
|
|
94534
|
-
targetId: string$2().min(1),
|
|
94535
|
-
timeoutMs: number().nonnegative(),
|
|
94536
|
-
cooldownMs: number().nonnegative().optional(),
|
|
94537
|
-
sources: array(SpanSourceDraftSchema)
|
|
94538
|
-
})
|
|
94539
|
-
]);
|
|
94540
|
-
object({
|
|
94541
|
-
targetId: string$2().min(1),
|
|
94542
|
-
configured: boolean(),
|
|
94543
|
-
source: _enum(["native", "assigned"]),
|
|
94544
|
-
state: _enum([
|
|
94545
|
-
"active",
|
|
94546
|
-
"stale",
|
|
94547
|
-
"released",
|
|
94548
|
-
"unavailable"
|
|
94549
|
-
]),
|
|
94550
|
-
reason: string$2().nullable()
|
|
94551
|
-
});
|
|
94552
95686
|
var G711_SCALE_CORRECTION_DB = {
|
|
94553
95687
|
PCMU: 20 * Math.log10(4),
|
|
94554
95688
|
PCMA: 20 * Math.log10(8)
|
|
@@ -95164,6 +96298,7 @@ var STATE_SUMMARY_VARS = [
|
|
|
95164
96298
|
[...RULE_TEXT],
|
|
95165
96299
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95166
96300
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
96301
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
95167
96302
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95168
96303
|
([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
|
|
95169
96304
|
([...RULE_TEXT], [...CONSUMABLE]),
|
|
@@ -206241,13 +207376,16 @@ var MatterBrokerRegistry = class {
|
|
|
206241
207376
|
}
|
|
206242
207377
|
async restore(entries) {
|
|
206243
207378
|
for (const entry of entries) this.#seedCounter(entry.id);
|
|
206244
|
-
for (const entry of entries)
|
|
206245
|
-
|
|
206246
|
-
|
|
206247
|
-
|
|
206248
|
-
|
|
206249
|
-
|
|
206250
|
-
|
|
207379
|
+
for (const entry of entries) {
|
|
207380
|
+
if (entry.enabled === false) continue;
|
|
207381
|
+
try {
|
|
207382
|
+
await this.#startManager(entry);
|
|
207383
|
+
} catch (err) {
|
|
207384
|
+
this.#logger.warn("MatterBrokerRegistry: failed to restore manager", {
|
|
207385
|
+
tags: { brokerId: entry.id },
|
|
207386
|
+
meta: { error: errMsg(err) }
|
|
207387
|
+
});
|
|
207388
|
+
}
|
|
206251
207389
|
}
|
|
206252
207390
|
}
|
|
206253
207391
|
async shutdown() {
|
|
@@ -206278,7 +207416,8 @@ var MatterBrokerRegistry = class {
|
|
|
206278
207416
|
this.#onAttributeReport = cb;
|
|
206279
207417
|
}
|
|
206280
207418
|
async createEntry(name, connection, opts = {}) {
|
|
206281
|
-
const id = this.#allocateId();
|
|
207419
|
+
const id = opts.id ?? this.#allocateId();
|
|
207420
|
+
if (opts.id !== void 0) this.#seedCounter(opts.id);
|
|
206282
207421
|
const entry = {
|
|
206283
207422
|
id,
|
|
206284
207423
|
name,
|
|
@@ -206291,7 +207430,7 @@ var MatterBrokerRegistry = class {
|
|
|
206291
207430
|
}
|
|
206292
207431
|
async removeEntry(id) {
|
|
206293
207432
|
const mgr = this.#managers.get(id);
|
|
206294
|
-
if (!mgr)
|
|
207433
|
+
if (!mgr) return;
|
|
206295
207434
|
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
206296
207435
|
this.#managers.delete(id);
|
|
206297
207436
|
try {
|
|
@@ -206373,6 +207512,10 @@ var MatterBrokerRegistry = class {
|
|
|
206373
207512
|
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
206374
207513
|
await mgr.start();
|
|
206375
207514
|
}
|
|
207515
|
+
/** The next free broker id, for a record minted before its manager exists. */
|
|
207516
|
+
allocateId() {
|
|
207517
|
+
return this.#allocateId();
|
|
207518
|
+
}
|
|
206376
207519
|
#allocateId() {
|
|
206377
207520
|
const id = `matter_${String(this.#nextId).padStart(3, "0")}`;
|
|
206378
207521
|
this.#nextId++;
|
|
@@ -206411,10 +207554,12 @@ var MatterBrokerEntrySchema = object({
|
|
|
206411
207554
|
id: string$2().min(1),
|
|
206412
207555
|
name: string$2().min(1),
|
|
206413
207556
|
connection: matterConnectionSchema,
|
|
206414
|
-
integrationId: string$2().optional()
|
|
207557
|
+
integrationId: string$2().optional(),
|
|
207558
|
+
/** Absent means on; the instances page switches a fabric off without deleting it. */
|
|
207559
|
+
enabled: boolean().optional()
|
|
206415
207560
|
});
|
|
206416
|
-
/** Top-level addon config —
|
|
206417
|
-
var matterAddonConfigSchema = object({
|
|
207561
|
+
/** Top-level addon config — the fabrics are records, so nothing is left here. */
|
|
207562
|
+
var matterAddonConfigSchema = object({});
|
|
206418
207563
|
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
206419
207564
|
function toRecord(value) {
|
|
206420
207565
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value));
|
|
@@ -207496,19 +208641,146 @@ var MatterNodeDevice = class extends BaseDevice {
|
|
|
207496
208641
|
var matterBrokersEntryKind = defineEntryKind("brokers", {
|
|
207497
208642
|
title: "Matter brokers",
|
|
207498
208643
|
schema: entryBody({
|
|
207499
|
-
|
|
207500
|
-
integrationId: string$2().optional()
|
|
208644
|
+
...matterConnectionSchema.shape,
|
|
208645
|
+
integrationId: string$2().optional(),
|
|
208646
|
+
enabled: boolean().optional()
|
|
207501
208647
|
}),
|
|
207502
|
-
writeTrigger: "an operator adds, edits or removes a Matter fabric broker"
|
|
208648
|
+
writeTrigger: "an operator adds, edits or removes a Matter fabric broker",
|
|
208649
|
+
retiredSecretPaths: ["connection.threadDataset", "connection.wifiPassword"]
|
|
207503
208650
|
});
|
|
207504
208651
|
//#endregion
|
|
208652
|
+
//#region src/matter-instances.ts
|
|
208653
|
+
/**
|
|
208654
|
+
* The Matter fabrics as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
208655
|
+
* record, over the SAME records and ids (`matter_NNN`) the `broker` cap serves. The body is flat.
|
|
208656
|
+
* A fabric needs no sign-in; commissioning a device stays on the adoption flow, behind the fabric.
|
|
208657
|
+
*/
|
|
208658
|
+
var MATTER_INSTANCE_KIND = "matter";
|
|
208659
|
+
function statusOf(info) {
|
|
208660
|
+
if (info === null) return {
|
|
208661
|
+
state: "unknown",
|
|
208662
|
+
label: "Not started"
|
|
208663
|
+
};
|
|
208664
|
+
if (info.status === "connected") return {
|
|
208665
|
+
state: "ok",
|
|
208666
|
+
label: "Running"
|
|
208667
|
+
};
|
|
208668
|
+
if (info.status === "connecting") return {
|
|
208669
|
+
state: "unknown",
|
|
208670
|
+
label: "Starting"
|
|
208671
|
+
};
|
|
208672
|
+
return {
|
|
208673
|
+
state: "error",
|
|
208674
|
+
label: info.status,
|
|
208675
|
+
...info.error !== null ? { detail: info.error } : {}
|
|
208676
|
+
};
|
|
208677
|
+
}
|
|
208678
|
+
function buildMatterInstances(records, deps) {
|
|
208679
|
+
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208680
|
+
description: {
|
|
208681
|
+
role: "both",
|
|
208682
|
+
instanceMode: "multiple",
|
|
208683
|
+
kind: "connection",
|
|
208684
|
+
canTest: true,
|
|
208685
|
+
kinds: [{
|
|
208686
|
+
id: MATTER_INSTANCE_KIND,
|
|
208687
|
+
label: "Matter"
|
|
208688
|
+
}],
|
|
208689
|
+
allowRestoreId: true
|
|
208690
|
+
},
|
|
208691
|
+
formSchema: () => buildConnectionFormSchema(),
|
|
208692
|
+
initialBody: (name) => ({
|
|
208693
|
+
name,
|
|
208694
|
+
threadDataset: "",
|
|
208695
|
+
wifiSsid: "",
|
|
208696
|
+
wifiPassword: "",
|
|
208697
|
+
reconcileIntervalMs: 3e5
|
|
208698
|
+
}),
|
|
208699
|
+
mintId: () => deps.mintId(),
|
|
208700
|
+
kindOf: () => MATTER_INSTANCE_KIND,
|
|
208701
|
+
health: (_body, id) => {
|
|
208702
|
+
const info = deps.infoOf(id);
|
|
208703
|
+
return {
|
|
208704
|
+
status: statusOf(info),
|
|
208705
|
+
...info?.lastCheckedAt != null ? { lastCheckedAt: info.lastCheckedAt } : {}
|
|
208706
|
+
};
|
|
208707
|
+
},
|
|
208708
|
+
configured: () => true,
|
|
208709
|
+
onChanged: deps.onChanged,
|
|
208710
|
+
test: async ({ id }) => {
|
|
208711
|
+
const info = id === null ? null : deps.infoOf(id);
|
|
208712
|
+
if (info === null) return {
|
|
208713
|
+
success: false,
|
|
208714
|
+
status: "inconclusive",
|
|
208715
|
+
error: "Not started: save the fabric first"
|
|
208716
|
+
};
|
|
208717
|
+
return info.status === "connected" ? {
|
|
208718
|
+
success: true,
|
|
208719
|
+
status: "validated"
|
|
208720
|
+
} : {
|
|
208721
|
+
success: false,
|
|
208722
|
+
status: "rejected",
|
|
208723
|
+
error: info.error ?? `status: ${info.status}`
|
|
208724
|
+
};
|
|
208725
|
+
}
|
|
208726
|
+
});
|
|
208727
|
+
}
|
|
208728
|
+
//#endregion
|
|
208729
|
+
//#region src/migrations/flatten-broker-records.ts
|
|
208730
|
+
/**
|
|
208731
|
+
* The Matter broker records keep their fabric settings flat (planning#408 P3): the nested
|
|
208732
|
+
* `connection` object (`threadDataset`, `wifiSsid`, `wifiPassword`, `reconcileIntervalMs`) becomes
|
|
208733
|
+
* top-level fields under the SAME record id (`matter_NNN`), so the devices that carry the id keep
|
|
208734
|
+
* their fabric. The work is `flattenNestedRecords`: every secret is read before anything is
|
|
208735
|
+
* written, read back from its new ref before the nested object goes, and the old refs are
|
|
208736
|
+
* deleted last.
|
|
208737
|
+
*/
|
|
208738
|
+
var FLATTEN_BROKER_RECORDS_ID = "matter-broker/2026-11-04-flatten-broker-records";
|
|
208739
|
+
async function flattenBrokerRecords(ports) {
|
|
208740
|
+
return flattenNestedRecords(ports, {
|
|
208741
|
+
legacyKey: "connection",
|
|
208742
|
+
flatSecretKeys: ["threadDataset", "wifiPassword"],
|
|
208743
|
+
toFlat: (legacy) => Object.fromEntries([
|
|
208744
|
+
"threadDataset",
|
|
208745
|
+
"wifiSsid",
|
|
208746
|
+
"wifiPassword",
|
|
208747
|
+
"reconcileIntervalMs"
|
|
208748
|
+
].filter((key) => legacy[key] !== void 0).map((key) => [key, legacy[key]]))
|
|
208749
|
+
});
|
|
208750
|
+
}
|
|
208751
|
+
//#endregion
|
|
208752
|
+
//#region src/migrations/index.ts
|
|
208753
|
+
/** The `matter-broker` catalogue (D987): offered to the engine when the addon initialises. */
|
|
208754
|
+
var ADDON_MIGRATIONS = [{
|
|
208755
|
+
meta: {
|
|
208756
|
+
id: FLATTEN_BROKER_RECORDS_ID,
|
|
208757
|
+
owner: "matter-broker",
|
|
208758
|
+
level: "addon-state",
|
|
208759
|
+
seq: 1,
|
|
208760
|
+
dependsOn: [],
|
|
208761
|
+
store: "settings",
|
|
208762
|
+
scope: "cluster",
|
|
208763
|
+
onFailure: "block-owner",
|
|
208764
|
+
bodyKind: "procedure",
|
|
208765
|
+
description: "Move each Matter fabric from a nested connection object to flat top-level fields under the same id, reading every secret back first",
|
|
208766
|
+
fixtureMark: "the integration link and the Wi-Fi SSID of each fabric"
|
|
208767
|
+
},
|
|
208768
|
+
procedure: flattenBrokerRecords
|
|
208769
|
+
}];
|
|
208770
|
+
//#endregion
|
|
207505
208771
|
//#region src/matter-broker-store.ts
|
|
207506
208772
|
function rowToEntry(id, data) {
|
|
207507
208773
|
const parsed = MatterBrokerEntrySchema.safeParse({
|
|
207508
208774
|
id,
|
|
207509
208775
|
name: data.name,
|
|
207510
|
-
connection:
|
|
207511
|
-
|
|
208776
|
+
connection: {
|
|
208777
|
+
threadDataset: data.threadDataset,
|
|
208778
|
+
wifiSsid: data.wifiSsid,
|
|
208779
|
+
wifiPassword: data.wifiPassword,
|
|
208780
|
+
reconcileIntervalMs: data.reconcileIntervalMs
|
|
208781
|
+
},
|
|
208782
|
+
integrationId: data.integrationId,
|
|
208783
|
+
enabled: data.enabled
|
|
207512
208784
|
});
|
|
207513
208785
|
return parsed.success ? parsed.data : null;
|
|
207514
208786
|
}
|
|
@@ -207536,16 +208808,20 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
207536
208808
|
}
|
|
207537
208809
|
return out;
|
|
207538
208810
|
}
|
|
208811
|
+
async getById(id) {
|
|
208812
|
+
return (await this.listResolved()).find((entry) => entry.id === id) ?? null;
|
|
208813
|
+
}
|
|
207539
208814
|
async replaceAll(entries) {
|
|
207540
208815
|
const page = await this.kind.query({ limit: 1e3 });
|
|
207541
208816
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
207542
208817
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
207543
208818
|
for (const entry of entries) {
|
|
207544
|
-
const { id, name, connection, integrationId } = entry;
|
|
208819
|
+
const { id, name, connection, integrationId, enabled } = entry;
|
|
207545
208820
|
const body = {
|
|
207546
208821
|
name,
|
|
207547
|
-
connection,
|
|
207548
|
-
...integrationId === void 0 ? {} : { integrationId }
|
|
208822
|
+
...connection,
|
|
208823
|
+
...integrationId === void 0 ? {} : { integrationId },
|
|
208824
|
+
...enabled === void 0 ? {} : { enabled }
|
|
207549
208825
|
};
|
|
207550
208826
|
const parsed = matterBrokersEntryKind.schema.safeParse(body);
|
|
207551
208827
|
if (!parsed.success) throw new Error(`invalid Matter broker '${id}': ${parsed.error.message}`);
|
|
@@ -207559,7 +208835,7 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
207559
208835
|
//#endregion
|
|
207560
208836
|
//#region src/addon.ts
|
|
207561
208837
|
/** Default config — a fresh install starts with no fabric. */
|
|
207562
|
-
var DEFAULTS = {
|
|
208838
|
+
var DEFAULTS = {};
|
|
207563
208839
|
/**
|
|
207564
208840
|
* Matter broker addon (hub-only). Owns a Matter fabric per broker entry via an
|
|
207565
208841
|
* embedded matter.js controller (behind the {@link matterCommands} /
|
|
@@ -207581,6 +208857,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207581
208857
|
deviceClasses = { [DeviceType.Container]: MatterNodeDevice };
|
|
207582
208858
|
registry = null;
|
|
207583
208859
|
brokerStore = null;
|
|
208860
|
+
instances = null;
|
|
207584
208861
|
activeBrokers = [];
|
|
207585
208862
|
constructor() {
|
|
207586
208863
|
super({ ...DEFAULTS });
|
|
@@ -207591,6 +208868,13 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207591
208868
|
if (records === void 0) throw new Error("matter-broker: ctx.records is required (hub-only addon)");
|
|
207592
208869
|
await records.declare({ brokers: matterBrokersEntryKind });
|
|
207593
208870
|
this.brokerStore = MatterBrokerStore.of(records);
|
|
208871
|
+
this.instances = buildMatterInstances(records, {
|
|
208872
|
+
mintId: () => this.requireRegistry().allocateId(),
|
|
208873
|
+
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
208874
|
+
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
208875
|
+
});
|
|
208876
|
+
if (this.ctx.secrets === void 0) throw new Error("matter-broker: ctx.secrets is required");
|
|
208877
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, flattenPortsOver(records.kind(matterBrokersEntryKind))));
|
|
207594
208878
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
207595
208879
|
this.registry = new MatterBrokerRegistry(this.ctx.logger, {
|
|
207596
208880
|
dataDir: this.ctx.dataDir,
|
|
@@ -207619,6 +208903,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207619
208903
|
capability: brokerCapability,
|
|
207620
208904
|
provider: this.buildBrokerProvider()
|
|
207621
208905
|
},
|
|
208906
|
+
{
|
|
208907
|
+
capability: integrationInstancesCapability,
|
|
208908
|
+
provider: this.requireInstances().toProvider()
|
|
208909
|
+
},
|
|
207622
208910
|
{
|
|
207623
208911
|
capability: deviceAdoptionCapability,
|
|
207624
208912
|
provider: this.buildAdoptionProvider()
|
|
@@ -207626,21 +208914,50 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207626
208914
|
];
|
|
207627
208915
|
}
|
|
207628
208916
|
async loadPersistedBrokers() {
|
|
207629
|
-
|
|
207630
|
-
|
|
207631
|
-
|
|
207632
|
-
|
|
208917
|
+
return this.requireBrokerStore().listResolved();
|
|
208918
|
+
}
|
|
208919
|
+
requireBrokerStore() {
|
|
208920
|
+
if (this.brokerStore === null) throw new Error("matter-broker: broker store not ready");
|
|
208921
|
+
return this.brokerStore;
|
|
208922
|
+
}
|
|
208923
|
+
requireInstances() {
|
|
208924
|
+
if (this.instances === null) throw new Error("matter-broker: instances not ready");
|
|
208925
|
+
return this.instances;
|
|
208926
|
+
}
|
|
208927
|
+
/**
|
|
208928
|
+
* Bring the runtime in line with one record after the instances page changed it: a removed
|
|
208929
|
+
* record stops its controller and cascades its devices, an off record only stops the
|
|
208930
|
+
* controller, a live one re-applies its settings or starts under the record's own id.
|
|
208931
|
+
*/
|
|
208932
|
+
async syncBrokerRuntime(id) {
|
|
208933
|
+
const reg = this.requireRegistry();
|
|
208934
|
+
const entry = await this.requireBrokerStore().getById(id);
|
|
208935
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
208936
|
+
if (entry === null) {
|
|
208937
|
+
await reg.removeEntry(id);
|
|
208938
|
+
await this.cascadeRemoveDevicesForBroker(id);
|
|
208939
|
+
return;
|
|
208940
|
+
}
|
|
208941
|
+
if (entry.enabled === false) {
|
|
208942
|
+
await reg.removeEntry(id);
|
|
208943
|
+
return;
|
|
208944
|
+
}
|
|
208945
|
+
try {
|
|
208946
|
+
if (reg.get(id) !== null) await reg.updateEntry(id, entry.connection);
|
|
208947
|
+
else await reg.createEntry(entry.name, entry.connection, {
|
|
208948
|
+
id,
|
|
208949
|
+
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
208950
|
+
});
|
|
208951
|
+
} catch (err) {
|
|
208952
|
+
this.ctx.logger.warn("matter instance: could not start the fabric", { meta: {
|
|
208953
|
+
brokerId: id,
|
|
208954
|
+
error: errMsg(err)
|
|
208955
|
+
} });
|
|
207633
208956
|
}
|
|
207634
|
-
return this.config.brokers;
|
|
207635
208957
|
}
|
|
207636
208958
|
async persistBrokerEntries(entries) {
|
|
207637
208959
|
this.activeBrokers = [...entries];
|
|
207638
|
-
|
|
207639
|
-
if (store !== null) {
|
|
207640
|
-
await store.replaceAll(entries);
|
|
207641
|
-
return;
|
|
207642
|
-
}
|
|
207643
|
-
await this.updateGlobalSettings({ brokers: [...entries] });
|
|
208960
|
+
await this.requireBrokerStore().replaceAll(entries);
|
|
207644
208961
|
}
|
|
207645
208962
|
async onConfigChanged() {
|
|
207646
208963
|
const reg = this.registry;
|
|
@@ -207658,6 +208975,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207658
208975
|
} });
|
|
207659
208976
|
}
|
|
207660
208977
|
for (const entry of persisted) {
|
|
208978
|
+
if (entry.enabled === false) continue;
|
|
207661
208979
|
if (entry.id && liveIds.has(entry.id)) {
|
|
207662
208980
|
if (sameConnection(entry.connection, reg.getConnection(entry.id))) continue;
|
|
207663
208981
|
try {
|