@camstack/addon-provider-dreame 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +183 -7
- package/dist/addon.mjs +183 -7
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -4680,7 +4680,7 @@ function preprocess(fn, schema) {
|
|
|
4680
4680
|
});
|
|
4681
4681
|
}
|
|
4682
4682
|
//#endregion
|
|
4683
|
-
//#region ../types/dist/sleep-
|
|
4683
|
+
//#region ../types/dist/sleep-C2M2zF7x.mjs
|
|
4684
4684
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4685
4685
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4686
4686
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -6248,6 +6248,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
6248
6248
|
DeviceType["Switch"] = "switch";
|
|
6249
6249
|
DeviceType["Sensor"] = "sensor";
|
|
6250
6250
|
DeviceType["Thermostat"] = "thermostat";
|
|
6251
|
+
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
6252
|
+
* `climate-control` cap surface with `Thermostat` but renders a
|
|
6253
|
+
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
6254
|
+
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
6255
|
+
* reusable by other AC integrations. */
|
|
6256
|
+
DeviceType["Climate"] = "climate";
|
|
6251
6257
|
DeviceType["Button"] = "button";
|
|
6252
6258
|
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
6253
6259
|
* event vocabulary verbatim (no normalization). Installed with the
|
|
@@ -9043,7 +9049,7 @@ var climateControlCapability = {
|
|
|
9043
9049
|
scope: "device",
|
|
9044
9050
|
deviceNative: true,
|
|
9045
9051
|
mode: "singleton",
|
|
9046
|
-
deviceTypes: [DeviceType.Thermostat],
|
|
9052
|
+
deviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
|
|
9047
9053
|
methods: {
|
|
9048
9054
|
setMode: method(object({
|
|
9049
9055
|
deviceId: number().int().nonnegative(),
|
|
@@ -13642,10 +13648,30 @@ var deviceProviderCapability = {
|
|
|
13642
13648
|
type: string()
|
|
13643
13649
|
}))),
|
|
13644
13650
|
supportsDiscovery: method(object({}), boolean()),
|
|
13645
|
-
|
|
13651
|
+
/**
|
|
13652
|
+
* Run a network scan. `params` carries optional provider-specific scan
|
|
13653
|
+
* inputs (e.g. a broadcast address / subnet for cross-subnet discovery),
|
|
13654
|
+
* shaped by `getDiscoveryParamsSchema`. Omitted for the generic scan
|
|
13655
|
+
* (provider uses its local-network default).
|
|
13656
|
+
*/
|
|
13657
|
+
discoverDevices: method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
|
|
13646
13658
|
kind: "mutation",
|
|
13647
13659
|
auth: "admin"
|
|
13648
13660
|
}),
|
|
13661
|
+
/**
|
|
13662
|
+
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
13663
|
+
* provider accepts (e.g. Gree's broadcast address for a different subnet).
|
|
13664
|
+
* `null` when the provider takes no extra scan params — the generic
|
|
13665
|
+
* aggregated scan never renders this; the per-integration scan does.
|
|
13666
|
+
*/
|
|
13667
|
+
getDiscoveryParamsSchema: method(object({}), CreationSchemaOutputSchema),
|
|
13668
|
+
/**
|
|
13669
|
+
* The DeviceType this provider creates via manual add (Camera for
|
|
13670
|
+
* Reolink/ONVIF, Container for Gree, Hub for Ecowitt). `null` when the
|
|
13671
|
+
* provider does not support manual creation. Lets the Add-Device dialog
|
|
13672
|
+
* pick the right type instead of assuming Camera.
|
|
13673
|
+
*/
|
|
13674
|
+
getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
13649
13675
|
adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
|
|
13650
13676
|
kind: "mutation",
|
|
13651
13677
|
auth: "admin"
|
|
@@ -13769,9 +13795,23 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
13769
13795
|
async supportsDiscovery() {
|
|
13770
13796
|
return false;
|
|
13771
13797
|
}
|
|
13772
|
-
async discoverDevices() {
|
|
13798
|
+
async discoverDevices(_input) {
|
|
13773
13799
|
return [];
|
|
13774
13800
|
}
|
|
13801
|
+
/** Extra per-scan input form (e.g. a broadcast address for another subnet).
|
|
13802
|
+
* Null = no extra params. Override in providers that support scoped scans. */
|
|
13803
|
+
async getDiscoveryParamsSchema() {
|
|
13804
|
+
return null;
|
|
13805
|
+
}
|
|
13806
|
+
/**
|
|
13807
|
+
* The DeviceType this provider creates via manual add — derived from the
|
|
13808
|
+
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
13809
|
+
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
13810
|
+
*/
|
|
13811
|
+
async getManualCreationType() {
|
|
13812
|
+
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
13813
|
+
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
13814
|
+
}
|
|
13775
13815
|
async adoptDiscoveredDevice(_input) {
|
|
13776
13816
|
throw new Error(`${this.providerName} provider does not support discovery-based adoption`);
|
|
13777
13817
|
}
|
|
@@ -15633,7 +15673,10 @@ method(object({
|
|
|
15633
15673
|
}), FieldProbeResultSchema, {
|
|
15634
15674
|
kind: "mutation",
|
|
15635
15675
|
auth: "admin"
|
|
15636
|
-
}), method(
|
|
15676
|
+
}), method(object({
|
|
15677
|
+
addonId: string(),
|
|
15678
|
+
integrationId: string()
|
|
15679
|
+
}), object({ filters: array(AdoptionFilterSchema) }), { auth: "admin" }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
|
|
15637
15680
|
addonId: string(),
|
|
15638
15681
|
integrationId: string()
|
|
15639
15682
|
}), AdoptionStatusSchema, {
|
|
@@ -15648,7 +15691,24 @@ method(object({
|
|
|
15648
15691
|
}), method(ResyncInputSchema, ResyncResultSchema, {
|
|
15649
15692
|
kind: "mutation",
|
|
15650
15693
|
auth: "admin"
|
|
15694
|
+
}), method(object({}), object({ providers: array(object({
|
|
15695
|
+
addonId: string(),
|
|
15696
|
+
label: string()
|
|
15697
|
+
})).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
|
|
15698
|
+
addonId: string(),
|
|
15699
|
+
label: string(),
|
|
15700
|
+
candidates: array(DiscoveryCandidateSchema).readonly(),
|
|
15701
|
+
error: string().nullable()
|
|
15702
|
+
})).readonly() }), {
|
|
15703
|
+
kind: "mutation",
|
|
15704
|
+
auth: "admin"
|
|
15651
15705
|
}), method(object({
|
|
15706
|
+
addonId: string(),
|
|
15707
|
+
params: record(string(), unknown()).optional()
|
|
15708
|
+
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
15709
|
+
kind: "mutation",
|
|
15710
|
+
auth: "admin"
|
|
15711
|
+
}), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
15652
15712
|
deviceId: number(),
|
|
15653
15713
|
key: string(),
|
|
15654
15714
|
value: unknown()
|
|
@@ -20788,6 +20848,12 @@ Object.freeze({
|
|
|
20788
20848
|
addonId: null,
|
|
20789
20849
|
access: "create"
|
|
20790
20850
|
},
|
|
20851
|
+
"deviceManager.adoptionListCandidateFilters": {
|
|
20852
|
+
capName: "device-manager",
|
|
20853
|
+
capScope: "system",
|
|
20854
|
+
addonId: null,
|
|
20855
|
+
access: "view"
|
|
20856
|
+
},
|
|
20791
20857
|
"deviceManager.adoptionListCandidates": {
|
|
20792
20858
|
capName: "device-manager",
|
|
20793
20859
|
capScope: "system",
|
|
@@ -20836,12 +20902,30 @@ Object.freeze({
|
|
|
20836
20902
|
addonId: null,
|
|
20837
20903
|
access: "create"
|
|
20838
20904
|
},
|
|
20905
|
+
"deviceManager.discoverAllProviders": {
|
|
20906
|
+
capName: "device-manager",
|
|
20907
|
+
capScope: "system",
|
|
20908
|
+
addonId: null,
|
|
20909
|
+
access: "create"
|
|
20910
|
+
},
|
|
20839
20911
|
"deviceManager.discoverDevices": {
|
|
20840
20912
|
capName: "device-manager",
|
|
20841
20913
|
capScope: "system",
|
|
20842
20914
|
addonId: null,
|
|
20843
20915
|
access: "create"
|
|
20844
20916
|
},
|
|
20917
|
+
"deviceManager.discoverProvider": {
|
|
20918
|
+
capName: "device-manager",
|
|
20919
|
+
capScope: "system",
|
|
20920
|
+
addonId: null,
|
|
20921
|
+
access: "create"
|
|
20922
|
+
},
|
|
20923
|
+
"deviceManager.discoveryProviders": {
|
|
20924
|
+
capName: "device-manager",
|
|
20925
|
+
capScope: "system",
|
|
20926
|
+
addonId: null,
|
|
20927
|
+
access: "view"
|
|
20928
|
+
},
|
|
20845
20929
|
"deviceManager.enable": {
|
|
20846
20930
|
capName: "device-manager",
|
|
20847
20931
|
capScope: "system",
|
|
@@ -20992,6 +21076,18 @@ Object.freeze({
|
|
|
20992
21076
|
addonId: null,
|
|
20993
21077
|
access: "create"
|
|
20994
21078
|
},
|
|
21079
|
+
"deviceManager.providerCreationType": {
|
|
21080
|
+
capName: "device-manager",
|
|
21081
|
+
capScope: "system",
|
|
21082
|
+
addonId: null,
|
|
21083
|
+
access: "view"
|
|
21084
|
+
},
|
|
21085
|
+
"deviceManager.providerDiscoveryParamsSchema": {
|
|
21086
|
+
capName: "device-manager",
|
|
21087
|
+
capScope: "system",
|
|
21088
|
+
addonId: null,
|
|
21089
|
+
access: "view"
|
|
21090
|
+
},
|
|
20995
21091
|
"deviceManager.registerDevice": {
|
|
20996
21092
|
capName: "device-manager",
|
|
20997
21093
|
capScope: "system",
|
|
@@ -21208,6 +21304,18 @@ Object.freeze({
|
|
|
21208
21304
|
addonId: null,
|
|
21209
21305
|
access: "view"
|
|
21210
21306
|
},
|
|
21307
|
+
"deviceProvider.getDiscoveryParamsSchema": {
|
|
21308
|
+
capName: "device-provider",
|
|
21309
|
+
capScope: "system",
|
|
21310
|
+
addonId: null,
|
|
21311
|
+
access: "view"
|
|
21312
|
+
},
|
|
21313
|
+
"deviceProvider.getManualCreationType": {
|
|
21314
|
+
capName: "device-provider",
|
|
21315
|
+
capScope: "system",
|
|
21316
|
+
addonId: null,
|
|
21317
|
+
access: "view"
|
|
21318
|
+
},
|
|
21211
21319
|
"deviceProvider.getStatus": {
|
|
21212
21320
|
capName: "device-provider",
|
|
21213
21321
|
capScope: "system",
|
|
@@ -75112,6 +75220,34 @@ function enumOptions(e) {
|
|
|
75112
75220
|
label: humanizeEnumName(name)
|
|
75113
75221
|
}));
|
|
75114
75222
|
}
|
|
75223
|
+
/**
|
|
75224
|
+
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
75225
|
+
* faults. HA does not surface these as errors — a fresh cloud-shadow read of an
|
|
75226
|
+
* idle docked robot legitimately returns `TaskComplete` (68). We relabel them to
|
|
75227
|
+
* the `Clear` label so the Error sensor only shows real faults (HA parity).
|
|
75228
|
+
* Extend this set if other non-fault codes surface as spurious "errors".
|
|
75229
|
+
*/
|
|
75230
|
+
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
75231
|
+
/** `MiotError` options with the benign completion codes remapped to the `Clear`
|
|
75232
|
+
* label, so the Error sensor reads as no-error for them (matches HA). */
|
|
75233
|
+
function errorEnumOptions() {
|
|
75234
|
+
const clearLabel = humanizeEnumName(MiotError[MiotError.Clear]);
|
|
75235
|
+
return enumOptions(MiotError).map((o) => BENIGN_ERROR_CODES.has(o.value) ? {
|
|
75236
|
+
...o,
|
|
75237
|
+
label: clearLabel
|
|
75238
|
+
} : o);
|
|
75239
|
+
}
|
|
75240
|
+
/**
|
|
75241
|
+
* Resolve the CURRENT catalog options for an enum-sensor by its MIoT coordinates
|
|
75242
|
+
* (across both the vacuum and mower enum-sensor catalogs). The enum-sensor child
|
|
75243
|
+
* persists its option map at adoption; preferring the live catalog here lets a
|
|
75244
|
+
* label change (e.g. the benign-error remap) take effect for already-adopted
|
|
75245
|
+
* children on the next boot — without a re-adopt. Null when no catalog entry
|
|
75246
|
+
* matches (persisted config is then the only source).
|
|
75247
|
+
*/
|
|
75248
|
+
function enumSensorOptionsFor(siid, piid) {
|
|
75249
|
+
return [...DREAME_ENUM_SENSORS, ...DREAME_MOWER_ENUM_SENSORS].find((es) => es.siid === siid && es.piid === piid)?.options ?? null;
|
|
75250
|
+
}
|
|
75115
75251
|
/** Humanise a Tasshack SNAKE_CASE enum name → spaced Title Case
|
|
75116
75252
|
* ('BACK_HOME' → 'Back Home'). */
|
|
75117
75253
|
function humanizeSnake(name) {
|
|
@@ -75237,7 +75373,7 @@ var DREAME_ENUM_SENSORS = [
|
|
|
75237
75373
|
label: "Error",
|
|
75238
75374
|
siid: 2,
|
|
75239
75375
|
piid: 2,
|
|
75240
|
-
options:
|
|
75376
|
+
options: errorEnumOptions(),
|
|
75241
75377
|
section: DIAG
|
|
75242
75378
|
},
|
|
75243
75379
|
{
|
|
@@ -76964,7 +77100,7 @@ var DreameEnumSensorDevice = class extends DreameChildDevice {
|
|
|
76964
77100
|
const cfg = dreameEnumSensorSchema.parse(ctx.persistedConfig ?? {});
|
|
76965
77101
|
this.#siid = cfg.siid;
|
|
76966
77102
|
this.#piid = cfg.piid;
|
|
76967
|
-
this.#options = cfg.options;
|
|
77103
|
+
this.#options = enumSensorOptionsFor(cfg.siid, cfg.piid) ?? cfg.options;
|
|
76968
77104
|
this.updateSourceInfo({
|
|
76969
77105
|
id: `${this.dreameDeviceId}:enum:${cfg.siid}-${cfg.piid}`,
|
|
76970
77106
|
system: "dreame"
|
|
@@ -77214,6 +77350,15 @@ var CONSUMABLES_CAP_NAME$1 = "consumables";
|
|
|
77214
77350
|
var CONSUMABLES_WAKE_RETRIES = 2;
|
|
77215
77351
|
/** Spacing between wake-window retries. */
|
|
77216
77352
|
var CONSUMABLES_RETRY_DELAY_MS = 1500;
|
|
77353
|
+
/**
|
|
77354
|
+
* Cloud-shadow (no-wake) refresh cadence for the mower's battery / charging /
|
|
77355
|
+
* status props. The shadow read (`refreshFromCache` → `getCachedProperties`)
|
|
77356
|
+
* hits the cloud SHADOW, not the device — so it succeeds even while the mower
|
|
77357
|
+
* sleeps at base, UNLIKE the synchronous CMS action above (which 80001s). The
|
|
77358
|
+
* mower never PUSHES battery over MQTT, so without this poll `batteryLevel`
|
|
77359
|
+
* stays null. A gentle interval keeps the level current as it charges.
|
|
77360
|
+
*/
|
|
77361
|
+
var SHADOW_POLL_MS = 12e4;
|
|
77217
77362
|
var COLD_START = {
|
|
77218
77363
|
activity: "idle",
|
|
77219
77364
|
batteryLevel: null,
|
|
@@ -77253,6 +77398,8 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
77253
77398
|
#prevStatus = null;
|
|
77254
77399
|
/** Last seen `chargingStatus` value — second awake-and-charging trigger. */
|
|
77255
77400
|
#prevCharging = null;
|
|
77401
|
+
/** Periodic cloud-shadow refresh timer (battery / charging / status). */
|
|
77402
|
+
#shadowTimer = null;
|
|
77256
77403
|
constructor(ctx) {
|
|
77257
77404
|
super(ctx);
|
|
77258
77405
|
}
|
|
@@ -77288,6 +77435,35 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
77288
77435
|
async onActivate() {
|
|
77289
77436
|
await super.onActivate();
|
|
77290
77437
|
this.refreshConsumables(CONSUMABLES_WAKE_RETRIES);
|
|
77438
|
+
this.#refreshShadow();
|
|
77439
|
+
this.#shadowTimer = setInterval(() => {
|
|
77440
|
+
this.#refreshShadow();
|
|
77441
|
+
}, SHADOW_POLL_MS);
|
|
77442
|
+
}
|
|
77443
|
+
async removeDevice() {
|
|
77444
|
+
if (this.#shadowTimer) {
|
|
77445
|
+
clearInterval(this.#shadowTimer);
|
|
77446
|
+
this.#shadowTimer = null;
|
|
77447
|
+
}
|
|
77448
|
+
await super.removeDevice();
|
|
77449
|
+
}
|
|
77450
|
+
/**
|
|
77451
|
+
* Seed the cached MIoT props (battery / charging / status …) from the CLOUD
|
|
77452
|
+
* SHADOW — a no-wake read that succeeds even while the mower sleeps at base.
|
|
77453
|
+
* `refreshFromCache` emits `stateChanged`, which drives `recomputeSlice`, so
|
|
77454
|
+
* the battery level lands in the slice. Best-effort; failures log at debug.
|
|
77455
|
+
*/
|
|
77456
|
+
async #refreshShadow() {
|
|
77457
|
+
const mower = this.resolveMower();
|
|
77458
|
+
if (mower === null) return;
|
|
77459
|
+
try {
|
|
77460
|
+
await mower.refreshFromCache();
|
|
77461
|
+
} catch (err) {
|
|
77462
|
+
this.ctx.logger.debug("dreame mower: cloud-shadow refresh failed", { meta: {
|
|
77463
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
77464
|
+
error: errMsg(err)
|
|
77465
|
+
} });
|
|
77466
|
+
}
|
|
77291
77467
|
}
|
|
77292
77468
|
recomputeSlice() {
|
|
77293
77469
|
const mower = this.resolveMower();
|
package/dist/addon.mjs
CHANGED
|
@@ -4680,7 +4680,7 @@ function preprocess(fn, schema) {
|
|
|
4680
4680
|
});
|
|
4681
4681
|
}
|
|
4682
4682
|
//#endregion
|
|
4683
|
-
//#region ../types/dist/sleep-
|
|
4683
|
+
//#region ../types/dist/sleep-C2M2zF7x.mjs
|
|
4684
4684
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4685
4685
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4686
4686
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -6248,6 +6248,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
6248
6248
|
DeviceType["Switch"] = "switch";
|
|
6249
6249
|
DeviceType["Sensor"] = "sensor";
|
|
6250
6250
|
DeviceType["Thermostat"] = "thermostat";
|
|
6251
|
+
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
6252
|
+
* `climate-control` cap surface with `Thermostat` but renders a
|
|
6253
|
+
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
6254
|
+
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
6255
|
+
* reusable by other AC integrations. */
|
|
6256
|
+
DeviceType["Climate"] = "climate";
|
|
6251
6257
|
DeviceType["Button"] = "button";
|
|
6252
6258
|
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
6253
6259
|
* event vocabulary verbatim (no normalization). Installed with the
|
|
@@ -9043,7 +9049,7 @@ var climateControlCapability = {
|
|
|
9043
9049
|
scope: "device",
|
|
9044
9050
|
deviceNative: true,
|
|
9045
9051
|
mode: "singleton",
|
|
9046
|
-
deviceTypes: [DeviceType.Thermostat],
|
|
9052
|
+
deviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
|
|
9047
9053
|
methods: {
|
|
9048
9054
|
setMode: method(object({
|
|
9049
9055
|
deviceId: number().int().nonnegative(),
|
|
@@ -13642,10 +13648,30 @@ var deviceProviderCapability = {
|
|
|
13642
13648
|
type: string()
|
|
13643
13649
|
}))),
|
|
13644
13650
|
supportsDiscovery: method(object({}), boolean()),
|
|
13645
|
-
|
|
13651
|
+
/**
|
|
13652
|
+
* Run a network scan. `params` carries optional provider-specific scan
|
|
13653
|
+
* inputs (e.g. a broadcast address / subnet for cross-subnet discovery),
|
|
13654
|
+
* shaped by `getDiscoveryParamsSchema`. Omitted for the generic scan
|
|
13655
|
+
* (provider uses its local-network default).
|
|
13656
|
+
*/
|
|
13657
|
+
discoverDevices: method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
|
|
13646
13658
|
kind: "mutation",
|
|
13647
13659
|
auth: "admin"
|
|
13648
13660
|
}),
|
|
13661
|
+
/**
|
|
13662
|
+
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
13663
|
+
* provider accepts (e.g. Gree's broadcast address for a different subnet).
|
|
13664
|
+
* `null` when the provider takes no extra scan params — the generic
|
|
13665
|
+
* aggregated scan never renders this; the per-integration scan does.
|
|
13666
|
+
*/
|
|
13667
|
+
getDiscoveryParamsSchema: method(object({}), CreationSchemaOutputSchema),
|
|
13668
|
+
/**
|
|
13669
|
+
* The DeviceType this provider creates via manual add (Camera for
|
|
13670
|
+
* Reolink/ONVIF, Container for Gree, Hub for Ecowitt). `null` when the
|
|
13671
|
+
* provider does not support manual creation. Lets the Add-Device dialog
|
|
13672
|
+
* pick the right type instead of assuming Camera.
|
|
13673
|
+
*/
|
|
13674
|
+
getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
|
|
13649
13675
|
adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
|
|
13650
13676
|
kind: "mutation",
|
|
13651
13677
|
auth: "admin"
|
|
@@ -13769,9 +13795,23 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
13769
13795
|
async supportsDiscovery() {
|
|
13770
13796
|
return false;
|
|
13771
13797
|
}
|
|
13772
|
-
async discoverDevices() {
|
|
13798
|
+
async discoverDevices(_input) {
|
|
13773
13799
|
return [];
|
|
13774
13800
|
}
|
|
13801
|
+
/** Extra per-scan input form (e.g. a broadcast address for another subnet).
|
|
13802
|
+
* Null = no extra params. Override in providers that support scoped scans. */
|
|
13803
|
+
async getDiscoveryParamsSchema() {
|
|
13804
|
+
return null;
|
|
13805
|
+
}
|
|
13806
|
+
/**
|
|
13807
|
+
* The DeviceType this provider creates via manual add — derived from the
|
|
13808
|
+
* `deviceClasses` map (first registered type). `null` when manual creation is
|
|
13809
|
+
* unsupported. Lets the Add-Device dialog pick the right type per provider.
|
|
13810
|
+
*/
|
|
13811
|
+
async getManualCreationType() {
|
|
13812
|
+
if (!await this.supportsManualCreation()) return { deviceType: null };
|
|
13813
|
+
return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
|
|
13814
|
+
}
|
|
13775
13815
|
async adoptDiscoveredDevice(_input) {
|
|
13776
13816
|
throw new Error(`${this.providerName} provider does not support discovery-based adoption`);
|
|
13777
13817
|
}
|
|
@@ -15633,7 +15673,10 @@ method(object({
|
|
|
15633
15673
|
}), FieldProbeResultSchema, {
|
|
15634
15674
|
kind: "mutation",
|
|
15635
15675
|
auth: "admin"
|
|
15636
|
-
}), method(
|
|
15676
|
+
}), method(object({
|
|
15677
|
+
addonId: string(),
|
|
15678
|
+
integrationId: string()
|
|
15679
|
+
}), object({ filters: array(AdoptionFilterSchema) }), { auth: "admin" }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
|
|
15637
15680
|
addonId: string(),
|
|
15638
15681
|
integrationId: string()
|
|
15639
15682
|
}), AdoptionStatusSchema, {
|
|
@@ -15648,7 +15691,24 @@ method(object({
|
|
|
15648
15691
|
}), method(ResyncInputSchema, ResyncResultSchema, {
|
|
15649
15692
|
kind: "mutation",
|
|
15650
15693
|
auth: "admin"
|
|
15694
|
+
}), method(object({}), object({ providers: array(object({
|
|
15695
|
+
addonId: string(),
|
|
15696
|
+
label: string()
|
|
15697
|
+
})).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
|
|
15698
|
+
addonId: string(),
|
|
15699
|
+
label: string(),
|
|
15700
|
+
candidates: array(DiscoveryCandidateSchema).readonly(),
|
|
15701
|
+
error: string().nullable()
|
|
15702
|
+
})).readonly() }), {
|
|
15703
|
+
kind: "mutation",
|
|
15704
|
+
auth: "admin"
|
|
15651
15705
|
}), method(object({
|
|
15706
|
+
addonId: string(),
|
|
15707
|
+
params: record(string(), unknown()).optional()
|
|
15708
|
+
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
15709
|
+
kind: "mutation",
|
|
15710
|
+
auth: "admin"
|
|
15711
|
+
}), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
15652
15712
|
deviceId: number(),
|
|
15653
15713
|
key: string(),
|
|
15654
15714
|
value: unknown()
|
|
@@ -20788,6 +20848,12 @@ Object.freeze({
|
|
|
20788
20848
|
addonId: null,
|
|
20789
20849
|
access: "create"
|
|
20790
20850
|
},
|
|
20851
|
+
"deviceManager.adoptionListCandidateFilters": {
|
|
20852
|
+
capName: "device-manager",
|
|
20853
|
+
capScope: "system",
|
|
20854
|
+
addonId: null,
|
|
20855
|
+
access: "view"
|
|
20856
|
+
},
|
|
20791
20857
|
"deviceManager.adoptionListCandidates": {
|
|
20792
20858
|
capName: "device-manager",
|
|
20793
20859
|
capScope: "system",
|
|
@@ -20836,12 +20902,30 @@ Object.freeze({
|
|
|
20836
20902
|
addonId: null,
|
|
20837
20903
|
access: "create"
|
|
20838
20904
|
},
|
|
20905
|
+
"deviceManager.discoverAllProviders": {
|
|
20906
|
+
capName: "device-manager",
|
|
20907
|
+
capScope: "system",
|
|
20908
|
+
addonId: null,
|
|
20909
|
+
access: "create"
|
|
20910
|
+
},
|
|
20839
20911
|
"deviceManager.discoverDevices": {
|
|
20840
20912
|
capName: "device-manager",
|
|
20841
20913
|
capScope: "system",
|
|
20842
20914
|
addonId: null,
|
|
20843
20915
|
access: "create"
|
|
20844
20916
|
},
|
|
20917
|
+
"deviceManager.discoverProvider": {
|
|
20918
|
+
capName: "device-manager",
|
|
20919
|
+
capScope: "system",
|
|
20920
|
+
addonId: null,
|
|
20921
|
+
access: "create"
|
|
20922
|
+
},
|
|
20923
|
+
"deviceManager.discoveryProviders": {
|
|
20924
|
+
capName: "device-manager",
|
|
20925
|
+
capScope: "system",
|
|
20926
|
+
addonId: null,
|
|
20927
|
+
access: "view"
|
|
20928
|
+
},
|
|
20845
20929
|
"deviceManager.enable": {
|
|
20846
20930
|
capName: "device-manager",
|
|
20847
20931
|
capScope: "system",
|
|
@@ -20992,6 +21076,18 @@ Object.freeze({
|
|
|
20992
21076
|
addonId: null,
|
|
20993
21077
|
access: "create"
|
|
20994
21078
|
},
|
|
21079
|
+
"deviceManager.providerCreationType": {
|
|
21080
|
+
capName: "device-manager",
|
|
21081
|
+
capScope: "system",
|
|
21082
|
+
addonId: null,
|
|
21083
|
+
access: "view"
|
|
21084
|
+
},
|
|
21085
|
+
"deviceManager.providerDiscoveryParamsSchema": {
|
|
21086
|
+
capName: "device-manager",
|
|
21087
|
+
capScope: "system",
|
|
21088
|
+
addonId: null,
|
|
21089
|
+
access: "view"
|
|
21090
|
+
},
|
|
20995
21091
|
"deviceManager.registerDevice": {
|
|
20996
21092
|
capName: "device-manager",
|
|
20997
21093
|
capScope: "system",
|
|
@@ -21208,6 +21304,18 @@ Object.freeze({
|
|
|
21208
21304
|
addonId: null,
|
|
21209
21305
|
access: "view"
|
|
21210
21306
|
},
|
|
21307
|
+
"deviceProvider.getDiscoveryParamsSchema": {
|
|
21308
|
+
capName: "device-provider",
|
|
21309
|
+
capScope: "system",
|
|
21310
|
+
addonId: null,
|
|
21311
|
+
access: "view"
|
|
21312
|
+
},
|
|
21313
|
+
"deviceProvider.getManualCreationType": {
|
|
21314
|
+
capName: "device-provider",
|
|
21315
|
+
capScope: "system",
|
|
21316
|
+
addonId: null,
|
|
21317
|
+
access: "view"
|
|
21318
|
+
},
|
|
21211
21319
|
"deviceProvider.getStatus": {
|
|
21212
21320
|
capName: "device-provider",
|
|
21213
21321
|
capScope: "system",
|
|
@@ -75112,6 +75220,34 @@ function enumOptions(e) {
|
|
|
75112
75220
|
label: humanizeEnumName(name)
|
|
75113
75221
|
}));
|
|
75114
75222
|
}
|
|
75223
|
+
/**
|
|
75224
|
+
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
75225
|
+
* faults. HA does not surface these as errors — a fresh cloud-shadow read of an
|
|
75226
|
+
* idle docked robot legitimately returns `TaskComplete` (68). We relabel them to
|
|
75227
|
+
* the `Clear` label so the Error sensor only shows real faults (HA parity).
|
|
75228
|
+
* Extend this set if other non-fault codes surface as spurious "errors".
|
|
75229
|
+
*/
|
|
75230
|
+
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
75231
|
+
/** `MiotError` options with the benign completion codes remapped to the `Clear`
|
|
75232
|
+
* label, so the Error sensor reads as no-error for them (matches HA). */
|
|
75233
|
+
function errorEnumOptions() {
|
|
75234
|
+
const clearLabel = humanizeEnumName(MiotError[MiotError.Clear]);
|
|
75235
|
+
return enumOptions(MiotError).map((o) => BENIGN_ERROR_CODES.has(o.value) ? {
|
|
75236
|
+
...o,
|
|
75237
|
+
label: clearLabel
|
|
75238
|
+
} : o);
|
|
75239
|
+
}
|
|
75240
|
+
/**
|
|
75241
|
+
* Resolve the CURRENT catalog options for an enum-sensor by its MIoT coordinates
|
|
75242
|
+
* (across both the vacuum and mower enum-sensor catalogs). The enum-sensor child
|
|
75243
|
+
* persists its option map at adoption; preferring the live catalog here lets a
|
|
75244
|
+
* label change (e.g. the benign-error remap) take effect for already-adopted
|
|
75245
|
+
* children on the next boot — without a re-adopt. Null when no catalog entry
|
|
75246
|
+
* matches (persisted config is then the only source).
|
|
75247
|
+
*/
|
|
75248
|
+
function enumSensorOptionsFor(siid, piid) {
|
|
75249
|
+
return [...DREAME_ENUM_SENSORS, ...DREAME_MOWER_ENUM_SENSORS].find((es) => es.siid === siid && es.piid === piid)?.options ?? null;
|
|
75250
|
+
}
|
|
75115
75251
|
/** Humanise a Tasshack SNAKE_CASE enum name → spaced Title Case
|
|
75116
75252
|
* ('BACK_HOME' → 'Back Home'). */
|
|
75117
75253
|
function humanizeSnake(name) {
|
|
@@ -75237,7 +75373,7 @@ var DREAME_ENUM_SENSORS = [
|
|
|
75237
75373
|
label: "Error",
|
|
75238
75374
|
siid: 2,
|
|
75239
75375
|
piid: 2,
|
|
75240
|
-
options:
|
|
75376
|
+
options: errorEnumOptions(),
|
|
75241
75377
|
section: DIAG
|
|
75242
75378
|
},
|
|
75243
75379
|
{
|
|
@@ -76964,7 +77100,7 @@ var DreameEnumSensorDevice = class extends DreameChildDevice {
|
|
|
76964
77100
|
const cfg = dreameEnumSensorSchema.parse(ctx.persistedConfig ?? {});
|
|
76965
77101
|
this.#siid = cfg.siid;
|
|
76966
77102
|
this.#piid = cfg.piid;
|
|
76967
|
-
this.#options = cfg.options;
|
|
77103
|
+
this.#options = enumSensorOptionsFor(cfg.siid, cfg.piid) ?? cfg.options;
|
|
76968
77104
|
this.updateSourceInfo({
|
|
76969
77105
|
id: `${this.dreameDeviceId}:enum:${cfg.siid}-${cfg.piid}`,
|
|
76970
77106
|
system: "dreame"
|
|
@@ -77214,6 +77350,15 @@ var CONSUMABLES_CAP_NAME$1 = "consumables";
|
|
|
77214
77350
|
var CONSUMABLES_WAKE_RETRIES = 2;
|
|
77215
77351
|
/** Spacing between wake-window retries. */
|
|
77216
77352
|
var CONSUMABLES_RETRY_DELAY_MS = 1500;
|
|
77353
|
+
/**
|
|
77354
|
+
* Cloud-shadow (no-wake) refresh cadence for the mower's battery / charging /
|
|
77355
|
+
* status props. The shadow read (`refreshFromCache` → `getCachedProperties`)
|
|
77356
|
+
* hits the cloud SHADOW, not the device — so it succeeds even while the mower
|
|
77357
|
+
* sleeps at base, UNLIKE the synchronous CMS action above (which 80001s). The
|
|
77358
|
+
* mower never PUSHES battery over MQTT, so without this poll `batteryLevel`
|
|
77359
|
+
* stays null. A gentle interval keeps the level current as it charges.
|
|
77360
|
+
*/
|
|
77361
|
+
var SHADOW_POLL_MS = 12e4;
|
|
77217
77362
|
var COLD_START = {
|
|
77218
77363
|
activity: "idle",
|
|
77219
77364
|
batteryLevel: null,
|
|
@@ -77253,6 +77398,8 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
77253
77398
|
#prevStatus = null;
|
|
77254
77399
|
/** Last seen `chargingStatus` value — second awake-and-charging trigger. */
|
|
77255
77400
|
#prevCharging = null;
|
|
77401
|
+
/** Periodic cloud-shadow refresh timer (battery / charging / status). */
|
|
77402
|
+
#shadowTimer = null;
|
|
77256
77403
|
constructor(ctx) {
|
|
77257
77404
|
super(ctx);
|
|
77258
77405
|
}
|
|
@@ -77288,6 +77435,35 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
77288
77435
|
async onActivate() {
|
|
77289
77436
|
await super.onActivate();
|
|
77290
77437
|
this.refreshConsumables(CONSUMABLES_WAKE_RETRIES);
|
|
77438
|
+
this.#refreshShadow();
|
|
77439
|
+
this.#shadowTimer = setInterval(() => {
|
|
77440
|
+
this.#refreshShadow();
|
|
77441
|
+
}, SHADOW_POLL_MS);
|
|
77442
|
+
}
|
|
77443
|
+
async removeDevice() {
|
|
77444
|
+
if (this.#shadowTimer) {
|
|
77445
|
+
clearInterval(this.#shadowTimer);
|
|
77446
|
+
this.#shadowTimer = null;
|
|
77447
|
+
}
|
|
77448
|
+
await super.removeDevice();
|
|
77449
|
+
}
|
|
77450
|
+
/**
|
|
77451
|
+
* Seed the cached MIoT props (battery / charging / status …) from the CLOUD
|
|
77452
|
+
* SHADOW — a no-wake read that succeeds even while the mower sleeps at base.
|
|
77453
|
+
* `refreshFromCache` emits `stateChanged`, which drives `recomputeSlice`, so
|
|
77454
|
+
* the battery level lands in the slice. Best-effort; failures log at debug.
|
|
77455
|
+
*/
|
|
77456
|
+
async #refreshShadow() {
|
|
77457
|
+
const mower = this.resolveMower();
|
|
77458
|
+
if (mower === null) return;
|
|
77459
|
+
try {
|
|
77460
|
+
await mower.refreshFromCache();
|
|
77461
|
+
} catch (err) {
|
|
77462
|
+
this.ctx.logger.debug("dreame mower: cloud-shadow refresh failed", { meta: {
|
|
77463
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
77464
|
+
error: errMsg(err)
|
|
77465
|
+
} });
|
|
77466
|
+
}
|
|
77291
77467
|
}
|
|
77292
77468
|
recomputeSlice() {
|
|
77293
77469
|
const mower = this.resolveMower();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-dreame",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|