@camstack/addon-matter-broker 0.2.154 → 0.2.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/addon.js +1907 -678
- package/dist/addon.mjs +1907 -678
- package/package.json +6 -2
package/dist/addon.js
CHANGED
|
@@ -799,7 +799,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
799
799
|
return EventCategory;
|
|
800
800
|
}({});
|
|
801
801
|
//#endregion
|
|
802
|
-
//#region ../types/dist/device-type-
|
|
802
|
+
//#region ../types/dist/device-type-D1fH5kaC.mjs
|
|
803
803
|
/**
|
|
804
804
|
import { errMsg } from '@camstack/types'
|
|
805
805
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -1030,6 +1030,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
|
1030
1030
|
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
1031
1031
|
/** Media player exposes stop distinct from pause. */
|
|
1032
1032
|
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
1033
|
+
/** Media player exposes volume up / down in steps (an infrared TV has no level). */
|
|
1034
|
+
DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
|
|
1035
|
+
/** Media player exposes channel up / down in steps. */
|
|
1036
|
+
DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
|
|
1037
|
+
/** Media player exposes power on / off. */
|
|
1038
|
+
DeviceFeature["MediaPlayerPower"] = "media-player-power";
|
|
1033
1039
|
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
1034
1040
|
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
1035
1041
|
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
@@ -5989,7 +5995,7 @@ var ZodIssueCode = {
|
|
|
5989
5995
|
var ZodFirstPartyTypeKind;
|
|
5990
5996
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5991
5997
|
//#endregion
|
|
5992
|
-
//#region ../types/dist/sleep-
|
|
5998
|
+
//#region ../types/dist/sleep-BkzQdpzF.mjs
|
|
5993
5999
|
/**
|
|
5994
6000
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5995
6001
|
* window to float samples (D455).
|
|
@@ -6068,6 +6074,22 @@ function buildAlawTable() {
|
|
|
6068
6074
|
}
|
|
6069
6075
|
buildUlawTable();
|
|
6070
6076
|
buildAlawTable();
|
|
6077
|
+
/**
|
|
6078
|
+
* An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
|
|
6079
|
+
* so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
|
|
6080
|
+
* co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
|
|
6081
|
+
* bundles its own copy of this module, so the class the runner sees is not the one that threw.
|
|
6082
|
+
*/
|
|
6083
|
+
var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
|
|
6084
|
+
var AddonMigrationsBlockedError = class extends Error {
|
|
6085
|
+
/** The ledger ids that did not apply and block the owner. */
|
|
6086
|
+
migrationIds;
|
|
6087
|
+
constructor(migrationIds) {
|
|
6088
|
+
super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
|
|
6089
|
+
this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
|
|
6090
|
+
this.migrationIds = migrationIds;
|
|
6091
|
+
}
|
|
6092
|
+
};
|
|
6071
6093
|
/** Is this field's stored value a credential? */
|
|
6072
6094
|
function isSecretConfigField(field) {
|
|
6073
6095
|
if (field.type === "password") return true;
|
|
@@ -6121,9 +6143,16 @@ function isSecretMarker(value) {
|
|
|
6121
6143
|
const ref = value["$secret"];
|
|
6122
6144
|
return typeof ref === "string" && ref.length > 0;
|
|
6123
6145
|
}
|
|
6146
|
+
/** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
|
|
6147
|
+
function isSecretMarkerLike(value) {
|
|
6148
|
+
return isRecord$7(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
|
|
6149
|
+
}
|
|
6124
6150
|
function isSecretClear(value) {
|
|
6125
6151
|
return isRecord$7(value) && value["$secretClear"] === true;
|
|
6126
6152
|
}
|
|
6153
|
+
function isSecretFieldView(value) {
|
|
6154
|
+
return isRecord$7(value) && value["set"] === true && isSecretMarkerLike(value);
|
|
6155
|
+
}
|
|
6127
6156
|
/**
|
|
6128
6157
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
6129
6158
|
* plaintext becomes "set (inline)", nothing becomes `null`. A value never
|
|
@@ -6505,6 +6534,53 @@ function emitReadiness(bus, params) {
|
|
|
6505
6534
|
ts
|
|
6506
6535
|
}));
|
|
6507
6536
|
}
|
|
6537
|
+
/** Why a catalogue entry is not a runnable body of its declared kind. */
|
|
6538
|
+
var AddonMigrationDefinitionError = class extends Error {
|
|
6539
|
+
id;
|
|
6540
|
+
constructor(id, message) {
|
|
6541
|
+
super(`${id}: ${message}`);
|
|
6542
|
+
this.id = id;
|
|
6543
|
+
this.name = "AddonMigrationDefinitionError";
|
|
6544
|
+
}
|
|
6545
|
+
};
|
|
6546
|
+
/**
|
|
6547
|
+
* Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
|
|
6548
|
+
* an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
|
|
6549
|
+
* body). A throwing body never escapes `execute`: it is reported as a failed result.
|
|
6550
|
+
*/
|
|
6551
|
+
function defineAddonMigrations(catalogue, ports) {
|
|
6552
|
+
return catalogue.map((entry) => {
|
|
6553
|
+
const { meta } = entry;
|
|
6554
|
+
if ("plan" in entry) {
|
|
6555
|
+
if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
|
|
6556
|
+
return {
|
|
6557
|
+
meta,
|
|
6558
|
+
execute: () => guarded(async () => ({
|
|
6559
|
+
kind: "plan",
|
|
6560
|
+
plan: await entry.plan(ports)
|
|
6561
|
+
}))
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
6564
|
+
if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
|
|
6565
|
+
return {
|
|
6566
|
+
meta,
|
|
6567
|
+
execute: () => guarded(async () => ({
|
|
6568
|
+
kind: "procedure",
|
|
6569
|
+
result: await entry.procedure(ports)
|
|
6570
|
+
}))
|
|
6571
|
+
};
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
async function guarded(work) {
|
|
6575
|
+
try {
|
|
6576
|
+
return await work();
|
|
6577
|
+
} catch (err) {
|
|
6578
|
+
return {
|
|
6579
|
+
kind: "failed",
|
|
6580
|
+
error: errMsg(err)
|
|
6581
|
+
};
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6508
6584
|
var NOT_RUN = {
|
|
6509
6585
|
declared: false,
|
|
6510
6586
|
snapshot: null,
|
|
@@ -6726,11 +6802,36 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6726
6802
|
}
|
|
6727
6803
|
return out;
|
|
6728
6804
|
}
|
|
6805
|
+
/**
|
|
6806
|
+
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6807
|
+
*
|
|
6808
|
+
* A ref is DETERMINISTIC from where the value lives — the owner addon, the
|
|
6809
|
+
* scope (global, one node, one device, one integration, one row of a business
|
|
6810
|
+
* collection) and the field path inside that scope — so saving the same field
|
|
6811
|
+
* twice updates the same ref, and the ref itself says which settings blob
|
|
6812
|
+
* points at it. It is also the AAD of the value's ciphertext: a ciphertext
|
|
6813
|
+
* copied onto another ref does not decrypt.
|
|
6814
|
+
*
|
|
6815
|
+
* Array items are addressed by ITEM ID, never by index
|
|
6816
|
+
* (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
|
|
6817
|
+
*
|
|
6818
|
+
* Pure and browser-safe: no node builtins, no I/O.
|
|
6819
|
+
*/
|
|
6820
|
+
/** The URI scheme of every keychain ref. */
|
|
6821
|
+
var SECRET_REF_SCHEME = "secret://";
|
|
6729
6822
|
var GLOBAL_SCOPE = { kind: "global" };
|
|
6730
6823
|
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6731
6824
|
function globalSecretScope() {
|
|
6732
6825
|
return GLOBAL_SCOPE;
|
|
6733
6826
|
}
|
|
6827
|
+
/**
|
|
6828
|
+
* An addon id as a ref owner. A hub in-process builtin's context id is
|
|
6829
|
+
* `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
|
|
6830
|
+
* addon running forked.
|
|
6831
|
+
*/
|
|
6832
|
+
function normalizeSecretOwner(addonId) {
|
|
6833
|
+
return addonId.startsWith("addon:") ? addonId.slice(6) : addonId;
|
|
6834
|
+
}
|
|
6734
6835
|
function encodeSegment(value) {
|
|
6735
6836
|
return encodeURIComponent(value);
|
|
6736
6837
|
}
|
|
@@ -6741,6 +6842,16 @@ function decodeSegment(value) {
|
|
|
6741
6842
|
return null;
|
|
6742
6843
|
}
|
|
6743
6844
|
}
|
|
6845
|
+
/** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
|
|
6846
|
+
function formatSecretScope(scope) {
|
|
6847
|
+
switch (scope.kind) {
|
|
6848
|
+
case "global": return "global";
|
|
6849
|
+
case "node": return `node/${encodeSegment(scope.nodeId)}`;
|
|
6850
|
+
case "device": return `device/${scope.deviceId}`;
|
|
6851
|
+
case "integration": return `integration/${encodeSegment(scope.integrationId)}`;
|
|
6852
|
+
case "row": return `row/${encodeSegment(scope.collection)}/${encodeSegment(scope.rowId)}`;
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6744
6855
|
/**
|
|
6745
6856
|
* The field path of a value inside an array item, addressed by the item's id:
|
|
6746
6857
|
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
@@ -6749,6 +6860,13 @@ function decodeSegment(value) {
|
|
|
6749
6860
|
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6750
6861
|
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6751
6862
|
}
|
|
6863
|
+
/** Build a ref. Throws on an empty owner or field path — both are bugs. */
|
|
6864
|
+
function buildSecretRef(parts) {
|
|
6865
|
+
const owner = normalizeSecretOwner(parts.owner);
|
|
6866
|
+
if (owner.length === 0) throw new Error("buildSecretRef: owner is empty");
|
|
6867
|
+
if (parts.fieldPath.length === 0) throw new Error("buildSecretRef: fieldPath is empty");
|
|
6868
|
+
return `${SECRET_REF_SCHEME}${encodeSegment(owner)}/${formatSecretScope(parts.scope)}/${parts.fieldPath}`;
|
|
6869
|
+
}
|
|
6752
6870
|
function parseScope(segments) {
|
|
6753
6871
|
switch (segments[0]) {
|
|
6754
6872
|
case "global": return {
|
|
@@ -7674,7 +7792,7 @@ var BaseAddon = class {
|
|
|
7674
7792
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7675
7793
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7676
7794
|
}, migrations, this.ctx.logger);
|
|
7677
|
-
if (report.blockOwner.length > 0) throw new
|
|
7795
|
+
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7678
7796
|
return report;
|
|
7679
7797
|
}
|
|
7680
7798
|
/**
|
|
@@ -8014,6 +8132,10 @@ var BaseAddon = class {
|
|
|
8014
8132
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8015
8133
|
const patch = {};
|
|
8016
8134
|
for (const key of keys) {
|
|
8135
|
+
if (tree.get(key)?.kind === "array") {
|
|
8136
|
+
patch[key] = [];
|
|
8137
|
+
continue;
|
|
8138
|
+
}
|
|
8017
8139
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8018
8140
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8019
8141
|
}
|
|
@@ -8702,6 +8824,8 @@ var RECORD_BATCH_MAX = 1e3;
|
|
|
8702
8824
|
var RECORD_QUERY_MAX = 1e3;
|
|
8703
8825
|
/** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
|
|
8704
8826
|
var RECORD_SETTINGS_MAX_ROWS = 5e4;
|
|
8827
|
+
/** The `Column`-free proof prefix: a ref collection for a kind's secret fields. */
|
|
8828
|
+
var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
8705
8829
|
/**
|
|
8706
8830
|
* Secret fields of a record, addressed the way a Zod schema declares them
|
|
8707
8831
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
@@ -8738,6 +8862,112 @@ function secretArrayPrefixes(path) {
|
|
|
8738
8862
|
}
|
|
8739
8863
|
return prefixes;
|
|
8740
8864
|
}
|
|
8865
|
+
function isRecord$4$1(value) {
|
|
8866
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8867
|
+
}
|
|
8868
|
+
function elementId(element, idKey, declared, prefix) {
|
|
8869
|
+
const id = isRecord$4$1(element) ? element[idKey] : void 0;
|
|
8870
|
+
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`);
|
|
8871
|
+
return id;
|
|
8872
|
+
}
|
|
8873
|
+
/**
|
|
8874
|
+
* Visit every secret leaf PRESENT in `data` (an absent branch has nothing to
|
|
8875
|
+
* visit). `replace` returns the new leaf value, or {@link REMOVE_LEAF}; the
|
|
8876
|
+
* result is a new body, `data` is untouched.
|
|
8877
|
+
*/
|
|
8878
|
+
var REMOVE_LEAF = Symbol("remove-leaf");
|
|
8879
|
+
function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arrayKeys, visit) {
|
|
8880
|
+
if (!isRecord$4$1(node)) return node;
|
|
8881
|
+
const segment = segments[index];
|
|
8882
|
+
if (segment === void 0) return node;
|
|
8883
|
+
if (!Object.prototype.hasOwnProperty.call(node, segment.key)) return node;
|
|
8884
|
+
const child = node[segment.key];
|
|
8885
|
+
const keys = [...walkedKeys, segment.key];
|
|
8886
|
+
const last = index === segments.length - 1;
|
|
8887
|
+
if (segment.each) {
|
|
8888
|
+
if (!Array.isArray(child)) return node;
|
|
8889
|
+
const prefix = keys.join(".");
|
|
8890
|
+
const idKey = arrayKeys[prefix];
|
|
8891
|
+
if (idKey === void 0) throw new InvalidRecordError(`"${declared}": no id key declared for the array "${prefix}"`);
|
|
8892
|
+
const mapped = child.map((element) => {
|
|
8893
|
+
const id = elementId(element, idKey, declared, prefix);
|
|
8894
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}[${encodeURIComponent(id)}]`;
|
|
8895
|
+
return last ? element : mapAt(element, segments, index + 1, keys, concrete, declared, arrayKeys, visit);
|
|
8896
|
+
});
|
|
8897
|
+
return {
|
|
8898
|
+
...node,
|
|
8899
|
+
[segment.key]: mapped
|
|
8900
|
+
};
|
|
8901
|
+
}
|
|
8902
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8903
|
+
if (last) {
|
|
8904
|
+
const replacement = visit({
|
|
8905
|
+
declared,
|
|
8906
|
+
concrete,
|
|
8907
|
+
value: child
|
|
8908
|
+
});
|
|
8909
|
+
if (replacement === REMOVE_LEAF) return Object.fromEntries(Object.entries(node).filter(([key]) => key !== segment.key));
|
|
8910
|
+
return {
|
|
8911
|
+
...node,
|
|
8912
|
+
[segment.key]: replacement
|
|
8913
|
+
};
|
|
8914
|
+
}
|
|
8915
|
+
return {
|
|
8916
|
+
...node,
|
|
8917
|
+
[segment.key]: mapAt(child, segments, index + 1, keys, concrete, declared, arrayKeys, visit)
|
|
8918
|
+
};
|
|
8919
|
+
}
|
|
8920
|
+
/** Replace/remove every present secret leaf; returns a NEW body. */
|
|
8921
|
+
function mapSecretLeaves(data, paths, arrayKeys, visit) {
|
|
8922
|
+
let current = data;
|
|
8923
|
+
for (const declared of paths) current = mapAt(current, parseSecretPath(declared), 0, [], "", declared, arrayKeys, visit);
|
|
8924
|
+
return current;
|
|
8925
|
+
}
|
|
8926
|
+
/**
|
|
8927
|
+
* The keychain collection name a kind's secrets live under. Prefixed so a
|
|
8928
|
+
* record's refs never collide with the refs a legacy collection of the same name
|
|
8929
|
+
* already holds during an adoption.
|
|
8930
|
+
*/
|
|
8931
|
+
function recordSecretCollection(kind) {
|
|
8932
|
+
return `${RECORD_SECRET_COLLECTION_PREFIX}${kind}`;
|
|
8933
|
+
}
|
|
8934
|
+
/** The ONE ref a row's secret field may carry: the engine and the facade both build it here. */
|
|
8935
|
+
function recordSecretRef(owner, kind, rowId, concretePath) {
|
|
8936
|
+
return buildSecretRef({
|
|
8937
|
+
owner,
|
|
8938
|
+
scope: {
|
|
8939
|
+
kind: "row",
|
|
8940
|
+
collection: recordSecretCollection(kind),
|
|
8941
|
+
rowId
|
|
8942
|
+
},
|
|
8943
|
+
fieldPath: concretePath
|
|
8944
|
+
});
|
|
8945
|
+
}
|
|
8946
|
+
/**
|
|
8947
|
+
* A stored secret, as an admin read shows it: a VIEW naming the row's own
|
|
8948
|
+
* keychain ref (`set: true`), never the value and no longer the bare redaction
|
|
8949
|
+
* token. The form hands it to the secret field, which reveals the value by ref
|
|
8950
|
+
* through the audited `keychain-admin.reveal`; sent back untouched it keeps the
|
|
8951
|
+
* stored secret (the records door reads a view as KEEP).
|
|
8952
|
+
*
|
|
8953
|
+
* ONE implementation for the entries service and for every addon that serves
|
|
8954
|
+
* its own instances (`RecordIntegrationInstances`).
|
|
8955
|
+
*/
|
|
8956
|
+
function isRecord$3$1(value) {
|
|
8957
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8958
|
+
}
|
|
8959
|
+
function secretViewsOf(owner, kind, id, decl, data) {
|
|
8960
|
+
if (decl.secretPaths.length === 0) return data;
|
|
8961
|
+
const shown = mapSecretLeaves(data, decl.secretPaths, decl.secretArrayKeys, (leaf) => {
|
|
8962
|
+
if (leaf.value !== "__camstack_redacted__") return leaf.value;
|
|
8963
|
+
return {
|
|
8964
|
+
$secret: recordSecretRef(owner, kind, id, leaf.concrete),
|
|
8965
|
+
v: 1,
|
|
8966
|
+
set: true
|
|
8967
|
+
};
|
|
8968
|
+
});
|
|
8969
|
+
return isRecord$3$1(shown) ? shown : data;
|
|
8970
|
+
}
|
|
8741
8971
|
/**
|
|
8742
8972
|
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
8743
8973
|
* and validates them (`ShareViewGrantSchema` builds its
|
|
@@ -8960,7 +9190,7 @@ function defineRecordKinds(specs) {
|
|
|
8960
9190
|
retention: spec.retention,
|
|
8961
9191
|
deviceRemoval: spec.deviceRemoval,
|
|
8962
9192
|
maxDataBytes: spec.maxDataBytes,
|
|
8963
|
-
secretPaths: [...secretPaths],
|
|
9193
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
8964
9194
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
8965
9195
|
transactFamily: spec.transactFamily,
|
|
8966
9196
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9005,6 +9235,7 @@ function defineEntryKind(name, spec) {
|
|
|
9005
9235
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9006
9236
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9007
9237
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9238
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9008
9239
|
entry: {
|
|
9009
9240
|
title: spec.title,
|
|
9010
9241
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -9669,6 +9900,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9669
9900
|
"backup",
|
|
9670
9901
|
"battery",
|
|
9671
9902
|
"binary",
|
|
9903
|
+
"board-key-source",
|
|
9672
9904
|
"brightness",
|
|
9673
9905
|
"broker",
|
|
9674
9906
|
"button",
|
|
@@ -9790,6 +10022,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9790
10022
|
"privacy-mask",
|
|
9791
10023
|
"ptz",
|
|
9792
10024
|
"ptz-autotrack",
|
|
10025
|
+
"pump-cycle",
|
|
9793
10026
|
"reboot",
|
|
9794
10027
|
"record-entries",
|
|
9795
10028
|
"recording",
|
|
@@ -11082,18 +11315,6 @@ Object.freeze({
|
|
|
11082
11315
|
"path": "pumpRunning",
|
|
11083
11316
|
"valueType": "boolean"
|
|
11084
11317
|
},
|
|
11085
|
-
{
|
|
11086
|
-
"path": "pumpIntervalEnabled",
|
|
11087
|
-
"valueType": "boolean"
|
|
11088
|
-
},
|
|
11089
|
-
{
|
|
11090
|
-
"path": "pumpOnTime",
|
|
11091
|
-
"valueType": "number"
|
|
11092
|
-
},
|
|
11093
|
-
{
|
|
11094
|
-
"path": "pumpOffTime",
|
|
11095
|
-
"valueType": "number"
|
|
11096
|
-
},
|
|
11097
11318
|
{
|
|
11098
11319
|
"path": "lastDrink",
|
|
11099
11320
|
"valueType": "number"
|
|
@@ -11217,6 +11438,20 @@ Object.freeze({
|
|
|
11217
11438
|
"valueType": "number"
|
|
11218
11439
|
}
|
|
11219
11440
|
],
|
|
11441
|
+
"pump-cycle": [
|
|
11442
|
+
{
|
|
11443
|
+
"path": "enabled",
|
|
11444
|
+
"valueType": "boolean"
|
|
11445
|
+
},
|
|
11446
|
+
{
|
|
11447
|
+
"path": "onTime",
|
|
11448
|
+
"valueType": "number"
|
|
11449
|
+
},
|
|
11450
|
+
{
|
|
11451
|
+
"path": "offTime",
|
|
11452
|
+
"valueType": "number"
|
|
11453
|
+
}
|
|
11454
|
+
],
|
|
11220
11455
|
"recording-archive": [
|
|
11221
11456
|
{
|
|
11222
11457
|
"path": "deviceId",
|
|
@@ -11666,7 +11901,7 @@ object({
|
|
|
11666
11901
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
11667
11902
|
});
|
|
11668
11903
|
//#endregion
|
|
11669
|
-
//#region ../types/dist/composition-
|
|
11904
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11670
11905
|
/**
|
|
11671
11906
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11672
11907
|
*
|
|
@@ -12429,38 +12664,6 @@ function validateExpressionSource(src) {
|
|
|
12429
12664
|
return null;
|
|
12430
12665
|
}
|
|
12431
12666
|
/**
|
|
12432
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12433
|
-
*
|
|
12434
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12435
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12436
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12437
|
-
*
|
|
12438
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12439
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12440
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12441
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12442
|
-
* can move later without moving the others.
|
|
12443
|
-
*/
|
|
12444
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12445
|
-
var APPLIANCE_KINDS = [
|
|
12446
|
-
"washer",
|
|
12447
|
-
"dryer",
|
|
12448
|
-
"dishwasher"
|
|
12449
|
-
];
|
|
12450
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12451
|
-
/**
|
|
12452
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12453
|
-
*
|
|
12454
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12455
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12456
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12457
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12458
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12459
|
-
* the smallest radiator.
|
|
12460
|
-
*/
|
|
12461
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12462
|
-
DeviceType.Heater;
|
|
12463
|
-
/**
|
|
12464
12667
|
* Statistics and integrals over a history read (D944).
|
|
12465
12668
|
*
|
|
12466
12669
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12604,34 +12807,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12604
12807
|
/** Actuator groups only. Readings leave this unset. */
|
|
12605
12808
|
active: ActiveCriterionSchema.optional()
|
|
12606
12809
|
}),
|
|
12607
|
-
object({
|
|
12608
|
-
kind: literal("appliance"),
|
|
12609
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12610
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12611
|
-
thresholdWatts: number().positive(),
|
|
12612
|
-
startDwellMs: number().int().nonnegative(),
|
|
12613
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12614
|
-
member: HelperMemberSchema,
|
|
12615
|
-
/**
|
|
12616
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12617
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12618
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12619
|
-
*/
|
|
12620
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12621
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12622
|
-
offSeconds: number().optional()
|
|
12623
|
-
}),
|
|
12624
|
-
object({
|
|
12625
|
-
kind: literal("heater"),
|
|
12626
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12627
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12628
|
-
plug: HelperMemberSchema,
|
|
12629
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12630
|
-
meter: HelperMemberSchema.optional(),
|
|
12631
|
-
thresholdWatts: number().positive(),
|
|
12632
|
-
startDwellMs: number().int().nonnegative(),
|
|
12633
|
-
stopDwellMs: number().int().nonnegative()
|
|
12634
|
-
}),
|
|
12635
12810
|
object({
|
|
12636
12811
|
kind: literal("statistic"),
|
|
12637
12812
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -12803,7 +12978,14 @@ var CompositionForwardCommandSchema = object({
|
|
|
12803
12978
|
method: string$2().min(1),
|
|
12804
12979
|
args: record(string$2().min(1), CompositionForwardArgSchema).optional()
|
|
12805
12980
|
});
|
|
12806
|
-
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
12981
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
12982
|
+
CompositionForwardCommandSchema,
|
|
12983
|
+
CompositionCodeSourceSchema,
|
|
12984
|
+
object({
|
|
12985
|
+
kind: literal("extension"),
|
|
12986
|
+
target: literal("remote")
|
|
12987
|
+
})
|
|
12988
|
+
]);
|
|
12807
12989
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12808
12990
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12809
12991
|
/**
|
|
@@ -12848,7 +13030,7 @@ var CompositionSchema = object({
|
|
|
12848
13030
|
kind: literal("existing"),
|
|
12849
13031
|
device: CompositionSourceRefSchema
|
|
12850
13032
|
})]),
|
|
12851
|
-
features: array(CompositionFeatureSchema).min(
|
|
13033
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
12852
13034
|
/**
|
|
12853
13035
|
* Present when the block is a helper template. Absent on a free
|
|
12854
13036
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -12880,6 +13062,73 @@ var NodeColorIdSchema = _enum([
|
|
|
12880
13062
|
"node-g",
|
|
12881
13063
|
"node-h"
|
|
12882
13064
|
]);
|
|
13065
|
+
//#endregion
|
|
13066
|
+
//#region ../types/dist/index.mjs
|
|
13067
|
+
var SMTP_INBOUND_DEFAULT_PORT = 2525;
|
|
13068
|
+
var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
|
|
13069
|
+
var SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES = 25 * 1024 * 1024;
|
|
13070
|
+
/** The text excerpt a message carries on the bus. */
|
|
13071
|
+
var SMTP_INBOUND_TEXT_MAX_CHARS = 4096;
|
|
13072
|
+
var PortSchema = number().int().min(1).max(65535);
|
|
13073
|
+
/** A recipient local-part: what stands before the `@`. */
|
|
13074
|
+
var SmtpRecipientSchema = string$2().min(1).max(64).regex(/^[A-Za-z0-9._+-]+$/);
|
|
13075
|
+
object({
|
|
13076
|
+
port: PortSchema.default(SMTP_INBOUND_DEFAULT_PORT),
|
|
13077
|
+
bindHost: string$2().min(1).default("0.0.0.0"),
|
|
13078
|
+
domain: string$2().min(1).default(SMTP_INBOUND_DEFAULT_DOMAIN),
|
|
13079
|
+
requireAuth: boolean().default(true),
|
|
13080
|
+
username: string$2().default(""),
|
|
13081
|
+
password: string$2().default("").meta({ secret: true }),
|
|
13082
|
+
starttls: boolean().default(false),
|
|
13083
|
+
maxMessageBytes: number().int().positive().default(SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES)
|
|
13084
|
+
});
|
|
13085
|
+
object({
|
|
13086
|
+
host: string$2().min(1).nullable(),
|
|
13087
|
+
hostReason: string$2().nullable(),
|
|
13088
|
+
port: PortSchema,
|
|
13089
|
+
domain: string$2().min(1),
|
|
13090
|
+
requireAuth: boolean(),
|
|
13091
|
+
username: string$2(),
|
|
13092
|
+
password: string$2(),
|
|
13093
|
+
starttls: boolean()
|
|
13094
|
+
});
|
|
13095
|
+
object({
|
|
13096
|
+
recipients: array(SmtpRecipientSchema).min(1),
|
|
13097
|
+
/**
|
|
13098
|
+
* Who is subscribing. A second `subscribe` with the same id REPLACES that subscriber's
|
|
13099
|
+
* recipients (the way a consumer that restarted, or lost a device, declares its set again);
|
|
13100
|
+
* a recipient held by another subscriber is refused with `smtp-inbound/recipient-claimed`.
|
|
13101
|
+
* Absent, the subscriber is anonymous and unique.
|
|
13102
|
+
*/
|
|
13103
|
+
subscriberId: string$2().min(1).max(64).optional()
|
|
13104
|
+
});
|
|
13105
|
+
var SmtpInboundAttachmentSchema = object({
|
|
13106
|
+
filename: string$2(),
|
|
13107
|
+
contentType: string$2(),
|
|
13108
|
+
bytes: number().int().nonnegative()
|
|
13109
|
+
});
|
|
13110
|
+
/** The payload of one `broker.message` (the key is the recipient local-part). */
|
|
13111
|
+
var SmtpInboundMessageSchema = object({
|
|
13112
|
+
/** Monotonic per mailbox; a gap tells a consumer it missed an event. */
|
|
13113
|
+
seq: number().int().positive(),
|
|
13114
|
+
messageId: string$2(),
|
|
13115
|
+
receivedAt: number().int().nonnegative(),
|
|
13116
|
+
from: string$2(),
|
|
13117
|
+
to: string$2(),
|
|
13118
|
+
subject: string$2(),
|
|
13119
|
+
text: string$2().max(SMTP_INBOUND_TEXT_MAX_CHARS),
|
|
13120
|
+
attachments: array(SmtpInboundAttachmentSchema)
|
|
13121
|
+
});
|
|
13122
|
+
object({
|
|
13123
|
+
/** The newest seq issued so far (0 before any mail). */
|
|
13124
|
+
lastSeq: number().int().nonnegative(),
|
|
13125
|
+
/** The oldest seq still retained; messages below it are gone. */
|
|
13126
|
+
oldestSeq: number().int().nonnegative(),
|
|
13127
|
+
messages: array(object({
|
|
13128
|
+
key: string$2(),
|
|
13129
|
+
message: SmtpInboundMessageSchema
|
|
13130
|
+
}))
|
|
13131
|
+
});
|
|
12883
13132
|
new Set([
|
|
12884
13133
|
"track",
|
|
12885
13134
|
"face",
|
|
@@ -12999,6 +13248,157 @@ var IntegrationInstanceRefSchema = object({
|
|
|
12999
13248
|
]),
|
|
13000
13249
|
candidateStableId: string$2().optional()
|
|
13001
13250
|
});
|
|
13251
|
+
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13252
|
+
var INSTANCES_LIST_MAX = 1e3;
|
|
13253
|
+
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13254
|
+
function topLevelSecretKeys(paths) {
|
|
13255
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13256
|
+
}
|
|
13257
|
+
var RecordIntegrationInstances = class {
|
|
13258
|
+
records;
|
|
13259
|
+
kind;
|
|
13260
|
+
hooks;
|
|
13261
|
+
handle;
|
|
13262
|
+
secretKeys;
|
|
13263
|
+
nameField;
|
|
13264
|
+
constructor(records, kind, hooks) {
|
|
13265
|
+
this.records = records;
|
|
13266
|
+
this.kind = kind;
|
|
13267
|
+
this.hooks = hooks;
|
|
13268
|
+
this.handle = records.kind(kind);
|
|
13269
|
+
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13270
|
+
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13271
|
+
}
|
|
13272
|
+
/**
|
|
13273
|
+
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
13274
|
+
* locked the row is served WITHOUT its secrets and says so (`secrets: 'unavailable'`), so the
|
|
13275
|
+
* caller can keep what it had instead of reconfiguring with an empty secret (D49).
|
|
13276
|
+
*/
|
|
13277
|
+
async readResolved(id) {
|
|
13278
|
+
try {
|
|
13279
|
+
const record = await this.handle.getResolved(id);
|
|
13280
|
+
return record === null ? null : {
|
|
13281
|
+
id,
|
|
13282
|
+
body: record.data,
|
|
13283
|
+
enabled: record.data.enabled !== false,
|
|
13284
|
+
secrets: "resolved"
|
|
13285
|
+
};
|
|
13286
|
+
} catch (err) {
|
|
13287
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
13288
|
+
const stored = await this.handle.get(id);
|
|
13289
|
+
if (stored === null) return null;
|
|
13290
|
+
return {
|
|
13291
|
+
id,
|
|
13292
|
+
body: Object.fromEntries(Object.entries(stored.data).filter(([key]) => !this.secretKeys.has(key))),
|
|
13293
|
+
enabled: stored.data.enabled !== false,
|
|
13294
|
+
secrets: "unavailable"
|
|
13295
|
+
};
|
|
13296
|
+
}
|
|
13297
|
+
}
|
|
13298
|
+
/** The body of {@link readResolved} alone; a locked keychain yields the row without secrets. */
|
|
13299
|
+
async getResolved(id) {
|
|
13300
|
+
return (await this.readResolved(id))?.body ?? null;
|
|
13301
|
+
}
|
|
13302
|
+
/** Every instance, secrets resolved where the keychain allows; an absent one is skipped. */
|
|
13303
|
+
async listResolved() {
|
|
13304
|
+
const page = await this.handle.query({ limit: INSTANCES_LIST_MAX });
|
|
13305
|
+
const out = [];
|
|
13306
|
+
for (const record of page.records) {
|
|
13307
|
+
const read = await this.readResolved(record.id);
|
|
13308
|
+
if (read !== null) out.push(read);
|
|
13309
|
+
}
|
|
13310
|
+
return out;
|
|
13311
|
+
}
|
|
13312
|
+
viewOf(record) {
|
|
13313
|
+
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13314
|
+
const rawName = record.data[this.nameField];
|
|
13315
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13316
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13317
|
+
return {
|
|
13318
|
+
id: record.id,
|
|
13319
|
+
name: typeof rawName === "string" ? rawName : record.id,
|
|
13320
|
+
configured: this.hooks.configured(record.data),
|
|
13321
|
+
enabled: record.data.enabled !== false,
|
|
13322
|
+
formValues: shown,
|
|
13323
|
+
...kind !== void 0 ? { kind } : {},
|
|
13324
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13325
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13326
|
+
};
|
|
13327
|
+
}
|
|
13328
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13329
|
+
...this.hooks.description,
|
|
13330
|
+
mintsId: true
|
|
13331
|
+
};
|
|
13332
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13333
|
+
listInstances = async () => {
|
|
13334
|
+
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13335
|
+
};
|
|
13336
|
+
getInstance = async ({ id }) => {
|
|
13337
|
+
const record = await this.handle.get(id);
|
|
13338
|
+
return record === null ? null : this.viewOf(record);
|
|
13339
|
+
};
|
|
13340
|
+
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13341
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13342
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13343
|
+
name,
|
|
13344
|
+
...kind !== void 0 ? { kind } : {}
|
|
13345
|
+
});
|
|
13346
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13347
|
+
await this.handle.put({
|
|
13348
|
+
id: minted,
|
|
13349
|
+
data: this.hooks.initialBody(name, kind)
|
|
13350
|
+
}, { ifRev: 0 });
|
|
13351
|
+
await this.hooks.onChanged?.(minted);
|
|
13352
|
+
return { id: minted };
|
|
13353
|
+
};
|
|
13354
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13355
|
+
const patch = {};
|
|
13356
|
+
for (const [key, value] of Object.entries(values)) {
|
|
13357
|
+
if (value === void 0) continue;
|
|
13358
|
+
if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13359
|
+
else if (isSecretClear(value)) patch[key] = null;
|
|
13360
|
+
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13361
|
+
}
|
|
13362
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13363
|
+
await this.handle.patch(id, patch);
|
|
13364
|
+
await this.hooks.onChanged?.(id);
|
|
13365
|
+
return { ok: true };
|
|
13366
|
+
};
|
|
13367
|
+
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13368
|
+
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13369
|
+
await this.handle.patch(id, { enabled });
|
|
13370
|
+
await this.hooks.onChanged?.(id);
|
|
13371
|
+
return { ok: true };
|
|
13372
|
+
};
|
|
13373
|
+
/** The row and its keychain refs go together (the records door). */
|
|
13374
|
+
deleteInstance = async ({ id }) => {
|
|
13375
|
+
await this.handle.delete(id);
|
|
13376
|
+
await this.hooks.onChanged?.(id);
|
|
13377
|
+
return { ok: true };
|
|
13378
|
+
};
|
|
13379
|
+
testInstance = async (input) => {
|
|
13380
|
+
if (this.hooks.test === void 0) return {
|
|
13381
|
+
success: false,
|
|
13382
|
+
status: "unsupported",
|
|
13383
|
+
error: "This integration declares no connection test"
|
|
13384
|
+
};
|
|
13385
|
+
return this.hooks.test(input);
|
|
13386
|
+
};
|
|
13387
|
+
/** The flat provider object to hand to `registerProvider`. */
|
|
13388
|
+
toProvider() {
|
|
13389
|
+
return {
|
|
13390
|
+
describe: this.describe,
|
|
13391
|
+
instanceFormSchema: this.instanceFormSchema,
|
|
13392
|
+
listInstances: this.listInstances,
|
|
13393
|
+
getInstance: this.getInstance,
|
|
13394
|
+
createInstance: this.createInstance,
|
|
13395
|
+
updateInstance: this.updateInstance,
|
|
13396
|
+
setInstanceEnabled: this.setInstanceEnabled,
|
|
13397
|
+
deleteInstance: this.deleteInstance,
|
|
13398
|
+
testInstance: this.testInstance
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
};
|
|
13002
13402
|
var EncodeProfileSchema = object({
|
|
13003
13403
|
video: object({
|
|
13004
13404
|
codec: _enum([
|
|
@@ -14067,6 +14467,58 @@ var MigrationOwnerStepSchema = object({
|
|
|
14067
14467
|
/** The ledger state the engine found: `pending` has no row yet. */
|
|
14068
14468
|
state: union([literal("pending"), MigrationStatusSchema])
|
|
14069
14469
|
});
|
|
14470
|
+
function isPlain(value) {
|
|
14471
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14472
|
+
}
|
|
14473
|
+
async function flattenNestedRecords(ports, spec) {
|
|
14474
|
+
const ids = await ports.listNested(spec.legacyKey);
|
|
14475
|
+
if (ids.length === 0) return {
|
|
14476
|
+
outcome: "already-done",
|
|
14477
|
+
summary: { converted: 0 }
|
|
14478
|
+
};
|
|
14479
|
+
const rows = /* @__PURE__ */ new Map();
|
|
14480
|
+
for (const id of ids) try {
|
|
14481
|
+
rows.set(id, await ports.readResolved(id));
|
|
14482
|
+
} catch (err) {
|
|
14483
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
14484
|
+
throw new Error(`record '${id}': cannot be read with its secrets (${why}) - nothing moved`);
|
|
14485
|
+
}
|
|
14486
|
+
for (const [id, row] of rows) {
|
|
14487
|
+
const legacy = row[spec.legacyKey];
|
|
14488
|
+
if (!isPlain(legacy)) throw new Error(`record '${id}': '${spec.legacyKey}' is not an object - nothing moved`);
|
|
14489
|
+
const flat = spec.toFlat(legacy, row);
|
|
14490
|
+
await ports.patch(id, flat);
|
|
14491
|
+
const back = await ports.readResolved(id);
|
|
14492
|
+
const wrong = spec.flatSecretKeys.filter((key) => {
|
|
14493
|
+
const sent = flat[key];
|
|
14494
|
+
const got = back[key];
|
|
14495
|
+
return (typeof sent === "string" ? sent : "") !== (typeof got === "string" ? got : "");
|
|
14496
|
+
});
|
|
14497
|
+
if (wrong.length > 0) {
|
|
14498
|
+
await ports.patch(id, Object.fromEntries(Object.keys(flat).map((key) => [key, null])));
|
|
14499
|
+
throw new Error(`record '${id}': ${wrong.join(", ")} did not read back from its new home - the row is unchanged`);
|
|
14500
|
+
}
|
|
14501
|
+
await ports.patch(id, { [spec.legacyKey]: null });
|
|
14502
|
+
}
|
|
14503
|
+
return {
|
|
14504
|
+
outcome: "done",
|
|
14505
|
+
summary: { converted: rows.size }
|
|
14506
|
+
};
|
|
14507
|
+
}
|
|
14508
|
+
/** The ports over one kind's records handle. */
|
|
14509
|
+
function flattenPortsOver(handle) {
|
|
14510
|
+
return {
|
|
14511
|
+
listNested: async (legacyKey) => (await handle.query({ limit: 1e3 })).records.filter((r) => legacyKey in r.data).map((r) => r.id),
|
|
14512
|
+
readResolved: async (id) => {
|
|
14513
|
+
const row = await handle.getResolved(id);
|
|
14514
|
+
if (row === null) throw new Error("the record is gone");
|
|
14515
|
+
return row.data;
|
|
14516
|
+
},
|
|
14517
|
+
patch: async (id, patch) => {
|
|
14518
|
+
await handle.patch(id, patch);
|
|
14519
|
+
}
|
|
14520
|
+
};
|
|
14521
|
+
}
|
|
14070
14522
|
/**
|
|
14071
14523
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
14072
14524
|
* gets back (cloud training, D805+).
|
|
@@ -19152,24 +19604,52 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19152
19604
|
args: { on: false }
|
|
19153
19605
|
},
|
|
19154
19606
|
{
|
|
19155
|
-
id: "petFountain.
|
|
19156
|
-
method: "
|
|
19607
|
+
id: "petFountain.resetDailyConsumption",
|
|
19608
|
+
method: "resetDailyConsumption",
|
|
19609
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
19610
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
19611
|
+
icon: "rotate-ccw"
|
|
19612
|
+
}
|
|
19613
|
+
],
|
|
19614
|
+
state: [{
|
|
19615
|
+
kind: "boolean",
|
|
19616
|
+
field: "empty",
|
|
19617
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
19618
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
19619
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
19620
|
+
icon: "droplet"
|
|
19621
|
+
}, {
|
|
19622
|
+
kind: "numeric",
|
|
19623
|
+
field: "waterLevel",
|
|
19624
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
19625
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
19626
|
+
unit: "%",
|
|
19627
|
+
icon: "droplets"
|
|
19628
|
+
}]
|
|
19629
|
+
},
|
|
19630
|
+
"pump-cycle": {
|
|
19631
|
+
scope: "device",
|
|
19632
|
+
noun: t("Pump cycle", "Ciclo della pompa"),
|
|
19633
|
+
actions: [
|
|
19634
|
+
{
|
|
19635
|
+
id: "pumpCycle.on",
|
|
19636
|
+
method: "setEnabled",
|
|
19157
19637
|
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
19158
19638
|
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
19159
19639
|
icon: "timer",
|
|
19160
19640
|
args: { on: true }
|
|
19161
19641
|
},
|
|
19162
19642
|
{
|
|
19163
|
-
id: "
|
|
19164
|
-
method: "
|
|
19643
|
+
id: "pumpCycle.off",
|
|
19644
|
+
method: "setEnabled",
|
|
19165
19645
|
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
19166
19646
|
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
19167
19647
|
icon: "timer-off",
|
|
19168
19648
|
args: { on: false }
|
|
19169
19649
|
},
|
|
19170
19650
|
{
|
|
19171
|
-
id: "
|
|
19172
|
-
method: "
|
|
19651
|
+
id: "pumpCycle.onMinutes",
|
|
19652
|
+
method: "setOnMinutes",
|
|
19173
19653
|
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
19174
19654
|
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
19175
19655
|
icon: "timer",
|
|
@@ -19185,8 +19665,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19185
19665
|
}]
|
|
19186
19666
|
},
|
|
19187
19667
|
{
|
|
19188
|
-
id: "
|
|
19189
|
-
method: "
|
|
19668
|
+
id: "pumpCycle.offMinutes",
|
|
19669
|
+
method: "setOffMinutes",
|
|
19190
19670
|
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
19191
19671
|
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
19192
19672
|
icon: "timer-off",
|
|
@@ -19200,35 +19680,63 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19200
19680
|
unit: "min"
|
|
19201
19681
|
}
|
|
19202
19682
|
}]
|
|
19203
|
-
},
|
|
19204
|
-
{
|
|
19205
|
-
id: "petFountain.resetDailyConsumption",
|
|
19206
|
-
method: "resetDailyConsumption",
|
|
19207
|
-
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
19208
|
-
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
19209
|
-
icon: "rotate-ccw"
|
|
19210
19683
|
}
|
|
19211
19684
|
],
|
|
19212
19685
|
state: [{
|
|
19213
19686
|
kind: "boolean",
|
|
19214
|
-
field: "
|
|
19215
|
-
label: t("
|
|
19216
|
-
whenTrue: t("When
|
|
19217
|
-
whenFalse: t("When
|
|
19218
|
-
icon: "
|
|
19219
|
-
}, {
|
|
19220
|
-
kind: "numeric",
|
|
19221
|
-
field: "waterLevel",
|
|
19222
|
-
quantity: t("the water level", "il livello dell’acqua"),
|
|
19223
|
-
label: t("Water level", "Livello dell’acqua"),
|
|
19224
|
-
unit: "%",
|
|
19225
|
-
icon: "droplets"
|
|
19687
|
+
field: "enabled",
|
|
19688
|
+
label: t("Pump cycle", "Ciclo pompa"),
|
|
19689
|
+
whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
|
|
19690
|
+
whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
|
|
19691
|
+
icon: "timer"
|
|
19226
19692
|
}]
|
|
19227
19693
|
},
|
|
19228
19694
|
"media-player": {
|
|
19229
19695
|
scope: "device",
|
|
19230
19696
|
noun: t("Media player", "Lettore multimediale"),
|
|
19231
19697
|
actions: [
|
|
19698
|
+
{
|
|
19699
|
+
id: "media.turnOn",
|
|
19700
|
+
method: "turnOn",
|
|
19701
|
+
label: t("Turn on", "Accendi"),
|
|
19702
|
+
phrase: t("Turn on {device}", "Accendi {device}"),
|
|
19703
|
+
icon: "power"
|
|
19704
|
+
},
|
|
19705
|
+
{
|
|
19706
|
+
id: "media.turnOff",
|
|
19707
|
+
method: "turnOff",
|
|
19708
|
+
label: t("Turn off", "Spegni"),
|
|
19709
|
+
phrase: t("Turn off {device}", "Spegni {device}"),
|
|
19710
|
+
icon: "power"
|
|
19711
|
+
},
|
|
19712
|
+
{
|
|
19713
|
+
id: "media.volumeUp",
|
|
19714
|
+
method: "volumeUp",
|
|
19715
|
+
label: t("Volume up", "Alza il volume"),
|
|
19716
|
+
phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
|
|
19717
|
+
icon: "volume-2"
|
|
19718
|
+
},
|
|
19719
|
+
{
|
|
19720
|
+
id: "media.volumeDown",
|
|
19721
|
+
method: "volumeDown",
|
|
19722
|
+
label: t("Volume down", "Abbassa il volume"),
|
|
19723
|
+
phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
|
|
19724
|
+
icon: "volume-2"
|
|
19725
|
+
},
|
|
19726
|
+
{
|
|
19727
|
+
id: "media.channelUp",
|
|
19728
|
+
method: "channelUp",
|
|
19729
|
+
label: t("Channel up", "Canale successivo"),
|
|
19730
|
+
phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
|
|
19731
|
+
icon: "skip-forward"
|
|
19732
|
+
},
|
|
19733
|
+
{
|
|
19734
|
+
id: "media.channelDown",
|
|
19735
|
+
method: "channelDown",
|
|
19736
|
+
label: t("Channel down", "Canale precedente"),
|
|
19737
|
+
phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
|
|
19738
|
+
icon: "skip-back"
|
|
19739
|
+
},
|
|
19232
19740
|
{
|
|
19233
19741
|
id: "media.play",
|
|
19234
19742
|
method: "play",
|
|
@@ -21115,6 +21623,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
21115
21623
|
"audio-analyzer": system(PIPELINE),
|
|
21116
21624
|
"audio-codec": system(INTERNAL$1),
|
|
21117
21625
|
"auth-provider": system(IDENTITY),
|
|
21626
|
+
"board-key-source": system(INTERNAL$1),
|
|
21118
21627
|
backup: system("system backups — run and restored from the Backups page"),
|
|
21119
21628
|
broker: system("message-broker connections — configured, not actuated"),
|
|
21120
21629
|
"calendar-source": system(INTERNAL$1),
|
|
@@ -46024,6 +46533,49 @@ var binaryCapability = {
|
|
|
46024
46533
|
volatileStateFields: ["lastChangedAt"]
|
|
46025
46534
|
};
|
|
46026
46535
|
/**
|
|
46536
|
+
* Why a board key could not be read. Typed so a caller reports the cause and
|
|
46537
|
+
* never treats "could not read" as "has no key" (D49).
|
|
46538
|
+
*/
|
|
46539
|
+
var BoardKeyFailureReasonSchema = _enum([
|
|
46540
|
+
"unknown-source",
|
|
46541
|
+
"not-admin",
|
|
46542
|
+
"no-builder",
|
|
46543
|
+
"no-ingress-url",
|
|
46544
|
+
"unreachable",
|
|
46545
|
+
"unknown-board",
|
|
46546
|
+
"no-encryption-key",
|
|
46547
|
+
"unreadable-config"
|
|
46548
|
+
]);
|
|
46549
|
+
var BoardKeySourceSchema = object({
|
|
46550
|
+
sourceId: string$2(),
|
|
46551
|
+
name: string$2()
|
|
46552
|
+
});
|
|
46553
|
+
var BoardKeyBoardSchema = object({
|
|
46554
|
+
/** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
|
|
46555
|
+
configuration: string$2(),
|
|
46556
|
+
/** The node name the builder gives the board. */
|
|
46557
|
+
name: string$2(),
|
|
46558
|
+
/** The address the builder knows the board by, when it has one. */
|
|
46559
|
+
address: string$2().nullable()
|
|
46560
|
+
});
|
|
46561
|
+
var BoardKeyFailureSchema = object({
|
|
46562
|
+
ok: literal(false),
|
|
46563
|
+
reason: BoardKeyFailureReasonSchema,
|
|
46564
|
+
detail: string$2().optional()
|
|
46565
|
+
});
|
|
46566
|
+
var ListBoardsResultSchema = discriminatedUnion("ok", [object({
|
|
46567
|
+
ok: literal(true),
|
|
46568
|
+
boards: array(BoardKeyBoardSchema).readonly()
|
|
46569
|
+
}), BoardKeyFailureSchema]);
|
|
46570
|
+
var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
|
|
46571
|
+
ok: literal(true),
|
|
46572
|
+
key: string$2()
|
|
46573
|
+
}), BoardKeyFailureSchema]);
|
|
46574
|
+
method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string$2().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
|
|
46575
|
+
sourceId: string$2().min(1),
|
|
46576
|
+
configuration: string$2().min(1)
|
|
46577
|
+
}), GetBoardKeyResultSchema, { auth: "admin" });
|
|
46578
|
+
/**
|
|
46027
46579
|
* Dimmable-light brightness control. Co-exists with `switch` on the
|
|
46028
46580
|
* same device — the switch toggles on/off, this cap sets the level
|
|
46029
46581
|
* applied when the light is on. Drivers map their per-vendor dim
|
|
@@ -48771,6 +49323,244 @@ var controlCapability = {
|
|
|
48771
49323
|
*/
|
|
48772
49324
|
history: "none"
|
|
48773
49325
|
};
|
|
49326
|
+
/**
|
|
49327
|
+
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
49328
|
+
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
49329
|
+
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
49330
|
+
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
49331
|
+
*
|
|
49332
|
+
* The cap declares a wide method surface; not every backend supports
|
|
49333
|
+
* every method. Drivers signal sub-capability via the
|
|
49334
|
+
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
49335
|
+
* corresponding method) so the UI gates buttons without a slice
|
|
49336
|
+
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
49337
|
+
*/
|
|
49338
|
+
var MediaPlayerStateSchema = _enum([
|
|
49339
|
+
"off",
|
|
49340
|
+
"on",
|
|
49341
|
+
"idle",
|
|
49342
|
+
"playing",
|
|
49343
|
+
"paused",
|
|
49344
|
+
"buffering",
|
|
49345
|
+
"standby"
|
|
49346
|
+
]);
|
|
49347
|
+
var MediaPlayerRepeatSchema = _enum([
|
|
49348
|
+
"off",
|
|
49349
|
+
"all",
|
|
49350
|
+
"one"
|
|
49351
|
+
]);
|
|
49352
|
+
var MediaInfoSchema = object({
|
|
49353
|
+
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
49354
|
+
* `podcast`, …). */
|
|
49355
|
+
type: string$2(),
|
|
49356
|
+
/** Human-readable title. */
|
|
49357
|
+
title: string$2(),
|
|
49358
|
+
/** Optional artist / channel / station label. */
|
|
49359
|
+
artist: string$2().optional(),
|
|
49360
|
+
/** Optional album / season / show name. */
|
|
49361
|
+
album: string$2().optional(),
|
|
49362
|
+
/** Optional cover-art / thumbnail URL. */
|
|
49363
|
+
imageUrl: string$2().optional()
|
|
49364
|
+
});
|
|
49365
|
+
/**
|
|
49366
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
49367
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
49368
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
49369
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
49370
|
+
* guess, never as a reading.
|
|
49371
|
+
*/
|
|
49372
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
49373
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
49374
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
49375
|
+
"state",
|
|
49376
|
+
"volumeLevel",
|
|
49377
|
+
"isMuted",
|
|
49378
|
+
"source"
|
|
49379
|
+
]);
|
|
49380
|
+
var MediaPlayerStatusSchema = object({
|
|
49381
|
+
/** Playback lifecycle state. */
|
|
49382
|
+
state: MediaPlayerStateSchema,
|
|
49383
|
+
/**
|
|
49384
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
49385
|
+
* A slice written before this field existed parses as `reported`.
|
|
49386
|
+
*/
|
|
49387
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
49388
|
+
/**
|
|
49389
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
49390
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
49391
|
+
* from a running-state source, mute guessed from the last send).
|
|
49392
|
+
*/
|
|
49393
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
49394
|
+
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
49395
|
+
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
49396
|
+
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
49397
|
+
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
49398
|
+
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
49399
|
+
isMuted: boolean().nullable(),
|
|
49400
|
+
/** Active source / input. Empty when no source surface. */
|
|
49401
|
+
source: string$2(),
|
|
49402
|
+
/** Selectable sources. Empty when no source surface.
|
|
49403
|
+
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
49404
|
+
availableSources: array(string$2()),
|
|
49405
|
+
/** Currently-playing media info. Null when nothing is playing. */
|
|
49406
|
+
currentMedia: MediaInfoSchema.nullable(),
|
|
49407
|
+
/** Current playback position in ms. Null when not seekable or
|
|
49408
|
+
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
49409
|
+
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49410
|
+
/** Total duration of the current media in ms. Null when unknown
|
|
49411
|
+
* (live stream) or nothing is playing. */
|
|
49412
|
+
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49413
|
+
/** Shuffle toggle. Null when no shuffle surface.
|
|
49414
|
+
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
49415
|
+
shuffle: boolean().nullable(),
|
|
49416
|
+
/** Repeat mode. Null when no repeat surface.
|
|
49417
|
+
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
49418
|
+
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
49419
|
+
/** Ms epoch when the slice was last updated. */
|
|
49420
|
+
lastChangedAt: number()
|
|
49421
|
+
});
|
|
49422
|
+
var stepInput = object({
|
|
49423
|
+
deviceId: number().int().nonnegative(),
|
|
49424
|
+
/** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
|
|
49425
|
+
steps: number().int().min(1).max(50).default(1)
|
|
49426
|
+
});
|
|
49427
|
+
var deviceOnly = object({ deviceId: number().int().nonnegative() });
|
|
49428
|
+
var mediaPlayerCapability = {
|
|
49429
|
+
name: "media-player",
|
|
49430
|
+
scope: "device",
|
|
49431
|
+
deviceNative: true,
|
|
49432
|
+
mode: "singleton",
|
|
49433
|
+
deviceTypes: [DeviceType.MediaPlayer],
|
|
49434
|
+
methods: {
|
|
49435
|
+
turnOn: method(deviceOnly, _void(), {
|
|
49436
|
+
kind: "mutation",
|
|
49437
|
+
auth: "protected"
|
|
49438
|
+
}),
|
|
49439
|
+
turnOff: method(deviceOnly, _void(), {
|
|
49440
|
+
kind: "mutation",
|
|
49441
|
+
auth: "protected"
|
|
49442
|
+
}),
|
|
49443
|
+
volumeUp: method(stepInput, _void(), {
|
|
49444
|
+
kind: "mutation",
|
|
49445
|
+
auth: "protected"
|
|
49446
|
+
}),
|
|
49447
|
+
volumeDown: method(stepInput, _void(), {
|
|
49448
|
+
kind: "mutation",
|
|
49449
|
+
auth: "protected"
|
|
49450
|
+
}),
|
|
49451
|
+
channelUp: method(stepInput, _void(), {
|
|
49452
|
+
kind: "mutation",
|
|
49453
|
+
auth: "protected"
|
|
49454
|
+
}),
|
|
49455
|
+
channelDown: method(stepInput, _void(), {
|
|
49456
|
+
kind: "mutation",
|
|
49457
|
+
auth: "protected"
|
|
49458
|
+
}),
|
|
49459
|
+
play: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49460
|
+
kind: "mutation",
|
|
49461
|
+
auth: "protected"
|
|
49462
|
+
}),
|
|
49463
|
+
pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49464
|
+
kind: "mutation",
|
|
49465
|
+
auth: "protected"
|
|
49466
|
+
}),
|
|
49467
|
+
stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49468
|
+
kind: "mutation",
|
|
49469
|
+
auth: "protected"
|
|
49470
|
+
}),
|
|
49471
|
+
next: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49472
|
+
kind: "mutation",
|
|
49473
|
+
auth: "protected"
|
|
49474
|
+
}),
|
|
49475
|
+
previous: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
49476
|
+
kind: "mutation",
|
|
49477
|
+
auth: "protected"
|
|
49478
|
+
}),
|
|
49479
|
+
seek: method(object({
|
|
49480
|
+
deviceId: number().int().nonnegative(),
|
|
49481
|
+
position: number().int().nonnegative()
|
|
49482
|
+
}), _void(), {
|
|
49483
|
+
kind: "mutation",
|
|
49484
|
+
auth: "protected"
|
|
49485
|
+
}),
|
|
49486
|
+
setVolume: method(object({
|
|
49487
|
+
deviceId: number().int().nonnegative(),
|
|
49488
|
+
volumeLevel: number().min(0).max(100)
|
|
49489
|
+
}), _void(), {
|
|
49490
|
+
kind: "mutation",
|
|
49491
|
+
auth: "protected"
|
|
49492
|
+
}),
|
|
49493
|
+
setMute: method(object({
|
|
49494
|
+
deviceId: number().int().nonnegative(),
|
|
49495
|
+
muted: boolean()
|
|
49496
|
+
}), _void(), {
|
|
49497
|
+
kind: "mutation",
|
|
49498
|
+
auth: "protected"
|
|
49499
|
+
}),
|
|
49500
|
+
setShuffle: method(object({
|
|
49501
|
+
deviceId: number().int().nonnegative(),
|
|
49502
|
+
shuffle: boolean()
|
|
49503
|
+
}), _void(), {
|
|
49504
|
+
kind: "mutation",
|
|
49505
|
+
auth: "protected"
|
|
49506
|
+
}),
|
|
49507
|
+
setRepeat: method(object({
|
|
49508
|
+
deviceId: number().int().nonnegative(),
|
|
49509
|
+
repeat: MediaPlayerRepeatSchema
|
|
49510
|
+
}), _void(), {
|
|
49511
|
+
kind: "mutation",
|
|
49512
|
+
auth: "protected"
|
|
49513
|
+
}),
|
|
49514
|
+
selectSource: method(object({
|
|
49515
|
+
deviceId: number().int().nonnegative(),
|
|
49516
|
+
source: string$2().min(1)
|
|
49517
|
+
}), _void(), {
|
|
49518
|
+
kind: "mutation",
|
|
49519
|
+
auth: "protected"
|
|
49520
|
+
}),
|
|
49521
|
+
playMedia: method(object({
|
|
49522
|
+
deviceId: number().int().nonnegative(),
|
|
49523
|
+
/** Media identifier / URL. */
|
|
49524
|
+
mediaId: string$2().min(1),
|
|
49525
|
+
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
49526
|
+
* passes it through to the upstream service. */
|
|
49527
|
+
mediaType: string$2().min(1)
|
|
49528
|
+
}), _void(), {
|
|
49529
|
+
kind: "mutation",
|
|
49530
|
+
auth: "protected"
|
|
49531
|
+
})
|
|
49532
|
+
},
|
|
49533
|
+
status: {
|
|
49534
|
+
schema: MediaPlayerStatusSchema,
|
|
49535
|
+
kind: "push"
|
|
49536
|
+
},
|
|
49537
|
+
/**
|
|
49538
|
+
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
49539
|
+
* full slice for live now-playing, volume, and progress updates
|
|
49540
|
+
* without polling.
|
|
49541
|
+
*/
|
|
49542
|
+
runtimeState: MediaPlayerStatusSchema,
|
|
49543
|
+
/**
|
|
49544
|
+
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
49545
|
+
*
|
|
49546
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
49547
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
49548
|
+
*/
|
|
49549
|
+
durability: "session",
|
|
49550
|
+
/**
|
|
49551
|
+
* History (D850): volume (a level an operator or an automation sets; the
|
|
49552
|
+
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
49553
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
49554
|
+
*/
|
|
49555
|
+
history: { fields: {
|
|
49556
|
+
volumeLevel: {
|
|
49557
|
+
kind: "gauge",
|
|
49558
|
+
unit: "%",
|
|
49559
|
+
deadband: 1
|
|
49560
|
+
},
|
|
49561
|
+
isMuted: { kind: "boolean" }
|
|
49562
|
+
} }
|
|
49563
|
+
};
|
|
48774
49564
|
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;
|
|
48775
49565
|
var NUMBER = {
|
|
48776
49566
|
kind: "fixed",
|
|
@@ -49218,117 +50008,6 @@ function actuator(input) {
|
|
|
49218
50008
|
};
|
|
49219
50009
|
}
|
|
49220
50010
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
49221
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
49222
|
-
"no-power-source",
|
|
49223
|
-
"switch-missing",
|
|
49224
|
-
"switch-offline",
|
|
49225
|
-
"switch-unknown",
|
|
49226
|
-
"restart-in-progress",
|
|
49227
|
-
"switch-call-failed"
|
|
49228
|
-
]);
|
|
49229
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
49230
|
-
accepted: literal(false),
|
|
49231
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
49232
|
-
/** One line for the operator, naming the switch. */
|
|
49233
|
-
message: string$2()
|
|
49234
|
-
})]);
|
|
49235
|
-
var PowerSourceInfoSchema = object({
|
|
49236
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
49237
|
-
linked: boolean(),
|
|
49238
|
-
switchDeviceId: number().nullable(),
|
|
49239
|
-
switchName: string$2().nullable(),
|
|
49240
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
49241
|
-
switchOn: boolean().nullable(),
|
|
49242
|
-
switchOnline: boolean().nullable(),
|
|
49243
|
-
restartInProgress: boolean(),
|
|
49244
|
-
offSeconds: number().int()
|
|
49245
|
-
});
|
|
49246
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
49247
|
-
var PowerSourceStateSchema = object({
|
|
49248
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
49249
|
-
switchOn: boolean().nullable(),
|
|
49250
|
-
offSeconds: number().int(),
|
|
49251
|
-
lastFetchedAt: number()
|
|
49252
|
-
});
|
|
49253
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
49254
|
-
DeviceType.Gateway,
|
|
49255
|
-
DeviceType.Hub,
|
|
49256
|
-
DeviceType.Camera,
|
|
49257
|
-
DeviceType.Sensor,
|
|
49258
|
-
DeviceType.MediaPlayer,
|
|
49259
|
-
DeviceType.Vacuum,
|
|
49260
|
-
DeviceType.Generic,
|
|
49261
|
-
DeviceType.Washer,
|
|
49262
|
-
DeviceType.Dryer,
|
|
49263
|
-
DeviceType.Dishwasher,
|
|
49264
|
-
DeviceType.PetFountain,
|
|
49265
|
-
DeviceType.Fan,
|
|
49266
|
-
DeviceType.Humidifier,
|
|
49267
|
-
DeviceType.Climate,
|
|
49268
|
-
DeviceType.WaterHeater
|
|
49269
|
-
];
|
|
49270
|
-
var powerSourceCustomize = {
|
|
49271
|
-
version: 1,
|
|
49272
|
-
targets: [{
|
|
49273
|
-
id: "switch",
|
|
49274
|
-
cardinality: "scalar",
|
|
49275
|
-
vocabulary: {
|
|
49276
|
-
cap: "power-source",
|
|
49277
|
-
stateField: "switchOn"
|
|
49278
|
-
},
|
|
49279
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
49280
|
-
apply: "add",
|
|
49281
|
-
path: "switchOn",
|
|
49282
|
-
sources: [{
|
|
49283
|
-
kind: "switch",
|
|
49284
|
-
transforms: [{ kind: "identity" }]
|
|
49285
|
-
}],
|
|
49286
|
-
companions: [{
|
|
49287
|
-
kind: "constant",
|
|
49288
|
-
path: "offSeconds",
|
|
49289
|
-
value: 10,
|
|
49290
|
-
when: "add"
|
|
49291
|
-
}, {
|
|
49292
|
-
kind: "clock",
|
|
49293
|
-
path: "lastFetchedAt",
|
|
49294
|
-
when: "always"
|
|
49295
|
-
}],
|
|
49296
|
-
sourceReport: { status: "assignment-status" }
|
|
49297
|
-
}]
|
|
49298
|
-
};
|
|
49299
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
49300
|
-
var powerSourceCapability = {
|
|
49301
|
-
name: "power-source",
|
|
49302
|
-
scope: "device",
|
|
49303
|
-
mode: "singleton",
|
|
49304
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
49305
|
-
methods: {
|
|
49306
|
-
getStatus: systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }),
|
|
49307
|
-
powerOn: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49308
|
-
kind: "mutation",
|
|
49309
|
-
auth: "admin"
|
|
49310
|
-
}),
|
|
49311
|
-
powerOff: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49312
|
-
kind: "mutation",
|
|
49313
|
-
auth: "admin"
|
|
49314
|
-
}),
|
|
49315
|
-
forceRestart: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
49316
|
-
kind: "mutation",
|
|
49317
|
-
auth: "admin"
|
|
49318
|
-
})
|
|
49319
|
-
},
|
|
49320
|
-
runtimeState: PowerSourceStateSchema,
|
|
49321
|
-
customize: powerSourceCustomize,
|
|
49322
|
-
/**
|
|
49323
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
49324
|
-
* its next report; a restored value would outlive the reading it names.
|
|
49325
|
-
*
|
|
49326
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
49327
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
49328
|
-
*/
|
|
49329
|
-
durability: "session",
|
|
49330
|
-
history: "none"
|
|
49331
|
-
};
|
|
49332
50011
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
49333
50012
|
DeviceType.Camera;
|
|
49334
50013
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -49356,6 +50035,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
49356
50035
|
}).optional()
|
|
49357
50036
|
})) });
|
|
49358
50037
|
/**
|
|
50038
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
50039
|
+
* `z.infer`, they do not restate the shape.
|
|
50040
|
+
*/
|
|
50041
|
+
var OverrideSourceSchema = object({
|
|
50042
|
+
addonId: string$2().min(1),
|
|
50043
|
+
stableId: string$2().min(1),
|
|
50044
|
+
cap: string$2().min(1),
|
|
50045
|
+
fieldPath: string$2().min(1)
|
|
50046
|
+
});
|
|
50047
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
50048
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
50049
|
+
var ThresholdTransformDraftSchema = object({
|
|
50050
|
+
kind: literal("threshold"),
|
|
50051
|
+
comparison: _enum(["gte", "lte"]),
|
|
50052
|
+
threshold: number(),
|
|
50053
|
+
startDwellMs: number().nonnegative(),
|
|
50054
|
+
stopDwellMs: number().nonnegative()
|
|
50055
|
+
});
|
|
50056
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
50057
|
+
IdentityTransformDraftSchema,
|
|
50058
|
+
InvertTransformDraftSchema,
|
|
50059
|
+
ThresholdTransformDraftSchema,
|
|
50060
|
+
object({
|
|
50061
|
+
kind: literal("linear"),
|
|
50062
|
+
scale: number(),
|
|
50063
|
+
offset: number(),
|
|
50064
|
+
clampMin: number().optional(),
|
|
50065
|
+
clampMax: number().optional()
|
|
50066
|
+
}),
|
|
50067
|
+
object({
|
|
50068
|
+
kind: literal("enum-map"),
|
|
50069
|
+
mapping: record(string$2(), string$2().nullable())
|
|
50070
|
+
}),
|
|
50071
|
+
object({ kind: literal("number") })
|
|
50072
|
+
]);
|
|
50073
|
+
var ScalarOverrideDraftSchema = object({
|
|
50074
|
+
kind: literal("scalar"),
|
|
50075
|
+
cap: string$2().min(1),
|
|
50076
|
+
targetId: string$2().min(1),
|
|
50077
|
+
source: OverrideSourceSchema,
|
|
50078
|
+
transform: CustomizeTransformDraftSchema
|
|
50079
|
+
});
|
|
50080
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
50081
|
+
kind: literal("keyedArray"),
|
|
50082
|
+
cap: string$2().min(1),
|
|
50083
|
+
targetId: string$2().min(1),
|
|
50084
|
+
key: string$2().min(1),
|
|
50085
|
+
label: string$2().min(1),
|
|
50086
|
+
fieldPath: string$2().min(1),
|
|
50087
|
+
source: OverrideSourceSchema,
|
|
50088
|
+
transform: CustomizeTransformDraftSchema
|
|
50089
|
+
});
|
|
50090
|
+
var PulseOverrideDraftSchema = object({
|
|
50091
|
+
kind: literal("pulse"),
|
|
50092
|
+
cap: string$2().min(1),
|
|
50093
|
+
targetId: string$2().min(1),
|
|
50094
|
+
enabled: boolean(),
|
|
50095
|
+
debounceMs: number().nonnegative(),
|
|
50096
|
+
edges: array(_enum([
|
|
50097
|
+
"rising",
|
|
50098
|
+
"button",
|
|
50099
|
+
"native-event"
|
|
50100
|
+
])).min(1).optional(),
|
|
50101
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
50102
|
+
});
|
|
50103
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
50104
|
+
"in-addition",
|
|
50105
|
+
"confirm",
|
|
50106
|
+
"replace"
|
|
50107
|
+
]).optional() });
|
|
50108
|
+
discriminatedUnion("kind", [
|
|
50109
|
+
ScalarOverrideDraftSchema,
|
|
50110
|
+
KeyedArrayOverrideDraftSchema,
|
|
50111
|
+
PulseOverrideDraftSchema,
|
|
50112
|
+
object({
|
|
50113
|
+
kind: literal("span"),
|
|
50114
|
+
cap: string$2().min(1),
|
|
50115
|
+
targetId: string$2().min(1),
|
|
50116
|
+
timeoutMs: number().nonnegative(),
|
|
50117
|
+
cooldownMs: number().nonnegative().optional(),
|
|
50118
|
+
sources: array(SpanSourceDraftSchema)
|
|
50119
|
+
})
|
|
50120
|
+
]);
|
|
50121
|
+
object({
|
|
50122
|
+
targetId: string$2().min(1),
|
|
50123
|
+
configured: boolean(),
|
|
50124
|
+
source: _enum(["native", "assigned"]),
|
|
50125
|
+
state: _enum([
|
|
50126
|
+
"active",
|
|
50127
|
+
"stale",
|
|
50128
|
+
"released",
|
|
50129
|
+
"unavailable"
|
|
50130
|
+
]),
|
|
50131
|
+
reason: string$2().nullable()
|
|
50132
|
+
});
|
|
50133
|
+
/**
|
|
50134
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
50135
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
50136
|
+
*
|
|
50137
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
50138
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
50139
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
50140
|
+
*/
|
|
50141
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
50142
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
50143
|
+
"high",
|
|
50144
|
+
"medium",
|
|
50145
|
+
"low"
|
|
50146
|
+
]);
|
|
50147
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
50148
|
+
"no-history",
|
|
50149
|
+
"span-too-short",
|
|
50150
|
+
"truncated",
|
|
50151
|
+
"no-idle-floor",
|
|
50152
|
+
"no-runs",
|
|
50153
|
+
"no-separation"
|
|
50154
|
+
]);
|
|
50155
|
+
var RunningProposalReplaySchema = object({
|
|
50156
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
50157
|
+
intervals: number().int().nonnegative(),
|
|
50158
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
50159
|
+
extra: number().int().nonnegative(),
|
|
50160
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
50161
|
+
missed: number().int().nonnegative()
|
|
50162
|
+
});
|
|
50163
|
+
var RunningProposalEvidenceSchema = object({
|
|
50164
|
+
tier: RunningProposalTierSchema,
|
|
50165
|
+
spanMs: number().nonnegative(),
|
|
50166
|
+
from: number(),
|
|
50167
|
+
to: number(),
|
|
50168
|
+
points: number().int().nonnegative(),
|
|
50169
|
+
runsFound: number().int().nonnegative(),
|
|
50170
|
+
pausesFound: number().int().nonnegative(),
|
|
50171
|
+
longestPauseMs: number().nonnegative(),
|
|
50172
|
+
idleFloorW: number(),
|
|
50173
|
+
noiseCeilingW: number(),
|
|
50174
|
+
plateauW: number(),
|
|
50175
|
+
replay: RunningProposalReplaySchema
|
|
50176
|
+
});
|
|
50177
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
50178
|
+
spanMs: number().nonnegative(),
|
|
50179
|
+
points: number().int().nonnegative(),
|
|
50180
|
+
runsFound: number().int().nonnegative()
|
|
50181
|
+
});
|
|
50182
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
50183
|
+
state: literal("proposal"),
|
|
50184
|
+
transform: ThresholdTransformDraftSchema,
|
|
50185
|
+
evidence: RunningProposalEvidenceSchema,
|
|
50186
|
+
confidence: RunningProposalConfidenceSchema,
|
|
50187
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
50188
|
+
notes: array(string$2())
|
|
50189
|
+
}), object({
|
|
50190
|
+
state: literal("insufficient"),
|
|
50191
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
50192
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
50193
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
50194
|
+
fallback: ThresholdTransformDraftSchema
|
|
50195
|
+
})]);
|
|
50196
|
+
/**
|
|
49359
50197
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
49360
50198
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
49361
50199
|
*
|
|
@@ -49645,6 +50483,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
49645
50483
|
kind: "mutation",
|
|
49646
50484
|
auth: "admin",
|
|
49647
50485
|
access: "view"
|
|
50486
|
+
}), method(object({
|
|
50487
|
+
deviceId: number().int(),
|
|
50488
|
+
cap: string$2().min(1),
|
|
50489
|
+
fieldPath: string$2().min(1),
|
|
50490
|
+
deviceType: _enum(DeviceType).optional()
|
|
50491
|
+
}), RunningThresholdProposalSchema, {
|
|
50492
|
+
auth: "admin",
|
|
50493
|
+
access: "view"
|
|
49648
50494
|
}), method(object({
|
|
49649
50495
|
blockId: string$2().min(1),
|
|
49650
50496
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -50600,7 +51446,27 @@ method(object({
|
|
|
50600
51446
|
* exposes only the COMMON surface: link state, supported device
|
|
50601
51447
|
* kinds (so the UI can filter the device picker), and per-device
|
|
50602
51448
|
* expose/unexpose.
|
|
50603
|
-
|
|
51449
|
+
*
|
|
51450
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
51451
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
51452
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
51453
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
51454
|
+
* default instance's); present = that instance; an id the provider does not
|
|
51455
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
51456
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
51457
|
+
* change always names the target it changes. A single-instance provider
|
|
51458
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
51459
|
+
*/
|
|
51460
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
51461
|
+
var InstanceIdSchema$1 = string$2().min(1);
|
|
51462
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
51463
|
+
/** One independent export target of an addon. */
|
|
51464
|
+
var ExportInstanceSchema = object({
|
|
51465
|
+
instanceId: InstanceIdSchema$1,
|
|
51466
|
+
name: string$2(),
|
|
51467
|
+
enabled: boolean(),
|
|
51468
|
+
kind: string$2().optional()
|
|
51469
|
+
});
|
|
50604
51470
|
var LinkStateSchema = _enum([
|
|
50605
51471
|
"unlinked",
|
|
50606
51472
|
"linked",
|
|
@@ -50651,10 +51517,14 @@ var ExposedDeviceSchema = object({
|
|
|
50651
51517
|
capabilities: array(string$2()).optional()
|
|
50652
51518
|
});
|
|
50653
51519
|
var ExposeInputSchema = object({
|
|
51520
|
+
instanceId: InstanceIdSchema$1,
|
|
50654
51521
|
deviceId: string$2(),
|
|
50655
51522
|
capabilities: array(string$2()).optional()
|
|
50656
51523
|
});
|
|
50657
|
-
var UnexposeInputSchema = object({
|
|
51524
|
+
var UnexposeInputSchema = object({
|
|
51525
|
+
instanceId: InstanceIdSchema$1,
|
|
51526
|
+
deviceId: string$2()
|
|
51527
|
+
});
|
|
50658
51528
|
/**
|
|
50659
51529
|
* What an exporter may expose besides devices (D968). An action and a
|
|
50660
51530
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -50683,15 +51553,21 @@ var ExposedEntitySchema = object({
|
|
|
50683
51553
|
entity: ExportEntityRefSchema,
|
|
50684
51554
|
exposedAs: string$2().optional()
|
|
50685
51555
|
});
|
|
50686
|
-
var ExposeEntityInputSchema = object({
|
|
50687
|
-
|
|
50688
|
-
|
|
51556
|
+
var ExposeEntityInputSchema = object({
|
|
51557
|
+
instanceId: InstanceIdSchema$1,
|
|
51558
|
+
entity: ExportEntityRefSchema
|
|
51559
|
+
});
|
|
51560
|
+
var UnexposeEntityInputSchema = object({
|
|
51561
|
+
instanceId: InstanceIdSchema$1,
|
|
51562
|
+
entity: ExportEntityRefSchema
|
|
51563
|
+
});
|
|
51564
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
50689
51565
|
kind: "mutation",
|
|
50690
51566
|
auth: "admin"
|
|
50691
51567
|
}), method(UnexposeInputSchema, _void(), {
|
|
50692
51568
|
kind: "mutation",
|
|
50693
51569
|
auth: "admin"
|
|
50694
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(
|
|
51570
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
50695
51571
|
kind: "mutation",
|
|
50696
51572
|
auth: "admin"
|
|
50697
51573
|
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
@@ -52693,7 +53569,7 @@ var DishwasherStatusSchema = object({
|
|
|
52693
53569
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
52694
53570
|
lastFetchedAt: number()
|
|
52695
53571
|
});
|
|
52696
|
-
var DeviceIdSchema$
|
|
53572
|
+
var DeviceIdSchema$2 = number().int().nonnegative();
|
|
52697
53573
|
/** A value a program option or setting can take. */
|
|
52698
53574
|
var DishwasherValueSchema = union([
|
|
52699
53575
|
boolean(),
|
|
@@ -52768,77 +53644,77 @@ var dishwasherCapability = {
|
|
|
52768
53644
|
* option feature keys; each is checked against the program's constraints.
|
|
52769
53645
|
*/
|
|
52770
53646
|
start: method(object({
|
|
52771
|
-
deviceId: DeviceIdSchema$
|
|
53647
|
+
deviceId: DeviceIdSchema$2,
|
|
52772
53648
|
program: string$2().min(1).optional(),
|
|
52773
53649
|
options: record(string$2(), DishwasherValueSchema).optional()
|
|
52774
53650
|
}), _void(), {
|
|
52775
53651
|
kind: "mutation",
|
|
52776
53652
|
auth: "protected"
|
|
52777
53653
|
}),
|
|
52778
|
-
pause: method(object({ deviceId: DeviceIdSchema$
|
|
53654
|
+
pause: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52779
53655
|
kind: "mutation",
|
|
52780
53656
|
auth: "protected"
|
|
52781
53657
|
}),
|
|
52782
|
-
resume: method(object({ deviceId: DeviceIdSchema$
|
|
53658
|
+
resume: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52783
53659
|
kind: "mutation",
|
|
52784
53660
|
auth: "protected"
|
|
52785
53661
|
}),
|
|
52786
|
-
stop: method(object({ deviceId: DeviceIdSchema$
|
|
53662
|
+
stop: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
|
|
52787
53663
|
kind: "mutation",
|
|
52788
53664
|
auth: "protected"
|
|
52789
53665
|
}),
|
|
52790
53666
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
52791
53667
|
setProgram: method(object({
|
|
52792
|
-
deviceId: DeviceIdSchema$
|
|
53668
|
+
deviceId: DeviceIdSchema$2,
|
|
52793
53669
|
program: string$2().min(1)
|
|
52794
53670
|
}), _void(), {
|
|
52795
53671
|
kind: "mutation",
|
|
52796
53672
|
auth: "protected"
|
|
52797
53673
|
}),
|
|
52798
53674
|
setPower: method(object({
|
|
52799
|
-
deviceId: DeviceIdSchema$
|
|
53675
|
+
deviceId: DeviceIdSchema$2,
|
|
52800
53676
|
on: boolean()
|
|
52801
53677
|
}), _void(), {
|
|
52802
53678
|
kind: "mutation",
|
|
52803
53679
|
auth: "protected"
|
|
52804
53680
|
}),
|
|
52805
53681
|
setHalfLoad: method(object({
|
|
52806
|
-
deviceId: DeviceIdSchema$
|
|
53682
|
+
deviceId: DeviceIdSchema$2,
|
|
52807
53683
|
on: boolean()
|
|
52808
53684
|
}), _void(), {
|
|
52809
53685
|
kind: "mutation",
|
|
52810
53686
|
auth: "protected"
|
|
52811
53687
|
}),
|
|
52812
53688
|
setHygienePlus: method(object({
|
|
52813
|
-
deviceId: DeviceIdSchema$
|
|
53689
|
+
deviceId: DeviceIdSchema$2,
|
|
52814
53690
|
on: boolean()
|
|
52815
53691
|
}), _void(), {
|
|
52816
53692
|
kind: "mutation",
|
|
52817
53693
|
auth: "protected"
|
|
52818
53694
|
}),
|
|
52819
53695
|
setExtraDry: method(object({
|
|
52820
|
-
deviceId: DeviceIdSchema$
|
|
53696
|
+
deviceId: DeviceIdSchema$2,
|
|
52821
53697
|
on: boolean()
|
|
52822
53698
|
}), _void(), {
|
|
52823
53699
|
kind: "mutation",
|
|
52824
53700
|
auth: "protected"
|
|
52825
53701
|
}),
|
|
52826
53702
|
setIntensiveZone: method(object({
|
|
52827
|
-
deviceId: DeviceIdSchema$
|
|
53703
|
+
deviceId: DeviceIdSchema$2,
|
|
52828
53704
|
on: boolean()
|
|
52829
53705
|
}), _void(), {
|
|
52830
53706
|
kind: "mutation",
|
|
52831
53707
|
auth: "protected"
|
|
52832
53708
|
}),
|
|
52833
53709
|
setVarioSpeedPlus: method(object({
|
|
52834
|
-
deviceId: DeviceIdSchema$
|
|
53710
|
+
deviceId: DeviceIdSchema$2,
|
|
52835
53711
|
on: boolean()
|
|
52836
53712
|
}), _void(), {
|
|
52837
53713
|
kind: "mutation",
|
|
52838
53714
|
auth: "protected"
|
|
52839
53715
|
}),
|
|
52840
53716
|
setChildLock: method(object({
|
|
52841
|
-
deviceId: DeviceIdSchema$
|
|
53717
|
+
deviceId: DeviceIdSchema$2,
|
|
52842
53718
|
on: boolean()
|
|
52843
53719
|
}), _void(), {
|
|
52844
53720
|
kind: "mutation",
|
|
@@ -52850,7 +53726,7 @@ var dishwasherCapability = {
|
|
|
52850
53726
|
* its declared range / members.
|
|
52851
53727
|
*/
|
|
52852
53728
|
setOption: method(object({
|
|
52853
|
-
deviceId: DeviceIdSchema$
|
|
53729
|
+
deviceId: DeviceIdSchema$2,
|
|
52854
53730
|
key: string$2().min(1),
|
|
52855
53731
|
value: DishwasherValueSchema
|
|
52856
53732
|
}), _void(), {
|
|
@@ -52858,7 +53734,7 @@ var dishwasherCapability = {
|
|
|
52858
53734
|
auth: "protected"
|
|
52859
53735
|
}),
|
|
52860
53736
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
52861
|
-
getOptions: method(object({ deviceId: DeviceIdSchema$
|
|
53737
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
52862
53738
|
},
|
|
52863
53739
|
status: {
|
|
52864
53740
|
schema: DishwasherStatusSchema,
|
|
@@ -54282,15 +55158,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
54282
55158
|
items: array(FanCaptureItemSchema),
|
|
54283
55159
|
omitted: array(FanCaptureOmissionSchema)
|
|
54284
55160
|
});
|
|
54285
|
-
var deviceInput$
|
|
55161
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
54286
55162
|
DeviceType.Fan;
|
|
54287
|
-
method(deviceInput$
|
|
55163
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
54288
55164
|
kind: "mutation",
|
|
54289
55165
|
auth: "admin"
|
|
54290
|
-
}), method(deviceInput$
|
|
55166
|
+
}), method(deviceInput$2.extend({ presetId: string$2().min(1) }), FanPresetApplyResultSchema, {
|
|
54291
55167
|
kind: "mutation",
|
|
54292
55168
|
auth: "protected"
|
|
54293
|
-
}), method(deviceInput$
|
|
55169
|
+
}), method(deviceInput$2.extend({
|
|
54294
55170
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
54295
55171
|
steps: array(ActionStepSchema).max(20).default([])
|
|
54296
55172
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -55201,13 +56077,50 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
|
|
|
55201
56077
|
*/
|
|
55202
56078
|
var ConfigSchemaPassthrough$3 = custom();
|
|
55203
56079
|
var IntegrationInstanceIdSchema = string$2().min(1);
|
|
56080
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56081
|
+
/**
|
|
56082
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56083
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56084
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
56085
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
56086
|
+
* instance id.
|
|
56087
|
+
*/
|
|
56088
|
+
var IntegrationInstanceKindSchema = object({
|
|
56089
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56090
|
+
id: string$2().min(1),
|
|
56091
|
+
label: string$2().min(1),
|
|
56092
|
+
/** The kind signs in through a server-held interactive session. */
|
|
56093
|
+
signIn: object({
|
|
56094
|
+
basePath: string$2().min(1),
|
|
56095
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
56096
|
+
}).optional(),
|
|
56097
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
56098
|
+
connectPath: string$2().min(1).optional(),
|
|
56099
|
+
/** Relative hub path to disconnect that account. */
|
|
56100
|
+
disconnectPath: string$2().min(1).optional()
|
|
56101
|
+
});
|
|
55204
56102
|
var IntegrationInstanceDescriptionSchema = object({
|
|
55205
56103
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
55206
|
-
instanceMode:
|
|
56104
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
55207
56105
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
55208
56106
|
kind: string$2(),
|
|
55209
56107
|
/** True when `testInstance` really probes the remote. */
|
|
55210
|
-
canTest: boolean()
|
|
56108
|
+
canTest: boolean(),
|
|
56109
|
+
/**
|
|
56110
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
56111
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
56112
|
+
*/
|
|
56113
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
56114
|
+
/**
|
|
56115
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
56116
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
56117
|
+
*/
|
|
56118
|
+
allowRestoreId: boolean().optional(),
|
|
56119
|
+
/**
|
|
56120
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56121
|
+
* send an id (the UI then mints an opaque one).
|
|
56122
|
+
*/
|
|
56123
|
+
mintsId: boolean().optional()
|
|
55211
56124
|
});
|
|
55212
56125
|
var IntegrationInstanceStatusSchema = object({
|
|
55213
56126
|
state: _enum([
|
|
@@ -55216,16 +56129,27 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
55216
56129
|
"error",
|
|
55217
56130
|
"unknown"
|
|
55218
56131
|
]),
|
|
55219
|
-
detail: string$2().optional()
|
|
56132
|
+
detail: string$2().optional(),
|
|
56133
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
56134
|
+
label: string$2().optional()
|
|
55220
56135
|
});
|
|
55221
56136
|
var IntegrationInstanceSchema = object({
|
|
55222
56137
|
id: IntegrationInstanceIdSchema,
|
|
55223
56138
|
name: string$2(),
|
|
55224
56139
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
55225
56140
|
configured: boolean(),
|
|
56141
|
+
/**
|
|
56142
|
+
* The instance's own on/off switch (D996): always present, derived from the record body
|
|
56143
|
+
* (`enabled !== false`, so a row written before the field existed is on).
|
|
56144
|
+
*/
|
|
56145
|
+
enabled: boolean(),
|
|
56146
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
56147
|
+
kind: string$2().optional(),
|
|
55226
56148
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
55227
56149
|
formValues: record(string$2(), unknown()),
|
|
55228
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
56150
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
56151
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
56152
|
+
lastCheckedAt: number().optional()
|
|
55229
56153
|
});
|
|
55230
56154
|
var IntegrationInstancePatchSchema = object({
|
|
55231
56155
|
id: IntegrationInstanceIdSchema,
|
|
@@ -55233,25 +56157,57 @@ var IntegrationInstancePatchSchema = object({
|
|
|
55233
56157
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
55234
56158
|
values: record(string$2(), unknown())
|
|
55235
56159
|
});
|
|
55236
|
-
|
|
55237
|
-
|
|
55238
|
-
|
|
55239
|
-
|
|
55240
|
-
|
|
55241
|
-
|
|
55242
|
-
|
|
55243
|
-
|
|
55244
|
-
|
|
55245
|
-
|
|
55246
|
-
|
|
55247
|
-
|
|
55248
|
-
|
|
55249
|
-
|
|
55250
|
-
|
|
55251
|
-
|
|
55252
|
-
|
|
55253
|
-
|
|
55254
|
-
})
|
|
56160
|
+
var integrationInstancesCapability = {
|
|
56161
|
+
name: "integration-instances",
|
|
56162
|
+
scope: "system",
|
|
56163
|
+
mode: "collection",
|
|
56164
|
+
methods: {
|
|
56165
|
+
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56166
|
+
/** The form; `id: null` is the create form. */
|
|
56167
|
+
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56168
|
+
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56169
|
+
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56170
|
+
/**
|
|
56171
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
56172
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
56173
|
+
*/
|
|
56174
|
+
createInstance: method(object({
|
|
56175
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
56176
|
+
name: string$2().min(1),
|
|
56177
|
+
kind: string$2().min(1).optional()
|
|
56178
|
+
}), object({ id: string$2() }), {
|
|
56179
|
+
kind: "mutation",
|
|
56180
|
+
auth: "admin"
|
|
56181
|
+
}),
|
|
56182
|
+
updateInstance: method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
56183
|
+
kind: "mutation",
|
|
56184
|
+
auth: "admin"
|
|
56185
|
+
}),
|
|
56186
|
+
/**
|
|
56187
|
+
* Switch ONE instance on or off. A row control, not a form field: `updateInstance.values` is the
|
|
56188
|
+
* form patch (D62, D996). An off instance keeps its record and secrets and does no work.
|
|
56189
|
+
*/
|
|
56190
|
+
setInstanceEnabled: method(object({
|
|
56191
|
+
id: IntegrationInstanceIdSchema,
|
|
56192
|
+
enabled: boolean()
|
|
56193
|
+
}), object({ ok: literal(true) }), {
|
|
56194
|
+
kind: "mutation",
|
|
56195
|
+
auth: "admin"
|
|
56196
|
+
}),
|
|
56197
|
+
/** Delete the record and its keychain refs together. */
|
|
56198
|
+
deleteInstance: method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
56199
|
+
kind: "mutation",
|
|
56200
|
+
auth: "admin"
|
|
56201
|
+
}),
|
|
56202
|
+
testInstance: optionalMethod(object({
|
|
56203
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56204
|
+
values: record(string$2(), unknown())
|
|
56205
|
+
}), TestConnectionResultSchema$1, {
|
|
56206
|
+
kind: "mutation",
|
|
56207
|
+
auth: "admin"
|
|
56208
|
+
})
|
|
56209
|
+
}
|
|
56210
|
+
};
|
|
55255
56211
|
/**
|
|
55256
56212
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
55257
56213
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -57269,6 +58225,11 @@ var LogEntrySchema = object({
|
|
|
57269
58225
|
* flight, and not asking for the next slice ends the scan.
|
|
57270
58226
|
*/
|
|
57271
58227
|
var LogReadQuerySchema = object({
|
|
58228
|
+
/**
|
|
58229
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
58230
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
58231
|
+
*/
|
|
58232
|
+
instanceId: string$2().min(1).max(200).optional(),
|
|
57272
58233
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
57273
58234
|
requestId: string$2().min(1).max(64).optional(),
|
|
57274
58235
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -57331,6 +58292,8 @@ var LogReadResultSchema = object({
|
|
|
57331
58292
|
/** True when `cancelRead` ended this slice early. */
|
|
57332
58293
|
cancelled: boolean().optional()
|
|
57333
58294
|
});
|
|
58295
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
58296
|
+
var LogInstanceSelectorSchema = object({ instanceId: string$2().min(1).max(200).optional() });
|
|
57334
58297
|
var LogReadCapabilitiesSchema = object({
|
|
57335
58298
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
57336
58299
|
readable: boolean(),
|
|
@@ -57349,7 +58312,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
57349
58312
|
/** How far back the store reaches, when the provider knows (days). */
|
|
57350
58313
|
retentionDays: number().nullable()
|
|
57351
58314
|
});
|
|
57352
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
58315
|
+
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() }), {
|
|
57353
58316
|
kind: "mutation",
|
|
57354
58317
|
auth: "admin"
|
|
57355
58318
|
});
|
|
@@ -57453,214 +58416,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
|
|
|
57453
58416
|
]);
|
|
57454
58417
|
method(_void(), array(LoginMethodContributionSchema).readonly());
|
|
57455
58418
|
/**
|
|
57456
|
-
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
57457
|
-
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
57458
|
-
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
57459
|
-
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
57460
|
-
*
|
|
57461
|
-
* The cap declares a wide method surface; not every backend supports
|
|
57462
|
-
* every method. Drivers signal sub-capability via the
|
|
57463
|
-
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
57464
|
-
* corresponding method) so the UI gates buttons without a slice
|
|
57465
|
-
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
57466
|
-
*/
|
|
57467
|
-
var MediaPlayerStateSchema = _enum([
|
|
57468
|
-
"off",
|
|
57469
|
-
"on",
|
|
57470
|
-
"idle",
|
|
57471
|
-
"playing",
|
|
57472
|
-
"paused",
|
|
57473
|
-
"buffering",
|
|
57474
|
-
"standby"
|
|
57475
|
-
]);
|
|
57476
|
-
var MediaPlayerRepeatSchema = _enum([
|
|
57477
|
-
"off",
|
|
57478
|
-
"all",
|
|
57479
|
-
"one"
|
|
57480
|
-
]);
|
|
57481
|
-
var MediaInfoSchema = object({
|
|
57482
|
-
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
57483
|
-
* `podcast`, …). */
|
|
57484
|
-
type: string$2(),
|
|
57485
|
-
/** Human-readable title. */
|
|
57486
|
-
title: string$2(),
|
|
57487
|
-
/** Optional artist / channel / station label. */
|
|
57488
|
-
artist: string$2().optional(),
|
|
57489
|
-
/** Optional album / season / show name. */
|
|
57490
|
-
album: string$2().optional(),
|
|
57491
|
-
/** Optional cover-art / thumbnail URL. */
|
|
57492
|
-
imageUrl: string$2().optional()
|
|
57493
|
-
});
|
|
57494
|
-
/**
|
|
57495
|
-
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
57496
|
-
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
57497
|
-
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
57498
|
-
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
57499
|
-
* guess, never as a reading.
|
|
57500
|
-
*/
|
|
57501
|
-
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
57502
|
-
/** The fields of a media-player status a driver can only guess. */
|
|
57503
|
-
var MediaPlayerAssumedFieldSchema = _enum([
|
|
57504
|
-
"state",
|
|
57505
|
-
"volumeLevel",
|
|
57506
|
-
"isMuted",
|
|
57507
|
-
"source"
|
|
57508
|
-
]);
|
|
57509
|
-
var MediaPlayerStatusSchema = object({
|
|
57510
|
-
/** Playback lifecycle state. */
|
|
57511
|
-
state: MediaPlayerStateSchema,
|
|
57512
|
-
/**
|
|
57513
|
-
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
57514
|
-
* A slice written before this field existed parses as `reported`.
|
|
57515
|
-
*/
|
|
57516
|
-
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
57517
|
-
/**
|
|
57518
|
-
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
57519
|
-
* this is not empty; a field not listed here is a reading even then (power
|
|
57520
|
-
* from a running-state source, mute guessed from the last send).
|
|
57521
|
-
*/
|
|
57522
|
-
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
57523
|
-
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
57524
|
-
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
57525
|
-
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
57526
|
-
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
57527
|
-
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
57528
|
-
isMuted: boolean().nullable(),
|
|
57529
|
-
/** Active source / input. Empty when no source surface. */
|
|
57530
|
-
source: string$2(),
|
|
57531
|
-
/** Selectable sources. Empty when no source surface.
|
|
57532
|
-
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
57533
|
-
availableSources: array(string$2()),
|
|
57534
|
-
/** Currently-playing media info. Null when nothing is playing. */
|
|
57535
|
-
currentMedia: MediaInfoSchema.nullable(),
|
|
57536
|
-
/** Current playback position in ms. Null when not seekable or
|
|
57537
|
-
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
57538
|
-
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57539
|
-
/** Total duration of the current media in ms. Null when unknown
|
|
57540
|
-
* (live stream) or nothing is playing. */
|
|
57541
|
-
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57542
|
-
/** Shuffle toggle. Null when no shuffle surface.
|
|
57543
|
-
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
57544
|
-
shuffle: boolean().nullable(),
|
|
57545
|
-
/** Repeat mode. Null when no repeat surface.
|
|
57546
|
-
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
57547
|
-
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
57548
|
-
/** Ms epoch when the slice was last updated. */
|
|
57549
|
-
lastChangedAt: number()
|
|
57550
|
-
});
|
|
57551
|
-
var mediaPlayerCapability = {
|
|
57552
|
-
name: "media-player",
|
|
57553
|
-
scope: "device",
|
|
57554
|
-
deviceNative: true,
|
|
57555
|
-
mode: "singleton",
|
|
57556
|
-
deviceTypes: [DeviceType.MediaPlayer],
|
|
57557
|
-
methods: {
|
|
57558
|
-
play: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57559
|
-
kind: "mutation",
|
|
57560
|
-
auth: "protected"
|
|
57561
|
-
}),
|
|
57562
|
-
pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57563
|
-
kind: "mutation",
|
|
57564
|
-
auth: "protected"
|
|
57565
|
-
}),
|
|
57566
|
-
stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57567
|
-
kind: "mutation",
|
|
57568
|
-
auth: "protected"
|
|
57569
|
-
}),
|
|
57570
|
-
next: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57571
|
-
kind: "mutation",
|
|
57572
|
-
auth: "protected"
|
|
57573
|
-
}),
|
|
57574
|
-
previous: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
57575
|
-
kind: "mutation",
|
|
57576
|
-
auth: "protected"
|
|
57577
|
-
}),
|
|
57578
|
-
seek: method(object({
|
|
57579
|
-
deviceId: number().int().nonnegative(),
|
|
57580
|
-
position: number().int().nonnegative()
|
|
57581
|
-
}), _void(), {
|
|
57582
|
-
kind: "mutation",
|
|
57583
|
-
auth: "protected"
|
|
57584
|
-
}),
|
|
57585
|
-
setVolume: method(object({
|
|
57586
|
-
deviceId: number().int().nonnegative(),
|
|
57587
|
-
volumeLevel: number().min(0).max(100)
|
|
57588
|
-
}), _void(), {
|
|
57589
|
-
kind: "mutation",
|
|
57590
|
-
auth: "protected"
|
|
57591
|
-
}),
|
|
57592
|
-
setMute: method(object({
|
|
57593
|
-
deviceId: number().int().nonnegative(),
|
|
57594
|
-
muted: boolean()
|
|
57595
|
-
}), _void(), {
|
|
57596
|
-
kind: "mutation",
|
|
57597
|
-
auth: "protected"
|
|
57598
|
-
}),
|
|
57599
|
-
setShuffle: method(object({
|
|
57600
|
-
deviceId: number().int().nonnegative(),
|
|
57601
|
-
shuffle: boolean()
|
|
57602
|
-
}), _void(), {
|
|
57603
|
-
kind: "mutation",
|
|
57604
|
-
auth: "protected"
|
|
57605
|
-
}),
|
|
57606
|
-
setRepeat: method(object({
|
|
57607
|
-
deviceId: number().int().nonnegative(),
|
|
57608
|
-
repeat: MediaPlayerRepeatSchema
|
|
57609
|
-
}), _void(), {
|
|
57610
|
-
kind: "mutation",
|
|
57611
|
-
auth: "protected"
|
|
57612
|
-
}),
|
|
57613
|
-
selectSource: method(object({
|
|
57614
|
-
deviceId: number().int().nonnegative(),
|
|
57615
|
-
source: string$2().min(1)
|
|
57616
|
-
}), _void(), {
|
|
57617
|
-
kind: "mutation",
|
|
57618
|
-
auth: "protected"
|
|
57619
|
-
}),
|
|
57620
|
-
playMedia: method(object({
|
|
57621
|
-
deviceId: number().int().nonnegative(),
|
|
57622
|
-
/** Media identifier / URL. */
|
|
57623
|
-
mediaId: string$2().min(1),
|
|
57624
|
-
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
57625
|
-
* passes it through to the upstream service. */
|
|
57626
|
-
mediaType: string$2().min(1)
|
|
57627
|
-
}), _void(), {
|
|
57628
|
-
kind: "mutation",
|
|
57629
|
-
auth: "protected"
|
|
57630
|
-
})
|
|
57631
|
-
},
|
|
57632
|
-
status: {
|
|
57633
|
-
schema: MediaPlayerStatusSchema,
|
|
57634
|
-
kind: "push"
|
|
57635
|
-
},
|
|
57636
|
-
/**
|
|
57637
|
-
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
57638
|
-
* full slice for live now-playing, volume, and progress updates
|
|
57639
|
-
* without polling.
|
|
57640
|
-
*/
|
|
57641
|
-
runtimeState: MediaPlayerStatusSchema,
|
|
57642
|
-
/**
|
|
57643
|
-
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
57644
|
-
*
|
|
57645
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
57646
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
57647
|
-
*/
|
|
57648
|
-
durability: "session",
|
|
57649
|
-
/**
|
|
57650
|
-
* History (D850): volume (a level an operator or an automation sets; the
|
|
57651
|
-
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
57652
|
-
* `position` is not recorded: it moves continuously while playing.
|
|
57653
|
-
*/
|
|
57654
|
-
history: { fields: {
|
|
57655
|
-
volumeLevel: {
|
|
57656
|
-
kind: "gauge",
|
|
57657
|
-
unit: "%",
|
|
57658
|
-
deadband: 1
|
|
57659
|
-
},
|
|
57660
|
-
isMuted: { kind: "boolean" }
|
|
57661
|
-
} }
|
|
57662
|
-
};
|
|
57663
|
-
/**
|
|
57664
58419
|
* mesh-network — collection cap for mesh-VPN providers.
|
|
57665
58420
|
*
|
|
57666
58421
|
* Different shape from `network-access` (public tunnels):
|
|
@@ -66419,6 +67174,21 @@ var NcTemplateVarKindSchema = _enum([
|
|
|
66419
67174
|
"sound",
|
|
66420
67175
|
"system"
|
|
66421
67176
|
]);
|
|
67177
|
+
/**
|
|
67178
|
+
* The default title/body a rule of one SITUATION is delivered with when the
|
|
67179
|
+
* operator wrote no template — served so an editor can pre-fill it (and the
|
|
67180
|
+
* operator can rebuild it from placeholders) instead of starting from a blank
|
|
67181
|
+
* box that, once saved, silently replaces it. `id` names the situation inside
|
|
67182
|
+
* a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
|
|
67183
|
+
* appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
|
|
67184
|
+
* the hub's current language and with its overrides applied.
|
|
67185
|
+
*/
|
|
67186
|
+
var NcTemplateDefaultSchema = object({
|
|
67187
|
+
id: _enum(["appliance-cycle"]),
|
|
67188
|
+
kind: NcRuleKindSchema,
|
|
67189
|
+
title: string$2(),
|
|
67190
|
+
body: string$2()
|
|
67191
|
+
});
|
|
66422
67192
|
/** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
|
|
66423
67193
|
var NcTemplateFamilySchema = _enum([
|
|
66424
67194
|
"rule",
|
|
@@ -67274,7 +68044,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
67274
68044
|
}), method(object({}), object({
|
|
67275
68045
|
catalog: array(NcConditionDescriptorSchema),
|
|
67276
68046
|
taxonomy: NcTaxonomySchema.optional()
|
|
67277
|
-
})), method(object({}), object({
|
|
68047
|
+
})), method(object({}), object({
|
|
68048
|
+
vars: array(NcTemplateVarDescriptorSchema),
|
|
68049
|
+
/** Per-situation default texts; absent on a hub that predates them. */
|
|
68050
|
+
defaults: array(NcTemplateDefaultSchema)
|
|
68051
|
+
})), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
|
|
67278
68052
|
kind: "mutation",
|
|
67279
68053
|
access: "view"
|
|
67280
68054
|
}), 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 }), {
|
|
@@ -68170,7 +68944,7 @@ var petFeederCapability = {
|
|
|
68170
68944
|
/**
|
|
68171
68945
|
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
68172
68946
|
* circulating drinking fountain: how much water is in the tank, how much the
|
|
68173
|
-
* pet drank, whether the pump runs
|
|
68947
|
+
* pet drank, whether the pump runs.
|
|
68174
68948
|
*
|
|
68175
68949
|
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
68176
68950
|
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
@@ -68186,8 +68960,10 @@ var petFeederCapability = {
|
|
|
68186
68960
|
* never here. Every reading may be `null`: a source that is offline or does
|
|
68187
68961
|
* not report is unknown, never zero.
|
|
68188
68962
|
*
|
|
68189
|
-
* The
|
|
68190
|
-
* cap
|
|
68963
|
+
* The pump's on/off duty cycle is model-specific and lives in the optional
|
|
68964
|
+
* `pump-cycle` cap. The filter, the cleaning and the water change are not
|
|
68965
|
+
* fields of this cap: they are items of the `consumables` cap on the same
|
|
68966
|
+
* device (kind `wear`), reset by the same forward mechanism.
|
|
68191
68967
|
*/
|
|
68192
68968
|
var PetFountainStatusSchema = object({
|
|
68193
68969
|
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
@@ -68198,27 +68974,20 @@ var PetFountainStatusSchema = object({
|
|
|
68198
68974
|
empty: boolean().nullable(),
|
|
68199
68975
|
/** True while the pump is running. Null while unknown. */
|
|
68200
68976
|
pumpRunning: boolean().nullable(),
|
|
68201
|
-
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
68202
|
-
pumpIntervalEnabled: boolean().nullable(),
|
|
68203
|
-
/** How long the pump runs per cycle, in seconds. */
|
|
68204
|
-
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
68205
|
-
/** How long the pump rests per cycle, in seconds. */
|
|
68206
|
-
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
68207
68977
|
/** Water drunk in the last drink, in litres. */
|
|
68208
68978
|
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68209
68979
|
/** Water drunk in the current hour, in litres. */
|
|
68210
68980
|
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68211
68981
|
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
68212
68982
|
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68213
|
-
/** Water drunk since local midnight, in litres. */
|
|
68983
|
+
/** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
|
|
68214
68984
|
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
68215
68985
|
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
68216
68986
|
error: string$2().nullable(),
|
|
68217
68987
|
/** Ms epoch when the slice was last refreshed. */
|
|
68218
68988
|
lastFetchedAt: number()
|
|
68219
68989
|
});
|
|
68220
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
68221
|
-
var MinutesSchema = number().int().min(0);
|
|
68990
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
68222
68991
|
var petFountainCapability = {
|
|
68223
68992
|
name: "pet-fountain",
|
|
68224
68993
|
scope: "device",
|
|
@@ -68228,38 +68997,14 @@ var petFountainCapability = {
|
|
|
68228
68997
|
methods: {
|
|
68229
68998
|
/** Start or stop the pump. */
|
|
68230
68999
|
setPump: method(object({
|
|
68231
|
-
deviceId: DeviceIdSchema,
|
|
68232
|
-
on: boolean()
|
|
68233
|
-
}), _void(), {
|
|
68234
|
-
kind: "mutation",
|
|
68235
|
-
auth: "protected"
|
|
68236
|
-
}),
|
|
68237
|
-
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
68238
|
-
setPumpInterval: method(object({
|
|
68239
|
-
deviceId: DeviceIdSchema,
|
|
69000
|
+
deviceId: DeviceIdSchema$1,
|
|
68240
69001
|
on: boolean()
|
|
68241
69002
|
}), _void(), {
|
|
68242
69003
|
kind: "mutation",
|
|
68243
69004
|
auth: "protected"
|
|
68244
69005
|
}),
|
|
68245
|
-
/** Set how many minutes the pump runs per cycle. */
|
|
68246
|
-
setPumpOnMinutes: method(object({
|
|
68247
|
-
deviceId: DeviceIdSchema,
|
|
68248
|
-
minutes: MinutesSchema
|
|
68249
|
-
}), _void(), {
|
|
68250
|
-
kind: "mutation",
|
|
68251
|
-
auth: "protected"
|
|
68252
|
-
}),
|
|
68253
|
-
/** Set how many minutes the pump rests per cycle. */
|
|
68254
|
-
setPumpOffMinutes: method(object({
|
|
68255
|
-
deviceId: DeviceIdSchema,
|
|
68256
|
-
minutes: MinutesSchema
|
|
68257
|
-
}), _void(), {
|
|
68258
|
-
kind: "mutation",
|
|
68259
|
-
auth: "protected"
|
|
68260
|
-
}),
|
|
68261
69006
|
/** Clear the rolling daily consumption counters on the device. */
|
|
68262
|
-
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69007
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
68263
69008
|
kind: "mutation",
|
|
68264
69009
|
auth: "protected"
|
|
68265
69010
|
})
|
|
@@ -69894,6 +70639,14 @@ var powerMeterCapability = {
|
|
|
69894
70639
|
deviceNative: true,
|
|
69895
70640
|
mode: "singleton",
|
|
69896
70641
|
deviceTypes: POWER_METER_DEVICE_TYPES,
|
|
70642
|
+
deviceConfig: { ui: {
|
|
70643
|
+
kind: "widget",
|
|
70644
|
+
widgetId: "host/power-meter-line",
|
|
70645
|
+
tab: "power",
|
|
70646
|
+
placement: "hero",
|
|
70647
|
+
label: "Power",
|
|
70648
|
+
order: 4
|
|
70649
|
+
} },
|
|
69897
70650
|
methods: {},
|
|
69898
70651
|
status: {
|
|
69899
70652
|
schema: PowerMeterStatusSchema,
|
|
@@ -69953,6 +70706,117 @@ var powerMeterCapability = {
|
|
|
69953
70706
|
volatileStateFields: ["lastFetchedAt"],
|
|
69954
70707
|
customize: powerMeterCustomize
|
|
69955
70708
|
};
|
|
70709
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
70710
|
+
"no-power-source",
|
|
70711
|
+
"switch-missing",
|
|
70712
|
+
"switch-offline",
|
|
70713
|
+
"switch-unknown",
|
|
70714
|
+
"restart-in-progress",
|
|
70715
|
+
"switch-call-failed"
|
|
70716
|
+
]);
|
|
70717
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
70718
|
+
accepted: literal(false),
|
|
70719
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
70720
|
+
/** One line for the operator, naming the switch. */
|
|
70721
|
+
message: string$2()
|
|
70722
|
+
})]);
|
|
70723
|
+
var PowerSourceInfoSchema = object({
|
|
70724
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
70725
|
+
linked: boolean(),
|
|
70726
|
+
switchDeviceId: number().nullable(),
|
|
70727
|
+
switchName: string$2().nullable(),
|
|
70728
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
70729
|
+
switchOn: boolean().nullable(),
|
|
70730
|
+
switchOnline: boolean().nullable(),
|
|
70731
|
+
restartInProgress: boolean(),
|
|
70732
|
+
offSeconds: number().int()
|
|
70733
|
+
});
|
|
70734
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
70735
|
+
var PowerSourceStateSchema = object({
|
|
70736
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
70737
|
+
switchOn: boolean().nullable(),
|
|
70738
|
+
offSeconds: number().int(),
|
|
70739
|
+
lastFetchedAt: number()
|
|
70740
|
+
});
|
|
70741
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
70742
|
+
DeviceType.Gateway,
|
|
70743
|
+
DeviceType.Hub,
|
|
70744
|
+
DeviceType.Camera,
|
|
70745
|
+
DeviceType.Sensor,
|
|
70746
|
+
DeviceType.MediaPlayer,
|
|
70747
|
+
DeviceType.Vacuum,
|
|
70748
|
+
DeviceType.Generic,
|
|
70749
|
+
DeviceType.Washer,
|
|
70750
|
+
DeviceType.Dryer,
|
|
70751
|
+
DeviceType.Dishwasher,
|
|
70752
|
+
DeviceType.PetFountain,
|
|
70753
|
+
DeviceType.Fan,
|
|
70754
|
+
DeviceType.Humidifier,
|
|
70755
|
+
DeviceType.Climate,
|
|
70756
|
+
DeviceType.WaterHeater
|
|
70757
|
+
];
|
|
70758
|
+
var powerSourceCustomize = {
|
|
70759
|
+
version: 1,
|
|
70760
|
+
targets: [{
|
|
70761
|
+
id: "switch",
|
|
70762
|
+
cardinality: "scalar",
|
|
70763
|
+
vocabulary: {
|
|
70764
|
+
cap: "power-source",
|
|
70765
|
+
stateField: "switchOn"
|
|
70766
|
+
},
|
|
70767
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70768
|
+
apply: "add",
|
|
70769
|
+
path: "switchOn",
|
|
70770
|
+
sources: [{
|
|
70771
|
+
kind: "switch",
|
|
70772
|
+
transforms: [{ kind: "identity" }]
|
|
70773
|
+
}],
|
|
70774
|
+
companions: [{
|
|
70775
|
+
kind: "constant",
|
|
70776
|
+
path: "offSeconds",
|
|
70777
|
+
value: 10,
|
|
70778
|
+
when: "add"
|
|
70779
|
+
}, {
|
|
70780
|
+
kind: "clock",
|
|
70781
|
+
path: "lastFetchedAt",
|
|
70782
|
+
when: "always"
|
|
70783
|
+
}],
|
|
70784
|
+
sourceReport: { status: "assignment-status" }
|
|
70785
|
+
}]
|
|
70786
|
+
};
|
|
70787
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
70788
|
+
var powerSourceCapability = {
|
|
70789
|
+
name: "power-source",
|
|
70790
|
+
scope: "device",
|
|
70791
|
+
mode: "singleton",
|
|
70792
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
70793
|
+
methods: {
|
|
70794
|
+
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
|
|
70795
|
+
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70796
|
+
kind: "mutation",
|
|
70797
|
+
auth: "admin"
|
|
70798
|
+
}),
|
|
70799
|
+
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70800
|
+
kind: "mutation",
|
|
70801
|
+
auth: "admin"
|
|
70802
|
+
}),
|
|
70803
|
+
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
70804
|
+
kind: "mutation",
|
|
70805
|
+
auth: "admin"
|
|
70806
|
+
})
|
|
70807
|
+
},
|
|
70808
|
+
runtimeState: PowerSourceStateSchema,
|
|
70809
|
+
customize: powerSourceCustomize,
|
|
70810
|
+
/**
|
|
70811
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
70812
|
+
* its next report; a restored value would outlive the reading it names.
|
|
70813
|
+
*
|
|
70814
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
70815
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
70816
|
+
*/
|
|
70817
|
+
durability: "session",
|
|
70818
|
+
history: "none"
|
|
70819
|
+
};
|
|
69956
70820
|
/**
|
|
69957
70821
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
69958
70822
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -70592,6 +71456,79 @@ var ptzAutotrackCapability = {
|
|
|
70592
71456
|
*/
|
|
70593
71457
|
history: "none"
|
|
70594
71458
|
};
|
|
71459
|
+
/**
|
|
71460
|
+
* The on/off duty cycle of a pet fountain's pump. Some models run the pump
|
|
71461
|
+
* continuously and have nothing to configure; others can alternate a run time
|
|
71462
|
+
* and a rest time. That is model-specific, so it is its own optional cap on the
|
|
71463
|
+
* fountain device: a fountain without cycle entities simply never composes it
|
|
71464
|
+
* (planning#366), and the `pet-fountain` cap stays the model-independent core.
|
|
71465
|
+
*
|
|
71466
|
+
* Like `pet-fountain` it has no native vendor provider: an operator BUILDS the
|
|
71467
|
+
* cycle from loose entities (a cycle switch and two number boxes) and every
|
|
71468
|
+
* method is a forward to one source entity (D659, D967). Durations are stored
|
|
71469
|
+
* in SECONDS (D944); a number box that cannot be read back leaves them `null`
|
|
71470
|
+
* — unknown, never zero (D393).
|
|
71471
|
+
*/
|
|
71472
|
+
var PumpCycleStatusSchema = object({
|
|
71473
|
+
/** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
|
|
71474
|
+
enabled: boolean().nullable(),
|
|
71475
|
+
/** How long the pump runs per cycle, in seconds. Null while unknown. */
|
|
71476
|
+
onTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
71477
|
+
/** How long the pump rests per cycle, in seconds. Null while unknown. */
|
|
71478
|
+
offTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
71479
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
71480
|
+
lastFetchedAt: number()
|
|
71481
|
+
});
|
|
71482
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
71483
|
+
var MinutesSchema = number().int().min(0);
|
|
71484
|
+
var pumpCycleCapability = {
|
|
71485
|
+
name: "pump-cycle",
|
|
71486
|
+
scope: "device",
|
|
71487
|
+
deviceNative: true,
|
|
71488
|
+
mode: "singleton",
|
|
71489
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
71490
|
+
methods: {
|
|
71491
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
71492
|
+
setEnabled: method(object({
|
|
71493
|
+
deviceId: DeviceIdSchema,
|
|
71494
|
+
on: boolean()
|
|
71495
|
+
}), _void(), {
|
|
71496
|
+
kind: "mutation",
|
|
71497
|
+
auth: "protected"
|
|
71498
|
+
}),
|
|
71499
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
71500
|
+
setOnMinutes: method(object({
|
|
71501
|
+
deviceId: DeviceIdSchema,
|
|
71502
|
+
minutes: MinutesSchema
|
|
71503
|
+
}), _void(), {
|
|
71504
|
+
kind: "mutation",
|
|
71505
|
+
auth: "protected"
|
|
71506
|
+
}),
|
|
71507
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
71508
|
+
setOffMinutes: method(object({
|
|
71509
|
+
deviceId: DeviceIdSchema,
|
|
71510
|
+
minutes: MinutesSchema
|
|
71511
|
+
}), _void(), {
|
|
71512
|
+
kind: "mutation",
|
|
71513
|
+
auth: "protected"
|
|
71514
|
+
})
|
|
71515
|
+
},
|
|
71516
|
+
status: {
|
|
71517
|
+
schema: PumpCycleStatusSchema,
|
|
71518
|
+
kind: "push"
|
|
71519
|
+
},
|
|
71520
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
71521
|
+
runtimeState: PumpCycleStatusSchema,
|
|
71522
|
+
/**
|
|
71523
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
71524
|
+
*
|
|
71525
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
71526
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
71527
|
+
*/
|
|
71528
|
+
durability: "session",
|
|
71529
|
+
/** History (D850): a duty-cycle setting is not a series worth recording. */
|
|
71530
|
+
history: "none"
|
|
71531
|
+
};
|
|
70595
71532
|
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
70596
71533
|
kind: "mutation",
|
|
70597
71534
|
auth: "admin"
|
|
@@ -73382,7 +74319,13 @@ var RemoteMediaCommandSchema = _enum([
|
|
|
73382
74319
|
"next",
|
|
73383
74320
|
"previous",
|
|
73384
74321
|
"setMute",
|
|
73385
|
-
"selectSource"
|
|
74322
|
+
"selectSource",
|
|
74323
|
+
"turnOn",
|
|
74324
|
+
"turnOff",
|
|
74325
|
+
"volumeUp",
|
|
74326
|
+
"volumeDown",
|
|
74327
|
+
"channelUp",
|
|
74328
|
+
"channelDown"
|
|
73386
74329
|
]);
|
|
73387
74330
|
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
73388
74331
|
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
@@ -73403,7 +74346,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
|
|
|
73403
74346
|
}), RemoteSendResultSchema, {
|
|
73404
74347
|
kind: "mutation",
|
|
73405
74348
|
auth: "protected"
|
|
73406
|
-
}), method(deviceInput.extend({
|
|
74349
|
+
}), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
|
|
73407
74350
|
command: RemoteMediaCommandSchema,
|
|
73408
74351
|
muted: boolean().optional(),
|
|
73409
74352
|
source: string$2().optional(),
|
|
@@ -75649,7 +76592,11 @@ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
|
|
|
75649
76592
|
* `getSettingsUISchema()` surface.
|
|
75650
76593
|
*/
|
|
75651
76594
|
var SwitchStatusSchema = object({
|
|
75652
|
-
|
|
76595
|
+
/**
|
|
76596
|
+
* `null` = the device has never reported its state (a measurement that
|
|
76597
|
+
* has not happened is unknown, never `false`).
|
|
76598
|
+
*/
|
|
76599
|
+
on: boolean().nullable().meta({ label: "On" }),
|
|
75653
76600
|
/** Ms epoch of the last state change. Useful for UI "X minutes ago". */
|
|
75654
76601
|
lastChangedAt: number()
|
|
75655
76602
|
});
|
|
@@ -75674,7 +76621,7 @@ var switchCapability = {
|
|
|
75674
76621
|
}) },
|
|
75675
76622
|
events: { onStateChanged: { data: object({
|
|
75676
76623
|
deviceId: number(),
|
|
75677
|
-
on: boolean(),
|
|
76624
|
+
on: boolean().nullable(),
|
|
75678
76625
|
lastChangedAt: number()
|
|
75679
76626
|
}) } },
|
|
75680
76627
|
status: {
|
|
@@ -79231,6 +80178,26 @@ function buildDescriptor(capName, kind) {
|
|
|
79231
80178
|
* contributes one entry per bound cap present in this map.
|
|
79232
80179
|
*/
|
|
79233
80180
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
80181
|
+
/**
|
|
80182
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
80183
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
80184
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
80185
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
80186
|
+
* server sends an id, a client maps it to its own icon set.
|
|
80187
|
+
*
|
|
80188
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
80189
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
80190
|
+
* to `icon`.
|
|
80191
|
+
*/
|
|
80192
|
+
function digit(n) {
|
|
80193
|
+
return {
|
|
80194
|
+
label: t(n, n),
|
|
80195
|
+
icon: "hash",
|
|
80196
|
+
legend: n
|
|
80197
|
+
};
|
|
80198
|
+
}
|
|
80199
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
80200
|
+
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
79234
80201
|
var btn = (idSuffix, row, col, init) => ({
|
|
79235
80202
|
idSuffix,
|
|
79236
80203
|
row,
|
|
@@ -79357,6 +80324,34 @@ discriminatedUnion("kind", [object({
|
|
|
79357
80324
|
kind: literal("pronto"),
|
|
79358
80325
|
code: string$2()
|
|
79359
80326
|
})]);
|
|
80327
|
+
/**
|
|
80328
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
80329
|
+
*
|
|
80330
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
80331
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
80332
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
80333
|
+
* count of samples.
|
|
80334
|
+
*
|
|
80335
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80336
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
80337
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80338
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
80339
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
80340
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80341
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
80342
|
+
* covers the longest standby blip above T.
|
|
80343
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80344
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
80345
|
+
*/
|
|
80346
|
+
var SECOND_MS = 1e3;
|
|
80347
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
80348
|
+
24 * (60 * MINUTE_MS$1);
|
|
80349
|
+
10 * MINUTE_MS$1;
|
|
80350
|
+
30 * SECOND_MS;
|
|
80351
|
+
60 * SECOND_MS;
|
|
80352
|
+
20 * MINUTE_MS$1;
|
|
80353
|
+
10 * SECOND_MS;
|
|
80354
|
+
10 * SECOND_MS;
|
|
79360
80355
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
79361
80356
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
79362
80357
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -79717,6 +80712,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
79717
80712
|
pressureSensor: pressureSensorCapability,
|
|
79718
80713
|
privacyMask: privacyMaskCapability,
|
|
79719
80714
|
ptzAutotrack: ptzAutotrackCapability,
|
|
80715
|
+
pumpCycle: pumpCycleCapability,
|
|
79720
80716
|
recordingOnboard: recordingOnboardCapability,
|
|
79721
80717
|
recordingSignal: recordingSignalCapability,
|
|
79722
80718
|
runningState: runningStateCapability,
|
|
@@ -81901,6 +82897,27 @@ Object.freeze({
|
|
|
81901
82897
|
access: "view",
|
|
81902
82898
|
role: "viewer"
|
|
81903
82899
|
},
|
|
82900
|
+
"boardKeySource.getBoardKey": {
|
|
82901
|
+
capName: "board-key-source",
|
|
82902
|
+
capScope: "system",
|
|
82903
|
+
addonId: null,
|
|
82904
|
+
access: "view",
|
|
82905
|
+
role: "admin"
|
|
82906
|
+
},
|
|
82907
|
+
"boardKeySource.listBoards": {
|
|
82908
|
+
capName: "board-key-source",
|
|
82909
|
+
capScope: "system",
|
|
82910
|
+
addonId: null,
|
|
82911
|
+
access: "view",
|
|
82912
|
+
role: "admin"
|
|
82913
|
+
},
|
|
82914
|
+
"boardKeySource.listSources": {
|
|
82915
|
+
capName: "board-key-source",
|
|
82916
|
+
capScope: "system",
|
|
82917
|
+
addonId: null,
|
|
82918
|
+
access: "view",
|
|
82919
|
+
role: "admin"
|
|
82920
|
+
},
|
|
81904
82921
|
"brightness.getStatus": {
|
|
81905
82922
|
capName: "brightness",
|
|
81906
82923
|
capScope: "device",
|
|
@@ -82440,6 +83457,13 @@ Object.freeze({
|
|
|
82440
83457
|
access: "create",
|
|
82441
83458
|
role: "admin"
|
|
82442
83459
|
},
|
|
83460
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
83461
|
+
capName: "core-blocks",
|
|
83462
|
+
capScope: "system",
|
|
83463
|
+
addonId: null,
|
|
83464
|
+
access: "view",
|
|
83465
|
+
role: "admin"
|
|
83466
|
+
},
|
|
82443
83467
|
"coreBlocks.reportAutomationRun": {
|
|
82444
83468
|
capName: "core-blocks",
|
|
82445
83469
|
capScope: "system",
|
|
@@ -82951,6 +83975,13 @@ Object.freeze({
|
|
|
82951
83975
|
access: "view",
|
|
82952
83976
|
role: "admin"
|
|
82953
83977
|
},
|
|
83978
|
+
"deviceExport.listInstances": {
|
|
83979
|
+
capName: "device-export",
|
|
83980
|
+
capScope: "system",
|
|
83981
|
+
addonId: null,
|
|
83982
|
+
access: "view",
|
|
83983
|
+
role: "admin"
|
|
83984
|
+
},
|
|
82954
83985
|
"deviceExport.listSupportedDeviceKinds": {
|
|
82955
83986
|
capName: "device-export",
|
|
82956
83987
|
capScope: "system",
|
|
@@ -84582,6 +85613,13 @@ Object.freeze({
|
|
|
84582
85613
|
access: "view",
|
|
84583
85614
|
role: "admin"
|
|
84584
85615
|
},
|
|
85616
|
+
"integrationInstances.setInstanceEnabled": {
|
|
85617
|
+
capName: "integration-instances",
|
|
85618
|
+
capScope: "system",
|
|
85619
|
+
addonId: null,
|
|
85620
|
+
access: "create",
|
|
85621
|
+
role: "admin"
|
|
85622
|
+
},
|
|
84585
85623
|
"integrationInstances.testInstance": {
|
|
84586
85624
|
capName: "integration-instances",
|
|
84587
85625
|
capScope: "system",
|
|
@@ -85380,6 +86418,20 @@ Object.freeze({
|
|
|
85380
86418
|
access: "view",
|
|
85381
86419
|
role: "admin"
|
|
85382
86420
|
},
|
|
86421
|
+
"mediaPlayer.channelDown": {
|
|
86422
|
+
capName: "media-player",
|
|
86423
|
+
capScope: "device",
|
|
86424
|
+
addonId: null,
|
|
86425
|
+
access: "create",
|
|
86426
|
+
role: "viewer"
|
|
86427
|
+
},
|
|
86428
|
+
"mediaPlayer.channelUp": {
|
|
86429
|
+
capName: "media-player",
|
|
86430
|
+
capScope: "device",
|
|
86431
|
+
addonId: null,
|
|
86432
|
+
access: "create",
|
|
86433
|
+
role: "viewer"
|
|
86434
|
+
},
|
|
85383
86435
|
"mediaPlayer.getStatus": {
|
|
85384
86436
|
capName: "media-player",
|
|
85385
86437
|
capScope: "device",
|
|
@@ -85471,6 +86523,34 @@ Object.freeze({
|
|
|
85471
86523
|
access: "create",
|
|
85472
86524
|
role: "viewer"
|
|
85473
86525
|
},
|
|
86526
|
+
"mediaPlayer.turnOff": {
|
|
86527
|
+
capName: "media-player",
|
|
86528
|
+
capScope: "device",
|
|
86529
|
+
addonId: null,
|
|
86530
|
+
access: "create",
|
|
86531
|
+
role: "viewer"
|
|
86532
|
+
},
|
|
86533
|
+
"mediaPlayer.turnOn": {
|
|
86534
|
+
capName: "media-player",
|
|
86535
|
+
capScope: "device",
|
|
86536
|
+
addonId: null,
|
|
86537
|
+
access: "create",
|
|
86538
|
+
role: "viewer"
|
|
86539
|
+
},
|
|
86540
|
+
"mediaPlayer.volumeDown": {
|
|
86541
|
+
capName: "media-player",
|
|
86542
|
+
capScope: "device",
|
|
86543
|
+
addonId: null,
|
|
86544
|
+
access: "create",
|
|
86545
|
+
role: "viewer"
|
|
86546
|
+
},
|
|
86547
|
+
"mediaPlayer.volumeUp": {
|
|
86548
|
+
capName: "media-player",
|
|
86549
|
+
capScope: "device",
|
|
86550
|
+
addonId: null,
|
|
86551
|
+
access: "create",
|
|
86552
|
+
role: "viewer"
|
|
86553
|
+
},
|
|
85474
86554
|
"meshNetwork.getStatus": {
|
|
85475
86555
|
capName: "mesh-network",
|
|
85476
86556
|
capScope: "system",
|
|
@@ -86717,27 +87797,6 @@ Object.freeze({
|
|
|
86717
87797
|
access: "create",
|
|
86718
87798
|
role: "viewer"
|
|
86719
87799
|
},
|
|
86720
|
-
"petFountain.setPumpInterval": {
|
|
86721
|
-
capName: "pet-fountain",
|
|
86722
|
-
capScope: "device",
|
|
86723
|
-
addonId: null,
|
|
86724
|
-
access: "create",
|
|
86725
|
-
role: "viewer"
|
|
86726
|
-
},
|
|
86727
|
-
"petFountain.setPumpOffMinutes": {
|
|
86728
|
-
capName: "pet-fountain",
|
|
86729
|
-
capScope: "device",
|
|
86730
|
-
addonId: null,
|
|
86731
|
-
access: "create",
|
|
86732
|
-
role: "viewer"
|
|
86733
|
-
},
|
|
86734
|
-
"petFountain.setPumpOnMinutes": {
|
|
86735
|
-
capName: "pet-fountain",
|
|
86736
|
-
capScope: "device",
|
|
86737
|
-
addonId: null,
|
|
86738
|
-
access: "create",
|
|
86739
|
-
role: "viewer"
|
|
86740
|
-
},
|
|
86741
87800
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
86742
87801
|
capName: "pipeline-analytics",
|
|
86743
87802
|
capScope: "device",
|
|
@@ -88516,6 +89575,34 @@ Object.freeze({
|
|
|
88516
89575
|
access: "create",
|
|
88517
89576
|
role: "admin"
|
|
88518
89577
|
},
|
|
89578
|
+
"pumpCycle.getStatus": {
|
|
89579
|
+
capName: "pump-cycle",
|
|
89580
|
+
capScope: "device",
|
|
89581
|
+
addonId: null,
|
|
89582
|
+
access: "view",
|
|
89583
|
+
role: "viewer"
|
|
89584
|
+
},
|
|
89585
|
+
"pumpCycle.setEnabled": {
|
|
89586
|
+
capName: "pump-cycle",
|
|
89587
|
+
capScope: "device",
|
|
89588
|
+
addonId: null,
|
|
89589
|
+
access: "create",
|
|
89590
|
+
role: "viewer"
|
|
89591
|
+
},
|
|
89592
|
+
"pumpCycle.setOffMinutes": {
|
|
89593
|
+
capName: "pump-cycle",
|
|
89594
|
+
capScope: "device",
|
|
89595
|
+
addonId: null,
|
|
89596
|
+
access: "create",
|
|
89597
|
+
role: "viewer"
|
|
89598
|
+
},
|
|
89599
|
+
"pumpCycle.setOnMinutes": {
|
|
89600
|
+
capName: "pump-cycle",
|
|
89601
|
+
capScope: "device",
|
|
89602
|
+
addonId: null,
|
|
89603
|
+
access: "create",
|
|
89604
|
+
role: "viewer"
|
|
89605
|
+
},
|
|
88519
89606
|
"reboot.reboot": {
|
|
88520
89607
|
capName: "reboot",
|
|
88521
89608
|
capScope: "device",
|
|
@@ -89020,6 +90107,13 @@ Object.freeze({
|
|
|
89020
90107
|
access: "create",
|
|
89021
90108
|
role: "admin"
|
|
89022
90109
|
},
|
|
90110
|
+
"remote.getAssumedStatus": {
|
|
90111
|
+
capName: "remote",
|
|
90112
|
+
capScope: "system",
|
|
90113
|
+
addonId: null,
|
|
90114
|
+
access: "view",
|
|
90115
|
+
role: "admin"
|
|
90116
|
+
},
|
|
89023
90117
|
"remote.getConfig": {
|
|
89024
90118
|
capName: "remote",
|
|
89025
90119
|
capScope: "system",
|
|
@@ -91835,6 +92929,11 @@ Object.freeze({
|
|
|
91835
92929
|
form: "single",
|
|
91836
92930
|
optional: false
|
|
91837
92931
|
}],
|
|
92932
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
92933
|
+
name: "deviceId",
|
|
92934
|
+
form: "single",
|
|
92935
|
+
optional: false
|
|
92936
|
+
}],
|
|
91838
92937
|
"cover.close": [{
|
|
91839
92938
|
name: "deviceId",
|
|
91840
92939
|
form: "single",
|
|
@@ -92686,6 +93785,16 @@ Object.freeze({
|
|
|
92686
93785
|
form: "single",
|
|
92687
93786
|
optional: true
|
|
92688
93787
|
}],
|
|
93788
|
+
"mediaPlayer.channelDown": [{
|
|
93789
|
+
name: "deviceId",
|
|
93790
|
+
form: "single",
|
|
93791
|
+
optional: false
|
|
93792
|
+
}],
|
|
93793
|
+
"mediaPlayer.channelUp": [{
|
|
93794
|
+
name: "deviceId",
|
|
93795
|
+
form: "single",
|
|
93796
|
+
optional: false
|
|
93797
|
+
}],
|
|
92689
93798
|
"mediaPlayer.getStatus": [{
|
|
92690
93799
|
name: "deviceId",
|
|
92691
93800
|
form: "single",
|
|
@@ -92751,6 +93860,26 @@ Object.freeze({
|
|
|
92751
93860
|
form: "single",
|
|
92752
93861
|
optional: false
|
|
92753
93862
|
}],
|
|
93863
|
+
"mediaPlayer.turnOff": [{
|
|
93864
|
+
name: "deviceId",
|
|
93865
|
+
form: "single",
|
|
93866
|
+
optional: false
|
|
93867
|
+
}],
|
|
93868
|
+
"mediaPlayer.turnOn": [{
|
|
93869
|
+
name: "deviceId",
|
|
93870
|
+
form: "single",
|
|
93871
|
+
optional: false
|
|
93872
|
+
}],
|
|
93873
|
+
"mediaPlayer.volumeDown": [{
|
|
93874
|
+
name: "deviceId",
|
|
93875
|
+
form: "single",
|
|
93876
|
+
optional: false
|
|
93877
|
+
}],
|
|
93878
|
+
"mediaPlayer.volumeUp": [{
|
|
93879
|
+
name: "deviceId",
|
|
93880
|
+
form: "single",
|
|
93881
|
+
optional: false
|
|
93882
|
+
}],
|
|
92754
93883
|
"meter.getStatus": [{
|
|
92755
93884
|
name: "deviceId",
|
|
92756
93885
|
form: "single",
|
|
@@ -93060,21 +94189,6 @@ Object.freeze({
|
|
|
93060
94189
|
form: "single",
|
|
93061
94190
|
optional: false
|
|
93062
94191
|
}],
|
|
93063
|
-
"petFountain.setPumpInterval": [{
|
|
93064
|
-
name: "deviceId",
|
|
93065
|
-
form: "single",
|
|
93066
|
-
optional: false
|
|
93067
|
-
}],
|
|
93068
|
-
"petFountain.setPumpOffMinutes": [{
|
|
93069
|
-
name: "deviceId",
|
|
93070
|
-
form: "single",
|
|
93071
|
-
optional: false
|
|
93072
|
-
}],
|
|
93073
|
-
"petFountain.setPumpOnMinutes": [{
|
|
93074
|
-
name: "deviceId",
|
|
93075
|
-
form: "single",
|
|
93076
|
-
optional: false
|
|
93077
|
-
}],
|
|
93078
94192
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
93079
94193
|
name: "deviceId",
|
|
93080
94194
|
form: "single",
|
|
@@ -93710,6 +94824,26 @@ Object.freeze({
|
|
|
93710
94824
|
form: "single",
|
|
93711
94825
|
optional: false
|
|
93712
94826
|
}],
|
|
94827
|
+
"pumpCycle.getStatus": [{
|
|
94828
|
+
name: "deviceId",
|
|
94829
|
+
form: "single",
|
|
94830
|
+
optional: false
|
|
94831
|
+
}],
|
|
94832
|
+
"pumpCycle.setEnabled": [{
|
|
94833
|
+
name: "deviceId",
|
|
94834
|
+
form: "single",
|
|
94835
|
+
optional: false
|
|
94836
|
+
}],
|
|
94837
|
+
"pumpCycle.setOffMinutes": [{
|
|
94838
|
+
name: "deviceId",
|
|
94839
|
+
form: "single",
|
|
94840
|
+
optional: false
|
|
94841
|
+
}],
|
|
94842
|
+
"pumpCycle.setOnMinutes": [{
|
|
94843
|
+
name: "deviceId",
|
|
94844
|
+
form: "single",
|
|
94845
|
+
optional: false
|
|
94846
|
+
}],
|
|
93713
94847
|
"reboot.reboot": [{
|
|
93714
94848
|
name: "deviceId",
|
|
93715
94849
|
form: "single",
|
|
@@ -93911,6 +95045,11 @@ Object.freeze({
|
|
|
93911
95045
|
form: "single",
|
|
93912
95046
|
optional: true
|
|
93913
95047
|
}],
|
|
95048
|
+
"remote.getAssumedStatus": [{
|
|
95049
|
+
name: "deviceId",
|
|
95050
|
+
form: "single",
|
|
95051
|
+
optional: false
|
|
95052
|
+
}],
|
|
93914
95053
|
"remote.getConfig": [{
|
|
93915
95054
|
name: "deviceId",
|
|
93916
95055
|
form: "single",
|
|
@@ -94458,99 +95597,6 @@ Object.freeze({
|
|
|
94458
95597
|
"network-access": "ingress",
|
|
94459
95598
|
"smtp-provider": "email"
|
|
94460
95599
|
});
|
|
94461
|
-
/**
|
|
94462
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
94463
|
-
* `z.infer`, they do not restate the shape.
|
|
94464
|
-
*/
|
|
94465
|
-
var OverrideSourceSchema = object({
|
|
94466
|
-
addonId: string$2().min(1),
|
|
94467
|
-
stableId: string$2().min(1),
|
|
94468
|
-
cap: string$2().min(1),
|
|
94469
|
-
fieldPath: string$2().min(1)
|
|
94470
|
-
});
|
|
94471
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
94472
|
-
object({ kind: literal("identity") }),
|
|
94473
|
-
object({ kind: literal("invert") }),
|
|
94474
|
-
object({
|
|
94475
|
-
kind: literal("threshold"),
|
|
94476
|
-
comparison: _enum(["gte", "lte"]),
|
|
94477
|
-
threshold: number(),
|
|
94478
|
-
startDwellMs: number().nonnegative(),
|
|
94479
|
-
stopDwellMs: number().nonnegative()
|
|
94480
|
-
}),
|
|
94481
|
-
object({
|
|
94482
|
-
kind: literal("linear"),
|
|
94483
|
-
scale: number(),
|
|
94484
|
-
offset: number(),
|
|
94485
|
-
clampMin: number().optional(),
|
|
94486
|
-
clampMax: number().optional()
|
|
94487
|
-
}),
|
|
94488
|
-
object({
|
|
94489
|
-
kind: literal("enum-map"),
|
|
94490
|
-
mapping: record(string$2(), string$2().nullable())
|
|
94491
|
-
}),
|
|
94492
|
-
object({ kind: literal("number") })
|
|
94493
|
-
]);
|
|
94494
|
-
var ScalarOverrideDraftSchema = object({
|
|
94495
|
-
kind: literal("scalar"),
|
|
94496
|
-
cap: string$2().min(1),
|
|
94497
|
-
targetId: string$2().min(1),
|
|
94498
|
-
source: OverrideSourceSchema,
|
|
94499
|
-
transform: CustomizeTransformDraftSchema
|
|
94500
|
-
});
|
|
94501
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
94502
|
-
kind: literal("keyedArray"),
|
|
94503
|
-
cap: string$2().min(1),
|
|
94504
|
-
targetId: string$2().min(1),
|
|
94505
|
-
key: string$2().min(1),
|
|
94506
|
-
label: string$2().min(1),
|
|
94507
|
-
fieldPath: string$2().min(1),
|
|
94508
|
-
source: OverrideSourceSchema,
|
|
94509
|
-
transform: CustomizeTransformDraftSchema
|
|
94510
|
-
});
|
|
94511
|
-
var PulseOverrideDraftSchema = object({
|
|
94512
|
-
kind: literal("pulse"),
|
|
94513
|
-
cap: string$2().min(1),
|
|
94514
|
-
targetId: string$2().min(1),
|
|
94515
|
-
enabled: boolean(),
|
|
94516
|
-
debounceMs: number().nonnegative(),
|
|
94517
|
-
edges: array(_enum([
|
|
94518
|
-
"rising",
|
|
94519
|
-
"button",
|
|
94520
|
-
"native-event"
|
|
94521
|
-
])).min(1).optional(),
|
|
94522
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
94523
|
-
});
|
|
94524
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
94525
|
-
"in-addition",
|
|
94526
|
-
"confirm",
|
|
94527
|
-
"replace"
|
|
94528
|
-
]).optional() });
|
|
94529
|
-
discriminatedUnion("kind", [
|
|
94530
|
-
ScalarOverrideDraftSchema,
|
|
94531
|
-
KeyedArrayOverrideDraftSchema,
|
|
94532
|
-
PulseOverrideDraftSchema,
|
|
94533
|
-
object({
|
|
94534
|
-
kind: literal("span"),
|
|
94535
|
-
cap: string$2().min(1),
|
|
94536
|
-
targetId: string$2().min(1),
|
|
94537
|
-
timeoutMs: number().nonnegative(),
|
|
94538
|
-
cooldownMs: number().nonnegative().optional(),
|
|
94539
|
-
sources: array(SpanSourceDraftSchema)
|
|
94540
|
-
})
|
|
94541
|
-
]);
|
|
94542
|
-
object({
|
|
94543
|
-
targetId: string$2().min(1),
|
|
94544
|
-
configured: boolean(),
|
|
94545
|
-
source: _enum(["native", "assigned"]),
|
|
94546
|
-
state: _enum([
|
|
94547
|
-
"active",
|
|
94548
|
-
"stale",
|
|
94549
|
-
"released",
|
|
94550
|
-
"unavailable"
|
|
94551
|
-
]),
|
|
94552
|
-
reason: string$2().nullable()
|
|
94553
|
-
});
|
|
94554
95600
|
var G711_SCALE_CORRECTION_DB = {
|
|
94555
95601
|
PCMU: 20 * Math.log10(4),
|
|
94556
95602
|
PCMA: 20 * Math.log10(8)
|
|
@@ -95166,6 +96212,7 @@ var STATE_SUMMARY_VARS = [
|
|
|
95166
96212
|
[...RULE_TEXT],
|
|
95167
96213
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95168
96214
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
96215
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
95169
96216
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95170
96217
|
([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
|
|
95171
96218
|
([...RULE_TEXT], [...CONSUMABLE]),
|
|
@@ -206243,13 +207290,16 @@ var MatterBrokerRegistry = class {
|
|
|
206243
207290
|
}
|
|
206244
207291
|
async restore(entries) {
|
|
206245
207292
|
for (const entry of entries) this.#seedCounter(entry.id);
|
|
206246
|
-
for (const entry of entries)
|
|
206247
|
-
|
|
206248
|
-
|
|
206249
|
-
|
|
206250
|
-
|
|
206251
|
-
|
|
206252
|
-
|
|
207293
|
+
for (const entry of entries) {
|
|
207294
|
+
if (entry.enabled === false) continue;
|
|
207295
|
+
try {
|
|
207296
|
+
await this.#startManager(entry);
|
|
207297
|
+
} catch (err) {
|
|
207298
|
+
this.#logger.warn("MatterBrokerRegistry: failed to restore manager", {
|
|
207299
|
+
tags: { brokerId: entry.id },
|
|
207300
|
+
meta: { error: errMsg(err) }
|
|
207301
|
+
});
|
|
207302
|
+
}
|
|
206253
207303
|
}
|
|
206254
207304
|
}
|
|
206255
207305
|
async shutdown() {
|
|
@@ -206280,7 +207330,8 @@ var MatterBrokerRegistry = class {
|
|
|
206280
207330
|
this.#onAttributeReport = cb;
|
|
206281
207331
|
}
|
|
206282
207332
|
async createEntry(name, connection, opts = {}) {
|
|
206283
|
-
const id = this.#allocateId();
|
|
207333
|
+
const id = opts.id ?? this.#allocateId();
|
|
207334
|
+
if (opts.id !== void 0) this.#seedCounter(opts.id);
|
|
206284
207335
|
const entry = {
|
|
206285
207336
|
id,
|
|
206286
207337
|
name,
|
|
@@ -206293,7 +207344,7 @@ var MatterBrokerRegistry = class {
|
|
|
206293
207344
|
}
|
|
206294
207345
|
async removeEntry(id) {
|
|
206295
207346
|
const mgr = this.#managers.get(id);
|
|
206296
|
-
if (!mgr)
|
|
207347
|
+
if (!mgr) return;
|
|
206297
207348
|
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
206298
207349
|
this.#managers.delete(id);
|
|
206299
207350
|
try {
|
|
@@ -206375,6 +207426,10 @@ var MatterBrokerRegistry = class {
|
|
|
206375
207426
|
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
206376
207427
|
await mgr.start();
|
|
206377
207428
|
}
|
|
207429
|
+
/** The next free broker id, for a record minted before its manager exists. */
|
|
207430
|
+
allocateId() {
|
|
207431
|
+
return this.#allocateId();
|
|
207432
|
+
}
|
|
206378
207433
|
#allocateId() {
|
|
206379
207434
|
const id = `matter_${String(this.#nextId).padStart(3, "0")}`;
|
|
206380
207435
|
this.#nextId++;
|
|
@@ -206413,10 +207468,12 @@ var MatterBrokerEntrySchema = object({
|
|
|
206413
207468
|
id: string$2().min(1),
|
|
206414
207469
|
name: string$2().min(1),
|
|
206415
207470
|
connection: matterConnectionSchema,
|
|
206416
|
-
integrationId: string$2().optional()
|
|
207471
|
+
integrationId: string$2().optional(),
|
|
207472
|
+
/** Absent means on; the instances page switches a fabric off without deleting it. */
|
|
207473
|
+
enabled: boolean().optional()
|
|
206417
207474
|
});
|
|
206418
|
-
/** Top-level addon config —
|
|
206419
|
-
var matterAddonConfigSchema = object({
|
|
207475
|
+
/** Top-level addon config — the fabrics are records, so nothing is left here. */
|
|
207476
|
+
var matterAddonConfigSchema = object({});
|
|
206420
207477
|
/** Narrow an `unknown` to a shallow plain-object record (cast-free boundary). */
|
|
206421
207478
|
function toRecord(value) {
|
|
206422
207479
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) return Object.fromEntries(Object.entries(value));
|
|
@@ -207498,19 +208555,145 @@ var MatterNodeDevice = class extends BaseDevice {
|
|
|
207498
208555
|
var matterBrokersEntryKind = defineEntryKind("brokers", {
|
|
207499
208556
|
title: "Matter brokers",
|
|
207500
208557
|
schema: entryBody({
|
|
207501
|
-
|
|
207502
|
-
integrationId: string$2().optional()
|
|
208558
|
+
...matterConnectionSchema.shape,
|
|
208559
|
+
integrationId: string$2().optional(),
|
|
208560
|
+
enabled: boolean().optional()
|
|
207503
208561
|
}),
|
|
207504
|
-
writeTrigger: "an operator adds, edits or removes a Matter fabric broker"
|
|
208562
|
+
writeTrigger: "an operator adds, edits or removes a Matter fabric broker",
|
|
208563
|
+
retiredSecretPaths: ["connection.threadDataset", "connection.wifiPassword"]
|
|
207505
208564
|
});
|
|
207506
208565
|
//#endregion
|
|
208566
|
+
//#region src/matter-instances.ts
|
|
208567
|
+
/**
|
|
208568
|
+
* The Matter fabrics as `integration-instances` (planning#408 P3): one instance per `brokers`
|
|
208569
|
+
* record, over the SAME records and ids (`matter_NNN`) the `broker` cap serves. The body is flat.
|
|
208570
|
+
* A fabric needs no sign-in; commissioning a device stays on the adoption flow, behind the fabric.
|
|
208571
|
+
*/
|
|
208572
|
+
var MATTER_INSTANCE_KIND = "matter";
|
|
208573
|
+
function statusOf(info) {
|
|
208574
|
+
if (info === null) return {
|
|
208575
|
+
state: "unknown",
|
|
208576
|
+
label: "Not started"
|
|
208577
|
+
};
|
|
208578
|
+
if (info.status === "connected") return {
|
|
208579
|
+
state: "ok",
|
|
208580
|
+
label: "Running"
|
|
208581
|
+
};
|
|
208582
|
+
if (info.status === "connecting") return {
|
|
208583
|
+
state: "unknown",
|
|
208584
|
+
label: "Starting"
|
|
208585
|
+
};
|
|
208586
|
+
return {
|
|
208587
|
+
state: "error",
|
|
208588
|
+
label: info.status,
|
|
208589
|
+
...info.error !== null ? { detail: info.error } : {}
|
|
208590
|
+
};
|
|
208591
|
+
}
|
|
208592
|
+
function buildMatterInstances(records, deps) {
|
|
208593
|
+
return new RecordIntegrationInstances(records, matterBrokersEntryKind, {
|
|
208594
|
+
description: {
|
|
208595
|
+
instanceMode: "multiple",
|
|
208596
|
+
kind: "connection",
|
|
208597
|
+
canTest: true,
|
|
208598
|
+
kinds: [{
|
|
208599
|
+
id: MATTER_INSTANCE_KIND,
|
|
208600
|
+
label: "Matter"
|
|
208601
|
+
}],
|
|
208602
|
+
allowRestoreId: true
|
|
208603
|
+
},
|
|
208604
|
+
formSchema: () => buildConnectionFormSchema(),
|
|
208605
|
+
initialBody: (name) => ({
|
|
208606
|
+
name,
|
|
208607
|
+
threadDataset: "",
|
|
208608
|
+
wifiSsid: "",
|
|
208609
|
+
wifiPassword: "",
|
|
208610
|
+
reconcileIntervalMs: 3e5
|
|
208611
|
+
}),
|
|
208612
|
+
mintId: () => deps.mintId(),
|
|
208613
|
+
kindOf: () => MATTER_INSTANCE_KIND,
|
|
208614
|
+
health: (_body, id) => {
|
|
208615
|
+
const info = deps.infoOf(id);
|
|
208616
|
+
return {
|
|
208617
|
+
status: statusOf(info),
|
|
208618
|
+
...info?.lastCheckedAt != null ? { lastCheckedAt: info.lastCheckedAt } : {}
|
|
208619
|
+
};
|
|
208620
|
+
},
|
|
208621
|
+
configured: () => true,
|
|
208622
|
+
onChanged: deps.onChanged,
|
|
208623
|
+
test: async ({ id }) => {
|
|
208624
|
+
const info = id === null ? null : deps.infoOf(id);
|
|
208625
|
+
if (info === null) return {
|
|
208626
|
+
success: false,
|
|
208627
|
+
status: "inconclusive",
|
|
208628
|
+
error: "Not started: save the fabric first"
|
|
208629
|
+
};
|
|
208630
|
+
return info.status === "connected" ? {
|
|
208631
|
+
success: true,
|
|
208632
|
+
status: "validated"
|
|
208633
|
+
} : {
|
|
208634
|
+
success: false,
|
|
208635
|
+
status: "rejected",
|
|
208636
|
+
error: info.error ?? `status: ${info.status}`
|
|
208637
|
+
};
|
|
208638
|
+
}
|
|
208639
|
+
});
|
|
208640
|
+
}
|
|
208641
|
+
//#endregion
|
|
208642
|
+
//#region src/migrations/flatten-broker-records.ts
|
|
208643
|
+
/**
|
|
208644
|
+
* The Matter broker records keep their fabric settings flat (planning#408 P3): the nested
|
|
208645
|
+
* `connection` object (`threadDataset`, `wifiSsid`, `wifiPassword`, `reconcileIntervalMs`) becomes
|
|
208646
|
+
* top-level fields under the SAME record id (`matter_NNN`), so the devices that carry the id keep
|
|
208647
|
+
* their fabric. The work is `flattenNestedRecords`: every secret is read before anything is
|
|
208648
|
+
* written, read back from its new ref before the nested object goes, and the old refs are
|
|
208649
|
+
* deleted last.
|
|
208650
|
+
*/
|
|
208651
|
+
var FLATTEN_BROKER_RECORDS_ID = "matter-broker/2026-11-04-flatten-broker-records";
|
|
208652
|
+
async function flattenBrokerRecords(ports) {
|
|
208653
|
+
return flattenNestedRecords(ports, {
|
|
208654
|
+
legacyKey: "connection",
|
|
208655
|
+
flatSecretKeys: ["threadDataset", "wifiPassword"],
|
|
208656
|
+
toFlat: (legacy) => Object.fromEntries([
|
|
208657
|
+
"threadDataset",
|
|
208658
|
+
"wifiSsid",
|
|
208659
|
+
"wifiPassword",
|
|
208660
|
+
"reconcileIntervalMs"
|
|
208661
|
+
].filter((key) => legacy[key] !== void 0).map((key) => [key, legacy[key]]))
|
|
208662
|
+
});
|
|
208663
|
+
}
|
|
208664
|
+
//#endregion
|
|
208665
|
+
//#region src/migrations/index.ts
|
|
208666
|
+
/** The `matter-broker` catalogue (D987): offered to the engine when the addon initialises. */
|
|
208667
|
+
var ADDON_MIGRATIONS = [{
|
|
208668
|
+
meta: {
|
|
208669
|
+
id: FLATTEN_BROKER_RECORDS_ID,
|
|
208670
|
+
owner: "matter-broker",
|
|
208671
|
+
level: "addon-state",
|
|
208672
|
+
seq: 1,
|
|
208673
|
+
dependsOn: [],
|
|
208674
|
+
store: "settings",
|
|
208675
|
+
scope: "cluster",
|
|
208676
|
+
onFailure: "block-owner",
|
|
208677
|
+
bodyKind: "procedure",
|
|
208678
|
+
description: "Move each Matter fabric from a nested connection object to flat top-level fields under the same id, reading every secret back first",
|
|
208679
|
+
fixtureMark: "the integration link and the Wi-Fi SSID of each fabric"
|
|
208680
|
+
},
|
|
208681
|
+
procedure: flattenBrokerRecords
|
|
208682
|
+
}];
|
|
208683
|
+
//#endregion
|
|
207507
208684
|
//#region src/matter-broker-store.ts
|
|
207508
208685
|
function rowToEntry(id, data) {
|
|
207509
208686
|
const parsed = MatterBrokerEntrySchema.safeParse({
|
|
207510
208687
|
id,
|
|
207511
208688
|
name: data.name,
|
|
207512
|
-
connection:
|
|
207513
|
-
|
|
208689
|
+
connection: {
|
|
208690
|
+
threadDataset: data.threadDataset,
|
|
208691
|
+
wifiSsid: data.wifiSsid,
|
|
208692
|
+
wifiPassword: data.wifiPassword,
|
|
208693
|
+
reconcileIntervalMs: data.reconcileIntervalMs
|
|
208694
|
+
},
|
|
208695
|
+
integrationId: data.integrationId,
|
|
208696
|
+
enabled: data.enabled
|
|
207514
208697
|
});
|
|
207515
208698
|
return parsed.success ? parsed.data : null;
|
|
207516
208699
|
}
|
|
@@ -207538,16 +208721,20 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
207538
208721
|
}
|
|
207539
208722
|
return out;
|
|
207540
208723
|
}
|
|
208724
|
+
async getById(id) {
|
|
208725
|
+
return (await this.listResolved()).find((entry) => entry.id === id) ?? null;
|
|
208726
|
+
}
|
|
207541
208727
|
async replaceAll(entries) {
|
|
207542
208728
|
const page = await this.kind.query({ limit: 1e3 });
|
|
207543
208729
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
207544
208730
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
207545
208731
|
for (const entry of entries) {
|
|
207546
|
-
const { id, name, connection, integrationId } = entry;
|
|
208732
|
+
const { id, name, connection, integrationId, enabled } = entry;
|
|
207547
208733
|
const body = {
|
|
207548
208734
|
name,
|
|
207549
|
-
connection,
|
|
207550
|
-
...integrationId === void 0 ? {} : { integrationId }
|
|
208735
|
+
...connection,
|
|
208736
|
+
...integrationId === void 0 ? {} : { integrationId },
|
|
208737
|
+
...enabled === void 0 ? {} : { enabled }
|
|
207551
208738
|
};
|
|
207552
208739
|
const parsed = matterBrokersEntryKind.schema.safeParse(body);
|
|
207553
208740
|
if (!parsed.success) throw new Error(`invalid Matter broker '${id}': ${parsed.error.message}`);
|
|
@@ -207561,7 +208748,7 @@ var MatterBrokerStore = class MatterBrokerStore {
|
|
|
207561
208748
|
//#endregion
|
|
207562
208749
|
//#region src/addon.ts
|
|
207563
208750
|
/** Default config — a fresh install starts with no fabric. */
|
|
207564
|
-
var DEFAULTS = {
|
|
208751
|
+
var DEFAULTS = {};
|
|
207565
208752
|
/**
|
|
207566
208753
|
* Matter broker addon (hub-only). Owns a Matter fabric per broker entry via an
|
|
207567
208754
|
* embedded matter.js controller (behind the {@link matterCommands} /
|
|
@@ -207583,6 +208770,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207583
208770
|
deviceClasses = { [DeviceType.Container]: MatterNodeDevice };
|
|
207584
208771
|
registry = null;
|
|
207585
208772
|
brokerStore = null;
|
|
208773
|
+
instances = null;
|
|
207586
208774
|
activeBrokers = [];
|
|
207587
208775
|
constructor() {
|
|
207588
208776
|
super({ ...DEFAULTS });
|
|
@@ -207593,6 +208781,13 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207593
208781
|
if (records === void 0) throw new Error("matter-broker: ctx.records is required (hub-only addon)");
|
|
207594
208782
|
await records.declare({ brokers: matterBrokersEntryKind });
|
|
207595
208783
|
this.brokerStore = MatterBrokerStore.of(records);
|
|
208784
|
+
this.instances = buildMatterInstances(records, {
|
|
208785
|
+
mintId: () => this.requireRegistry().allocateId(),
|
|
208786
|
+
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
208787
|
+
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
208788
|
+
});
|
|
208789
|
+
if (this.ctx.secrets === void 0) throw new Error("matter-broker: ctx.secrets is required");
|
|
208790
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, flattenPortsOver(records.kind(matterBrokersEntryKind))));
|
|
207596
208791
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
207597
208792
|
this.registry = new MatterBrokerRegistry(this.ctx.logger, {
|
|
207598
208793
|
dataDir: this.ctx.dataDir,
|
|
@@ -207621,6 +208816,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207621
208816
|
capability: brokerCapability,
|
|
207622
208817
|
provider: this.buildBrokerProvider()
|
|
207623
208818
|
},
|
|
208819
|
+
{
|
|
208820
|
+
capability: integrationInstancesCapability,
|
|
208821
|
+
provider: this.requireInstances().toProvider()
|
|
208822
|
+
},
|
|
207624
208823
|
{
|
|
207625
208824
|
capability: deviceAdoptionCapability,
|
|
207626
208825
|
provider: this.buildAdoptionProvider()
|
|
@@ -207628,21 +208827,50 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207628
208827
|
];
|
|
207629
208828
|
}
|
|
207630
208829
|
async loadPersistedBrokers() {
|
|
207631
|
-
|
|
207632
|
-
|
|
207633
|
-
|
|
207634
|
-
|
|
208830
|
+
return this.requireBrokerStore().listResolved();
|
|
208831
|
+
}
|
|
208832
|
+
requireBrokerStore() {
|
|
208833
|
+
if (this.brokerStore === null) throw new Error("matter-broker: broker store not ready");
|
|
208834
|
+
return this.brokerStore;
|
|
208835
|
+
}
|
|
208836
|
+
requireInstances() {
|
|
208837
|
+
if (this.instances === null) throw new Error("matter-broker: instances not ready");
|
|
208838
|
+
return this.instances;
|
|
208839
|
+
}
|
|
208840
|
+
/**
|
|
208841
|
+
* Bring the runtime in line with one record after the instances page changed it: a removed
|
|
208842
|
+
* record stops its controller and cascades its devices, an off record only stops the
|
|
208843
|
+
* controller, a live one re-applies its settings or starts under the record's own id.
|
|
208844
|
+
*/
|
|
208845
|
+
async syncBrokerRuntime(id) {
|
|
208846
|
+
const reg = this.requireRegistry();
|
|
208847
|
+
const entry = await this.requireBrokerStore().getById(id);
|
|
208848
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
208849
|
+
if (entry === null) {
|
|
208850
|
+
await reg.removeEntry(id);
|
|
208851
|
+
await this.cascadeRemoveDevicesForBroker(id);
|
|
208852
|
+
return;
|
|
208853
|
+
}
|
|
208854
|
+
if (entry.enabled === false) {
|
|
208855
|
+
await reg.removeEntry(id);
|
|
208856
|
+
return;
|
|
208857
|
+
}
|
|
208858
|
+
try {
|
|
208859
|
+
if (reg.get(id) !== null) await reg.updateEntry(id, entry.connection);
|
|
208860
|
+
else await reg.createEntry(entry.name, entry.connection, {
|
|
208861
|
+
id,
|
|
208862
|
+
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
208863
|
+
});
|
|
208864
|
+
} catch (err) {
|
|
208865
|
+
this.ctx.logger.warn("matter instance: could not start the fabric", { meta: {
|
|
208866
|
+
brokerId: id,
|
|
208867
|
+
error: errMsg(err)
|
|
208868
|
+
} });
|
|
207635
208869
|
}
|
|
207636
|
-
return this.config.brokers;
|
|
207637
208870
|
}
|
|
207638
208871
|
async persistBrokerEntries(entries) {
|
|
207639
208872
|
this.activeBrokers = [...entries];
|
|
207640
|
-
|
|
207641
|
-
if (store !== null) {
|
|
207642
|
-
await store.replaceAll(entries);
|
|
207643
|
-
return;
|
|
207644
|
-
}
|
|
207645
|
-
await this.updateGlobalSettings({ brokers: [...entries] });
|
|
208873
|
+
await this.requireBrokerStore().replaceAll(entries);
|
|
207646
208874
|
}
|
|
207647
208875
|
async onConfigChanged() {
|
|
207648
208876
|
const reg = this.registry;
|
|
@@ -207660,6 +208888,7 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
|
|
|
207660
208888
|
} });
|
|
207661
208889
|
}
|
|
207662
208890
|
for (const entry of persisted) {
|
|
208891
|
+
if (entry.enabled === false) continue;
|
|
207663
208892
|
if (entry.id && liveIds.has(entry.id)) {
|
|
207664
208893
|
if (sameConnection(entry.connection, reg.getConnection(entry.id))) continue;
|
|
207665
208894
|
try {
|