@mega-yfue/eufy-sdk 0.5.0-beta.51 → 0.5.0-beta.53
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/client/eufy-mega.d.ts +7 -2
- package/dist/index.js +65 -52
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/arming.d.ts +12 -6
- package/dist/transport/tuya/client.d.ts +4 -3
- package/dist/transport/tuya/command-router.d.ts +10 -14
- package/dist/transport/tuya/request.d.ts +3 -3
- package/package.json +1 -1
|
@@ -454,8 +454,9 @@ export declare class EufyMega extends EventEmitter {
|
|
|
454
454
|
*
|
|
455
455
|
* Side-effect: registers `eufy_home_tuya` devices with the Tuya command router so
|
|
456
456
|
* the command dispatcher can resolve a eufy SN → Tuya devId without a separate lookup.
|
|
457
|
-
* The Tuya id is
|
|
458
|
-
* `
|
|
457
|
+
* The Tuya id is the first non-empty `tuya_uuid`, `tuya_virtual_id`, `tuya_device_id` or
|
|
458
|
+
* `virtualId` on the raw cloud record, else the device's serial: the app keys its Tuya device by
|
|
459
|
+
* the eufy device id, and a record that carries none of those fields is read under its serial.
|
|
459
460
|
*
|
|
460
461
|
* A partial cloud outage still resolves, with the devices that answered plus the ones already known — but a
|
|
461
462
|
* session the cloud has rejected REJECTS, with {@link SessionExpiredError}. An empty list would be
|
|
@@ -790,6 +791,10 @@ export declare class EufyMega extends EventEmitter {
|
|
|
790
791
|
/**
|
|
791
792
|
* Subscribe the devices on one credential scope; a failing subscribe is reported, not fatal (one
|
|
792
793
|
* unreachable device must not stop the rest of the roster from coming up).
|
|
794
|
+
*
|
|
795
|
+
* A Tuya clean-line device's DPs are also read from the Tuya cloud, whether or not its subscribe
|
|
796
|
+
* succeeded: the cloud read is a separate plane, and the broker can refuse a subscribe for a device
|
|
797
|
+
* whose DPs the Tuya cloud still serves.
|
|
793
798
|
*/
|
|
794
799
|
private subscribeMqttDevices;
|
|
795
800
|
/**
|
package/dist/index.js
CHANGED
|
@@ -7769,6 +7769,12 @@ var ARMING_CMD = {
|
|
|
7769
7769
|
SET_ALL_ACTION: 1255
|
|
7770
7770
|
};
|
|
7771
7771
|
var DEVICE_NOTIFY_FLAG = 8;
|
|
7772
|
+
var STANDALONE_ACTION_MODELS = /* @__PURE__ */ new Set(["T8170", "T8171"]);
|
|
7773
|
+
var STANDALONE_READ_MODELS = /* @__PURE__ */ new Set([...STANDALONE_ACTION_MODELS, "T8400", "T8442", "T8410"]);
|
|
7774
|
+
function ownAction(read, mode) {
|
|
7775
|
+
const value = Number(read(`${mode}Action`)?.value);
|
|
7776
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
7777
|
+
}
|
|
7772
7778
|
function actionTable(read, mode) {
|
|
7773
7779
|
const value = read(`${mode}ActionTable`)?.value;
|
|
7774
7780
|
return value && typeof value === "object" && Array.isArray(value.devices) ? value : void 0;
|
|
@@ -7811,6 +7817,13 @@ function alarmDelayCommand(mode, config, ctx) {
|
|
|
7811
7817
|
};
|
|
7812
7818
|
return setJsonRaw(ARMING_CMD.SET_ALL_ACTION, data, ctx, STATION_CHANNEL2);
|
|
7813
7819
|
}
|
|
7820
|
+
function ownTable(read, mode, channel) {
|
|
7821
|
+
const flags = ownAction(read, mode);
|
|
7822
|
+
if (flags === void 0)
|
|
7823
|
+
return void 0;
|
|
7824
|
+
const wire = ARMING_MODE_WIRE[mode];
|
|
7825
|
+
return { mode_id: wire, devices: [{ device_channel: channel, action: wire << 24 | flags }] };
|
|
7826
|
+
}
|
|
7814
7827
|
var ACTION_TABLE_LAG_MS = 15e3;
|
|
7815
7828
|
function withDeviceNotification(table, channel, on) {
|
|
7816
7829
|
if (!table?.devices.some((d) => d.device_channel === channel))
|
|
@@ -7879,25 +7892,29 @@ var ARMING_MEMBERS = {
|
|
|
7879
7892
|
/**
|
|
7880
7893
|
* Whether the device on `channel` sends a push notification in `mode`: its {@link DEVICE_NOTIFY_FLAG}
|
|
7881
7894
|
* in the action table the station reports for that mode. `undefined` where the station reports no table
|
|
7882
|
-
* or the table does not list `channel`. A
|
|
7895
|
+
* or the table does not list `channel`. A standalone camera answers from its own 1225/1239/1148 flags,
|
|
7896
|
+
* for its own channel only. A device's channel is `EufyDevice.channel`.
|
|
7883
7897
|
*/
|
|
7884
|
-
deviceNotification: method(({ read }) => (mode, channel) => {
|
|
7885
|
-
const action = actionTable(read, mode)?.devices.find((d) => d.device_channel === channel)?.action;
|
|
7898
|
+
deviceNotification: method(({ ctx, read }) => (mode, channel) => {
|
|
7899
|
+
const action = ctx.model === "T8010" ? actionTable(read, mode)?.devices.find((d) => d.device_channel === channel)?.action : channel === ctx.channel ? ownAction(read, mode) : void 0;
|
|
7886
7900
|
return typeof action === "number" ? (action & DEVICE_NOTIFY_FLAG) !== 0 : void 0;
|
|
7887
|
-
}, "Whether the device on a channel sends a push notification in a guard mode (home/away).", (ctx) => ctx.model === "T8010"),
|
|
7901
|
+
}, "Whether the device on a channel sends a push notification in a guard mode (home/away/custom1).", (ctx) => ctx.model === "T8010" || STANDALONE_READ_MODELS.has(ctx.model ?? "")),
|
|
7888
7902
|
/**
|
|
7889
7903
|
* Turn the push notification of the device on `channel` on or off in `mode` (cmd
|
|
7890
7904
|
* {@link ARMING_CMD.SET_ALL_ACTION}): the station's reported table written back with that one flag
|
|
7891
|
-
* changed.
|
|
7892
|
-
*
|
|
7893
|
-
*
|
|
7905
|
+
* changed. A standalone camera is sent a one-entry `{mode_id, devices}` table built from its own flags,
|
|
7906
|
+
* for its own channel only. Rejects, sending nothing, where the table does not list `channel`. A write
|
|
7907
|
+
* builds on the last one sent for that mode until the device reports a table (`${mode}Action` on a
|
|
7908
|
+
* standalone camera) observed 15 s after it, so a second write does not revert it; a write whose dispatch
|
|
7909
|
+
* rejects is not built on.
|
|
7894
7910
|
*/
|
|
7895
7911
|
setDeviceNotification: method(({ ctx, sink, read }) => {
|
|
7896
7912
|
const sent2 = /* @__PURE__ */ new Map();
|
|
7897
7913
|
return (mode, channel, on) => {
|
|
7898
7914
|
const last = sent2.get(mode);
|
|
7899
|
-
const
|
|
7900
|
-
const
|
|
7915
|
+
const reported = ctx.model === "T8010" ? `${mode}ActionTable` : `${mode}Action`;
|
|
7916
|
+
const observed = read(reported)?.ts ?? 0;
|
|
7917
|
+
const base = last && observed < last.at + ACTION_TABLE_LAG_MS ? last.table : ctx.model === "T8010" ? actionTable(read, mode) : ownTable(read, mode, ctx.channel);
|
|
7901
7918
|
const table = withDeviceNotification(base, channel, on);
|
|
7902
7919
|
if (!table) {
|
|
7903
7920
|
return Promise.reject(new Error(`arming: no ${mode} action table lists channel ${channel} [${describeDevice(ctx)}]`));
|
|
@@ -7913,7 +7930,7 @@ var ARMING_MEMBERS = {
|
|
|
7913
7930
|
throw err;
|
|
7914
7931
|
});
|
|
7915
7932
|
};
|
|
7916
|
-
}, "Turn the push notification of the device on a channel on or off in a guard mode (home/away).", (ctx) => ctx.model === "T8010")
|
|
7933
|
+
}, "Turn the push notification of the device on a channel on or off in a guard mode (home/away/custom1).", (ctx) => ctx.model === "T8010" || STANDALONE_ACTION_MODELS.has(ctx.model ?? ""))
|
|
7917
7934
|
};
|
|
7918
7935
|
function decodeAlarmPhase(signal) {
|
|
7919
7936
|
const type = signal.source === "push" ? signal.payload?.["type"] : void 0;
|
|
@@ -11544,7 +11561,7 @@ var SECURITY_PARAMS = {
|
|
|
11544
11561
|
1148: {
|
|
11545
11562
|
name: "custom1Action",
|
|
11546
11563
|
type: "number",
|
|
11547
|
-
provenance: "
|
|
11564
|
+
provenance: "verified"
|
|
11548
11565
|
},
|
|
11549
11566
|
1149: {
|
|
11550
11567
|
name: "custom2Action",
|
|
@@ -11759,7 +11776,7 @@ var SECURITY_PARAMS = {
|
|
|
11759
11776
|
1225: {
|
|
11760
11777
|
name: "homeAction",
|
|
11761
11778
|
type: "number",
|
|
11762
|
-
provenance: "
|
|
11779
|
+
provenance: "verified"
|
|
11763
11780
|
},
|
|
11764
11781
|
1229: {
|
|
11765
11782
|
name: "param1229",
|
|
@@ -25525,11 +25542,11 @@ function buildPublishDpsAction(devId, gwId, dps) {
|
|
|
25525
25542
|
postData: JSON.stringify({ gwId, devId, dps: JSON.stringify(dps) })
|
|
25526
25543
|
};
|
|
25527
25544
|
}
|
|
25528
|
-
function buildGetDeviceDpsAction(devId
|
|
25545
|
+
function buildGetDeviceDpsAction(devId) {
|
|
25529
25546
|
return {
|
|
25530
|
-
a: "smartlife.m.device.
|
|
25531
|
-
v: "
|
|
25532
|
-
postData: JSON.stringify({ devId
|
|
25547
|
+
a: "smartlife.m.device.dp.get",
|
|
25548
|
+
v: "1.0",
|
|
25549
|
+
postData: JSON.stringify({ devId })
|
|
25533
25550
|
};
|
|
25534
25551
|
}
|
|
25535
25552
|
function buildUsernameTokenGetAction(countryCode, username) {
|
|
@@ -25685,11 +25702,12 @@ var TuyaClient = class {
|
|
|
25685
25702
|
return { sid: result.sid, uid: result.uid };
|
|
25686
25703
|
}
|
|
25687
25704
|
/**
|
|
25688
|
-
* READ
|
|
25689
|
-
* Builds the request without needing a working signer; the signer is
|
|
25705
|
+
* READ a device's current data-points (`smartlife.m.device.dp.get`). The envelope's `result` is a
|
|
25706
|
+
* flat `{ "<dpId>": value }` map. Builds the request without needing a working signer; the signer is
|
|
25707
|
+
* only exercised on send.
|
|
25690
25708
|
*/
|
|
25691
|
-
async getDeviceDps(devId
|
|
25692
|
-
return this.call(buildGetDeviceDpsAction(devId
|
|
25709
|
+
async getDeviceDps(devId) {
|
|
25710
|
+
return this.call(buildGetDeviceDpsAction(devId));
|
|
25693
25711
|
}
|
|
25694
25712
|
/**
|
|
25695
25713
|
* CONTROL: publish data-points to a device (`smartlife.m.device.dp.publish`). `dps` is
|
|
@@ -25780,9 +25798,9 @@ var TuyaCommandRouter = class {
|
|
|
25780
25798
|
/** Promise that resolves once Tuya login has completed. Reset by {@link bind}. */
|
|
25781
25799
|
loginOnce = null;
|
|
25782
25800
|
/**
|
|
25783
|
-
* eufy SN → Tuya device ids, populated
|
|
25784
|
-
* The
|
|
25785
|
-
* `
|
|
25801
|
+
* eufy SN → Tuya device ids, populated via {@link registerDevice} once the device list loads.
|
|
25802
|
+
* The Tuya id is the record's first non-empty `tuya_uuid` / `tuya_virtual_id` / `tuya_device_id` /
|
|
25803
|
+
* `virtualId`, or the device's serial when the record carries none of them.
|
|
25786
25804
|
*/
|
|
25787
25805
|
snMap = /* @__PURE__ */ new Map();
|
|
25788
25806
|
constructor(config = {}) {
|
|
@@ -25804,10 +25822,10 @@ var TuyaCommandRouter = class {
|
|
|
25804
25822
|
this.loginOnce = null;
|
|
25805
25823
|
}
|
|
25806
25824
|
/**
|
|
25807
|
-
* Register a eufy SN → Tuya devId mapping
|
|
25808
|
-
*
|
|
25809
|
-
*
|
|
25810
|
-
*
|
|
25825
|
+
* Register a eufy SN → Tuya devId mapping for an `eufy_home_tuya` device. `devId` is the record's
|
|
25826
|
+
* first non-empty `tuya_uuid` / `tuya_virtual_id` / `tuya_device_id` / `virtualId`, or the device's
|
|
25827
|
+
* serial when the record carries none of them. `gwId` defaults to `devId` — standalone devices
|
|
25828
|
+
* share the two.
|
|
25811
25829
|
*/
|
|
25812
25830
|
registerDevice(sn, devId, gwId = devId) {
|
|
25813
25831
|
this.snMap.set(sn, { devId, gwId });
|
|
@@ -25831,13 +25849,9 @@ var TuyaCommandRouter = class {
|
|
|
25831
25849
|
return this.loginOnce;
|
|
25832
25850
|
}
|
|
25833
25851
|
/**
|
|
25834
|
-
*
|
|
25835
|
-
* the
|
|
25836
|
-
*
|
|
25837
|
-
*
|
|
25838
|
-
* The response shape from `getDeviceDps` is not yet pinned from a live capture. The defensive
|
|
25839
|
-
* extraction tries both `result.dps` (a nested map) and bare `result` (a flat map), and returns
|
|
25840
|
-
* `null` when neither yields a non-empty record.
|
|
25852
|
+
* Read a device's current DPs from the Tuya cloud (`smartlife.m.device.dp.get`) and answer the DP
|
|
25853
|
+
* map: the reply's flat `result`, kept to integer DP keys with scalar values. Answers `null` for an
|
|
25854
|
+
* unregistered device, a failed reply, or a reply with no DPs.
|
|
25841
25855
|
*/
|
|
25842
25856
|
async fetchDps(sn) {
|
|
25843
25857
|
const ids = this.snMap.get(sn);
|
|
@@ -25847,12 +25861,8 @@ var TuyaCommandRouter = class {
|
|
|
25847
25861
|
const res = await this.client.getDeviceDps(ids.devId);
|
|
25848
25862
|
if (!res.success || !res.result)
|
|
25849
25863
|
return null;
|
|
25850
|
-
const
|
|
25851
|
-
|
|
25852
|
-
return nested;
|
|
25853
|
-
}
|
|
25854
|
-
const flat = parseTuyaDpEvent(res.result);
|
|
25855
|
-
return flat ? flat : null;
|
|
25864
|
+
const dps = parseTuyaDpEvent(res.result);
|
|
25865
|
+
return dps ? dps : null;
|
|
25856
25866
|
}
|
|
25857
25867
|
/**
|
|
25858
25868
|
* Route an `aiot-dp` {@link Command} to the Tuya REST API.
|
|
@@ -25871,7 +25881,7 @@ var TuyaCommandRouter = class {
|
|
|
25871
25881
|
await this.ensureLoggedIn();
|
|
25872
25882
|
const ids = this.snMap.get(sn);
|
|
25873
25883
|
if (!ids) {
|
|
25874
|
-
throw new Error(`TuyaCommandRouter: no Tuya device id registered for ${sn}. Ensure getDevices() was called
|
|
25884
|
+
throw new Error(`TuyaCommandRouter: no Tuya device id registered for ${sn}. Ensure getDevices() was called; it registers every eufy_home_tuya device under its Tuya id or its serial.`);
|
|
25875
25885
|
}
|
|
25876
25886
|
await this.client.publishDps(ids.devId, ids.gwId, { [cmd.dp]: cmd.value }, { allowUnverified: this.allowUnverified });
|
|
25877
25887
|
}
|
|
@@ -28679,8 +28689,9 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28679
28689
|
*
|
|
28680
28690
|
* Side-effect: registers `eufy_home_tuya` devices with the Tuya command router so
|
|
28681
28691
|
* the command dispatcher can resolve a eufy SN → Tuya devId without a separate lookup.
|
|
28682
|
-
* The Tuya id is
|
|
28683
|
-
* `
|
|
28692
|
+
* The Tuya id is the first non-empty `tuya_uuid`, `tuya_virtual_id`, `tuya_device_id` or
|
|
28693
|
+
* `virtualId` on the raw cloud record, else the device's serial: the app keys its Tuya device by
|
|
28694
|
+
* the eufy device id, and a record that carries none of those fields is read under its serial.
|
|
28684
28695
|
*
|
|
28685
28696
|
* A partial cloud outage still resolves, with the devices that answered plus the ones already known — but a
|
|
28686
28697
|
* session the cloud has rejected REJECTS, with {@link SessionExpiredError}. An empty list would be
|
|
@@ -28692,9 +28703,7 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
28692
28703
|
if (dev.category !== "eufy_home_tuya")
|
|
28693
28704
|
continue;
|
|
28694
28705
|
const raw = dev.raw ?? {};
|
|
28695
|
-
|
|
28696
|
-
if (devId)
|
|
28697
|
-
this.tuya.registerDevice(dev.sn, devId);
|
|
28706
|
+
this.tuya.registerDevice(dev.sn, tuyaDevIdFrom(raw) ?? dev.sn);
|
|
28698
28707
|
}
|
|
28699
28708
|
return devices;
|
|
28700
28709
|
}
|
|
@@ -29362,21 +29371,25 @@ var EufyMega = class extends EventEmitter13 {
|
|
|
29362
29371
|
/**
|
|
29363
29372
|
* Subscribe the devices on one credential scope; a failing subscribe is reported, not fatal (one
|
|
29364
29373
|
* unreachable device must not stop the rest of the roster from coming up).
|
|
29374
|
+
*
|
|
29375
|
+
* A Tuya clean-line device's DPs are also read from the Tuya cloud, whether or not its subscribe
|
|
29376
|
+
* succeeded: the cloud read is a separate plane, and the broker can refuse a subscribe for a device
|
|
29377
|
+
* whose DPs the Tuya cloud still serves.
|
|
29365
29378
|
*/
|
|
29366
29379
|
async subscribeMqttDevices(scope, transport) {
|
|
29367
29380
|
for (const d of this.getMqttDevices().filter((dev) => mqttScopeFor(dev) === scope)) {
|
|
29368
29381
|
try {
|
|
29369
29382
|
await transport.subscribeDevice(d);
|
|
29370
29383
|
await this.sendRealtimeInit(d.sn);
|
|
29371
|
-
if (d.category === "eufy_home_tuya") {
|
|
29372
|
-
this.tuya.fetchDps(d.sn).then((dps) => {
|
|
29373
|
-
if (dps)
|
|
29374
|
-
this.tuyaDpRouter.deliver(d.sn, dps);
|
|
29375
|
-
}).catch((e) => this.reportError(e));
|
|
29376
|
-
}
|
|
29377
29384
|
} catch (e) {
|
|
29378
29385
|
this.reportError(e);
|
|
29379
29386
|
}
|
|
29387
|
+
if (d.category === "eufy_home_tuya") {
|
|
29388
|
+
this.tuya.fetchDps(d.sn).then((dps) => {
|
|
29389
|
+
if (dps)
|
|
29390
|
+
this.tuyaDpRouter.deliver(d.sn, dps);
|
|
29391
|
+
}).catch((e) => this.reportError(e));
|
|
29392
|
+
}
|
|
29380
29393
|
}
|
|
29381
29394
|
}
|
|
29382
29395
|
/**
|