@jiangtaste/baiwei-sdk 1.0.0

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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/dist/core/BaiweiClient.d.ts +38 -0
  4. package/dist/core/BaiweiClient.js +94 -0
  5. package/dist/core/BaiweiSession.d.ts +56 -0
  6. package/dist/core/BaiweiSession.js +229 -0
  7. package/dist/core/Client.d.ts +74 -0
  8. package/dist/core/Client.js +127 -0
  9. package/dist/core/MessageAssembler.d.ts +17 -0
  10. package/dist/core/MessageAssembler.js +76 -0
  11. package/dist/core/PendingRequestStore.d.ts +18 -0
  12. package/dist/core/PendingRequestStore.js +33 -0
  13. package/dist/core/Session.d.ts +43 -0
  14. package/dist/core/Session.js +330 -0
  15. package/dist/core/SessionConnector.d.ts +14 -0
  16. package/dist/core/SessionConnector.js +62 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.js +27 -0
  19. package/dist/services/BaseService.d.ts +11 -0
  20. package/dist/services/BaseService.js +17 -0
  21. package/dist/services/ControlService.d.ts +33 -0
  22. package/dist/services/ControlService.js +67 -0
  23. package/dist/services/DeviceService.d.ts +26 -0
  24. package/dist/services/DeviceService.js +51 -0
  25. package/dist/services/GatewayService.d.ts +11 -0
  26. package/dist/services/GatewayService.js +33 -0
  27. package/dist/services/RoomService.d.ts +11 -0
  28. package/dist/services/RoomService.js +27 -0
  29. package/dist/services/SceneService.d.ts +12 -0
  30. package/dist/services/SceneService.js +39 -0
  31. package/dist/services/UserService.d.ts +21 -0
  32. package/dist/services/UserService.js +70 -0
  33. package/dist/transport/TcpClient.d.ts +38 -0
  34. package/dist/transport/TcpClient.js +315 -0
  35. package/dist/types/device-catalog.d.ts +42 -0
  36. package/dist/types/device-catalog.js +16 -0
  37. package/dist/types/device-state.d.ts +56 -0
  38. package/dist/types/device-state.js +2 -0
  39. package/dist/types/index.d.ts +8 -0
  40. package/dist/types/index.js +24 -0
  41. package/dist/types/messages.d.ts +53 -0
  42. package/dist/types/messages.js +32 -0
  43. package/dist/types/options.d.ts +27 -0
  44. package/dist/types/options.js +7 -0
  45. package/dist/types/room.d.ts +9 -0
  46. package/dist/types/room.js +2 -0
  47. package/dist/types/scene.d.ts +22 -0
  48. package/dist/types/scene.js +2 -0
  49. package/dist/types/user.d.ts +17 -0
  50. package/dist/types/user.js +2 -0
  51. package/dist/types.d.ts +212 -0
  52. package/dist/types.js +50 -0
  53. package/dist/utils/IdGenerator.d.ts +19 -0
  54. package/dist/utils/IdGenerator.js +49 -0
  55. package/dist/utils/MessageIdGenerator.d.ts +4 -0
  56. package/dist/utils/MessageIdGenerator.js +12 -0
  57. package/dist/utils/logger.d.ts +33 -0
  58. package/dist/utils/logger.js +115 -0
  59. package/dist/utils/time.d.ts +2 -0
  60. package/dist/utils/time.js +14 -0
  61. package/package.json +45 -0
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BaiweiSession = void 0;
7
+ const events_1 = __importDefault(require("events"));
8
+ const TcpClient_1 = require("../transport/TcpClient");
9
+ const types_1 = require("../types");
10
+ const IdGenerator_1 = require("../utils/IdGenerator");
11
+ const logger_1 = require("../utils/logger");
12
+ class BaiweiSession extends events_1.default {
13
+ tcp;
14
+ options;
15
+ log;
16
+ listenersAttached = false;
17
+ token;
18
+ connecting;
19
+ onTcpMessage = (message) => this.onMessage(message);
20
+ onTcpConnected = () => this.emit("connected");
21
+ onTcpDisconnect = (reason) => this.handleDisconnect(reason);
22
+ onTcpError = (err) => {
23
+ if (this.connecting) {
24
+ return;
25
+ }
26
+ this.emit("error", err);
27
+ };
28
+ onTcpProtocolError = (err) => this.emit("error", err);
29
+ idGenerator = new IdGenerator_1.IdGenerator();
30
+ pending = new Map();
31
+ partialPayloads = new Map();
32
+ constructor(options) {
33
+ super();
34
+ this.log = (0, logger_1.createLogger)(options.logger);
35
+ this.options = options;
36
+ this.tcp = new TcpClient_1.TcpClient({ ...this.options });
37
+ this.bindTcpListeners();
38
+ }
39
+ /**
40
+ * Check if the session is currently connected.
41
+ */
42
+ isConnected() {
43
+ return this.tcp.isConnected();
44
+ }
45
+ /**
46
+ * Establish TCP connection.
47
+ * Returns existing promise if connection is already in progress.
48
+ * If already connected, returns a resolved promise.
49
+ */
50
+ connect() {
51
+ if (this.isConnected()) {
52
+ return Promise.resolve();
53
+ }
54
+ if (this.connecting) {
55
+ return this.connecting;
56
+ }
57
+ this.connecting = new Promise((resolve, reject) => {
58
+ let done = false;
59
+ let timer;
60
+ const cleanup = () => {
61
+ this.tcp.off("connected", onConnected);
62
+ this.tcp.off("error", onError);
63
+ if (timer) {
64
+ clearTimeout(timer);
65
+ timer = undefined;
66
+ }
67
+ this.connecting = undefined;
68
+ };
69
+ const succeed = () => {
70
+ if (done)
71
+ return;
72
+ done = true;
73
+ cleanup();
74
+ resolve();
75
+ };
76
+ const fail = (err) => {
77
+ if (done)
78
+ return;
79
+ done = true;
80
+ const error = err instanceof Error ? err : new Error(String(err ?? "connect failed"));
81
+ this.log.error("Connection failed:", error.message);
82
+ cleanup();
83
+ this.emit("error", error);
84
+ reject(error);
85
+ };
86
+ const onConnected = () => succeed();
87
+ const onError = (err) => fail(err);
88
+ this.tcp.on("connected", onConnected);
89
+ this.tcp.on("error", onError);
90
+ timer = setTimeout(() => {
91
+ try {
92
+ fail(new Error("connect timeout"));
93
+ }
94
+ catch (e) {
95
+ this.log.error("Unexpected error in timeout handler:", e);
96
+ }
97
+ }, 10_000);
98
+ try {
99
+ this.tcp.connect();
100
+ }
101
+ catch (e) {
102
+ fail(e);
103
+ }
104
+ });
105
+ return this.connecting;
106
+ }
107
+ updateToken(token) {
108
+ this.token = token;
109
+ }
110
+ disconnect() {
111
+ if (!this.isConnected() && !this.connecting) {
112
+ this.clearRuntimeState(new Error("Session disconnected"));
113
+ return;
114
+ }
115
+ this.connecting = undefined;
116
+ this.tcp.disconnect();
117
+ }
118
+ request(req) {
119
+ const msgId = this.idGenerator.next();
120
+ const base = {
121
+ api_version: "0.1",
122
+ appId: "010",
123
+ from: this.options.clientId,
124
+ to: this.options.gatewaySN,
125
+ msg_id: msgId,
126
+ msg_class: req.msgClass,
127
+ msg_name: req.msgName,
128
+ msg_type: req.msgType || types_1.MsgType.GET,
129
+ token: this.token,
130
+ };
131
+ const message = { ...base, ...req.payload };
132
+ return new Promise(async (resolve, reject) => {
133
+ const timeoutTimer = setTimeout(() => {
134
+ this.pending.delete(msgId);
135
+ const err = new Error(`Request timeout: msgClass/msgName=${req.msgClass}/${req.msgName}, msgId=${msgId}`);
136
+ this.log.info("TCP request timeout:", err);
137
+ reject(err);
138
+ }, this.options.timeoutMs);
139
+ this.pending.set(msgId, {
140
+ resolve: (v) => resolve(v),
141
+ reject,
142
+ timeoutTimer,
143
+ });
144
+ try {
145
+ await this.tcp.send(message);
146
+ }
147
+ catch (err) {
148
+ clearTimeout(timeoutTimer);
149
+ this.pending.delete(msgId);
150
+ this.log.info("Send failed:", err);
151
+ reject(err);
152
+ }
153
+ });
154
+ }
155
+ handleDisconnect(reason) {
156
+ this.connecting = undefined;
157
+ this.clearRuntimeState(reason);
158
+ this.emit("disconnect", reason);
159
+ }
160
+ bindTcpListeners() {
161
+ if (this.listenersAttached) {
162
+ return;
163
+ }
164
+ this.tcp.on("connected", this.onTcpConnected);
165
+ this.tcp.on("message", this.onTcpMessage);
166
+ this.tcp.on("disconnected", this.onTcpDisconnect);
167
+ this.tcp.on("error", this.onTcpError);
168
+ this.tcp.on("protocol_error", this.onTcpProtocolError);
169
+ this.listenersAttached = true;
170
+ }
171
+ clearRuntimeState(reason) {
172
+ if (this.pending.size > 0) {
173
+ for (const [msgId, pending] of this.pending.entries()) {
174
+ clearTimeout(pending.timeoutTimer);
175
+ pending.reject(new Error(`TCP disconnected. msgId=${msgId}. reason=${String(reason ?? "")}`));
176
+ }
177
+ this.pending.clear();
178
+ }
179
+ if (this.partialPayloads.size > 0) {
180
+ for (const [, entry] of this.partialPayloads.entries()) {
181
+ clearTimeout(entry.timer);
182
+ }
183
+ this.partialPayloads.clear();
184
+ }
185
+ }
186
+ onMessage(message) {
187
+ const { api_version, from, to, msg_id, msg_class, msg_name, msg_type, end = 1, status = 0, ...payload } = message;
188
+ this.log.info(`API_VERSION: ${api_version}, FROM: ${from}, TO: ${to}, MSG_TYPE: ${msg_type}, MSG_ID: ${msg_id}, MSG_CLASS: ${msg_class}, MSG_NAME: ${msg_name}, END: ${end}`);
189
+ // Validate message
190
+ if (!this.validateMessage(message, from, msg_id)) {
191
+ return;
192
+ }
193
+ // Handle gateway errors
194
+ if (status !== 0) {
195
+ this.handleGatewayError(msg_id, status, msg_class, msg_name);
196
+ return;
197
+ }
198
+ // Handle message fragmentation
199
+ const finalPayload = this.handleFragmentation(msg_id, payload, end, msg_class, msg_name);
200
+ if (!finalPayload) {
201
+ return; // Waiting for more fragments
202
+ }
203
+ // Process complete message
204
+ const res = {
205
+ msgId: msg_id,
206
+ msgClass: msg_class,
207
+ msgName: msg_name,
208
+ msgType: msg_type,
209
+ payload: finalPayload,
210
+ };
211
+ this.log.info("FULL MESSAGE =====>", JSON.stringify(res));
212
+ this.handleMessageResponse(res);
213
+ }
214
+ validateMessage(message, from, msgId) {
215
+ if (from !== this.options.gatewaySN) {
216
+ const err = new Error(`Invalid gatewaySN, got ${from}, expected: ${this.options.gatewaySN}`);
217
+ this.emit("error", err);
218
+ return false;
219
+ }
220
+ if (!msgId) {
221
+ const err = new Error(`Invalid msgId, got ${msgId}`);
222
+ this.emit("error", err);
223
+ return false;
224
+ }
225
+ return true;
226
+ }
227
+ handleGatewayError(msgId, status, msgClass, msgName) {
228
+ const pending = this.pending.get(msgId);
229
+ if (pending) {
230
+ clearTimeout(pending.timeoutTimer);
231
+ this.pending.delete(msgId);
232
+ pending.reject(new Error(`Gateway status=${status}, msgClass/msgName=${msgClass}/${msgName}, msgId=${msgId}`));
233
+ }
234
+ else {
235
+ this.emit("error", new Error(`Gateway status=${status}, unmatched msgId=${msgId}, msgClass/msgName=${msgClass}/${msgName}`));
236
+ }
237
+ }
238
+ handleFragmentation(msgId, payload, end, msgClass, msgName) {
239
+ if (end === 1) {
240
+ // Last fragment
241
+ const prev = this.partialPayloads.get(msgId);
242
+ if (prev) {
243
+ clearTimeout(prev.timer);
244
+ const finalPayload = this.mergePayload(prev.payload, payload);
245
+ this.partialPayloads.delete(msgId);
246
+ return finalPayload;
247
+ }
248
+ else {
249
+ // No previous fragments, this is a complete message
250
+ return payload;
251
+ }
252
+ }
253
+ else {
254
+ // Intermediate or first fragment
255
+ const prev = this.partialPayloads.get(msgId);
256
+ if (prev) {
257
+ prev.payload = this.mergePayload(prev.payload, payload);
258
+ }
259
+ else {
260
+ const timer = setTimeout(() => {
261
+ // Expired without receiving end=1: cleanup and error
262
+ const entry = this.partialPayloads.get(msgId);
263
+ if (entry)
264
+ this.partialPayloads.delete(msgId);
265
+ this.emit("error", new Error(`Partial payload expired: msgId=${msgId}, msgClass/msgName=${msgClass}/${msgName}`));
266
+ }, this.options.timeoutMs);
267
+ this.partialPayloads.set(msgId, { payload, timer });
268
+ }
269
+ // Return null to indicate waiting for more fragments
270
+ return null;
271
+ }
272
+ }
273
+ mergePayload(prev, curr) {
274
+ const result = { ...prev };
275
+ for (const key of Object.keys(curr)) {
276
+ const prevVal = result[key];
277
+ const currVal = curr[key];
278
+ if (prevVal &&
279
+ currVal &&
280
+ typeof prevVal === "object" &&
281
+ typeof currVal === "object" &&
282
+ !Array.isArray(prevVal) &&
283
+ !Array.isArray(currVal)) {
284
+ // Recursive merge for nested objects
285
+ result[key] = this.mergePayload(prevVal, currVal);
286
+ }
287
+ else if (Array.isArray(prevVal) && Array.isArray(currVal)) {
288
+ // Concatenate arrays (useful for { type_list: [] } scenarios)
289
+ result[key] = [...prevVal, ...currVal];
290
+ }
291
+ else {
292
+ // Otherwise, directly overwrite
293
+ result[key] = currVal;
294
+ }
295
+ }
296
+ return result;
297
+ }
298
+ handleMessageResponse(res) {
299
+ switch (res.msgType) {
300
+ case types_1.MsgType.RESPONSE:
301
+ this.handleResponse(res);
302
+ break;
303
+ case types_1.MsgType.REPORT:
304
+ this.handleReport(res);
305
+ break;
306
+ default:
307
+ this.log.warn(`Unknown msg_type: ${res.msgType} for msgId: ${res.msgId}`);
308
+ return;
309
+ }
310
+ }
311
+ handleResponse(res) {
312
+ const { msgId: msg_id, payload } = res;
313
+ const pending = this.pending.get(msg_id);
314
+ if (!pending)
315
+ return;
316
+ // Remove from pending after finding it
317
+ this.pending.delete(msg_id);
318
+ // Clear timeout timer
319
+ clearTimeout(pending.timeoutTimer);
320
+ // Log response
321
+ this.log.info("RESPONSE =====>", JSON.stringify(payload));
322
+ // Resolve promise
323
+ pending.resolve(payload);
324
+ }
325
+ handleReport(res) {
326
+ this.log.info("REPORT =====>", JSON.stringify(res.payload));
327
+ this.emit("report", res);
328
+ }
329
+ }
330
+ exports.BaiweiSession = BaiweiSession;
@@ -0,0 +1,14 @@
1
+ import { TcpClient } from "../transport/TcpClient";
2
+ import { Logger } from "../utils/logger";
3
+ /**
4
+ * 一次性连接握手器。
5
+ * 只负责本次 connect 的监听、超时和错误归一化。
6
+ */
7
+ export declare class SessionConnector {
8
+ private readonly tcp;
9
+ private readonly log;
10
+ private readonly onError;
11
+ constructor(tcp: TcpClient, log: Required<Logger>, onError: (error: Error) => void);
12
+ /** 连接成功 resolve,连接错误或超时 reject。 */
13
+ connect(): Promise<void>;
14
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionConnector = void 0;
4
+ const CONNECT_TIMEOUT_MS = 10_000;
5
+ /**
6
+ * 一次性连接握手器。
7
+ * 只负责本次 connect 的监听、超时和错误归一化。
8
+ */
9
+ class SessionConnector {
10
+ tcp;
11
+ log;
12
+ onError;
13
+ constructor(tcp, log, onError) {
14
+ this.tcp = tcp;
15
+ this.log = log;
16
+ this.onError = onError;
17
+ }
18
+ /** 连接成功 resolve,连接错误或超时 reject。 */
19
+ connect() {
20
+ return new Promise((resolve, reject) => {
21
+ let settled = false;
22
+ let timer;
23
+ const cleanup = () => {
24
+ this.tcp.off("connected", handleConnected);
25
+ this.tcp.off("error", handleError);
26
+ if (timer) {
27
+ clearTimeout(timer);
28
+ timer = undefined;
29
+ }
30
+ };
31
+ const finish = (error) => {
32
+ if (settled) {
33
+ return;
34
+ }
35
+ settled = true;
36
+ cleanup();
37
+ if (!error) {
38
+ resolve();
39
+ return;
40
+ }
41
+ const normalized = error instanceof Error ? error : new Error(String(error ?? "connect failed"));
42
+ this.log.error("Connection failed:", normalized.message);
43
+ this.onError(normalized);
44
+ reject(normalized);
45
+ };
46
+ const handleConnected = () => finish();
47
+ const handleError = (error) => finish(error);
48
+ this.tcp.on("connected", handleConnected);
49
+ this.tcp.on("error", handleError);
50
+ timer = setTimeout(() => {
51
+ finish(new Error("connect timeout"));
52
+ }, CONNECT_TIMEOUT_MS);
53
+ try {
54
+ this.tcp.connect();
55
+ }
56
+ catch (error) {
57
+ finish(error);
58
+ }
59
+ });
60
+ }
61
+ }
62
+ exports.SessionConnector = SessionConnector;
@@ -0,0 +1,11 @@
1
+ /** SDK 对外公共导出入口。 */
2
+ export * from "./core/BaiweiClient";
3
+ export * from "./core/BaiweiSession";
4
+ export * from "./services/BaseService";
5
+ export * from "./services/ControlService";
6
+ export * from "./services/DeviceService";
7
+ export * from "./services/GatewayService";
8
+ export * from "./services/RoomService";
9
+ export * from "./services/SceneService";
10
+ export * from "./services/UserService";
11
+ export * from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /** SDK 对外公共导出入口。 */
18
+ __exportStar(require("./core/BaiweiClient"), exports);
19
+ __exportStar(require("./core/BaiweiSession"), exports);
20
+ __exportStar(require("./services/BaseService"), exports);
21
+ __exportStar(require("./services/ControlService"), exports);
22
+ __exportStar(require("./services/DeviceService"), exports);
23
+ __exportStar(require("./services/GatewayService"), exports);
24
+ __exportStar(require("./services/RoomService"), exports);
25
+ __exportStar(require("./services/SceneService"), exports);
26
+ __exportStar(require("./services/UserService"), exports);
27
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,11 @@
1
+ import { BaiweiSession } from "../core/BaiweiSession";
2
+ import { MessageRequest } from "../types";
3
+ /**
4
+ * 所有 service 的公共基类。
5
+ * 统一把协议请求委托给 session。
6
+ */
7
+ export declare abstract class BaseService {
8
+ protected readonly session: BaiweiSession;
9
+ constructor(session: BaiweiSession);
10
+ protected request<TResponse, TPayload extends Record<string, unknown> = Record<string, unknown>>(message: MessageRequest<TPayload>): Promise<TResponse>;
11
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseService = void 0;
4
+ /**
5
+ * 所有 service 的公共基类。
6
+ * 统一把协议请求委托给 session。
7
+ */
8
+ class BaseService {
9
+ session;
10
+ constructor(session) {
11
+ this.session = session;
12
+ }
13
+ request(message) {
14
+ return this.session.request(message);
15
+ }
16
+ }
17
+ exports.BaseService = BaseService;
@@ -0,0 +1,33 @@
1
+ import { BaiweiSession } from "../core/BaiweiSession";
2
+ import { BaiweiDeviceState, BaiweiDeviceStateReport, BaiweiProductType, DeviceStateList, MessageResponse } from "../types";
3
+ import { BaseService } from "./BaseService";
4
+ /**
5
+ * 薄控制层:只负责协议映射,不在 SDK 内维护设备状态缓存。
6
+ */
7
+ export declare class ControlService extends BaseService {
8
+ constructor(session: BaiweiSession);
9
+ /**
10
+ * 兼容旧命名。
11
+ * 当前实现不会在 SDK 内同步或缓存状态,只是发起一次查询。
12
+ */
13
+ syncDeviceStatesByType<T>(type: BaiweiProductType): Promise<DeviceStateList<T>>;
14
+ /** 查询某个产品类型下的全部设备状态。 */
15
+ getDeviceStatesByType<T>(type: BaiweiProductType): Promise<DeviceStateList<T>>;
16
+ /**
17
+ * 发送控制命令。
18
+ */
19
+ controlDevice<T>(deviceState: BaiweiDeviceState<T>): Promise<unknown>;
20
+ /**
21
+ * 在无缓存模式下,查询单个设备状态时需要显式提供设备类型。
22
+ */
23
+ getDeviceState<T>(deviceId: string, type?: BaiweiProductType): Promise<BaiweiDeviceState<T> | undefined>;
24
+ /**
25
+ * `report` 会由 client/session 原样抛出,业务层可用这个 helper 来识别设备状态上报。
26
+ */
27
+ /** 判断一条 report 是否为设备状态上报。 */
28
+ isDeviceStateReport(message: MessageResponse): message is MessageResponse<BaiweiDeviceStateReport>;
29
+ /**
30
+ * 从 report 中提取设备状态;如果不是设备状态上报,则返回 `undefined`。
31
+ */
32
+ getReportedDeviceState(message: MessageResponse): BaiweiDeviceState | undefined;
33
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ControlService = void 0;
4
+ const types_1 = require("../types");
5
+ const BaseService_1 = require("./BaseService");
6
+ /**
7
+ * 薄控制层:只负责协议映射,不在 SDK 内维护设备状态缓存。
8
+ */
9
+ class ControlService extends BaseService_1.BaseService {
10
+ constructor(session) {
11
+ super(session);
12
+ }
13
+ /**
14
+ * 兼容旧命名。
15
+ * 当前实现不会在 SDK 内同步或缓存状态,只是发起一次查询。
16
+ */
17
+ async syncDeviceStatesByType(type) {
18
+ return this.getDeviceStatesByType(type);
19
+ }
20
+ /** 查询某个产品类型下的全部设备状态。 */
21
+ async getDeviceStatesByType(type) {
22
+ return this.request({
23
+ msgClass: types_1.MsgClass.CONTROL_MGMT,
24
+ msgName: types_1.MsgName.DEVICE_STATE_GET,
25
+ payload: { device: { type } },
26
+ });
27
+ }
28
+ /**
29
+ * 发送控制命令。
30
+ */
31
+ async controlDevice(deviceState) {
32
+ return this.request({
33
+ msgClass: types_1.MsgClass.CONTROL_MGMT,
34
+ msgName: types_1.MsgName.DEVICE_CONTROL,
35
+ msgType: types_1.MsgType.SET,
36
+ payload: { device: deviceState },
37
+ });
38
+ }
39
+ /**
40
+ * 在无缓存模式下,查询单个设备状态时需要显式提供设备类型。
41
+ */
42
+ async getDeviceState(deviceId, type) {
43
+ if (!type) {
44
+ throw new Error("getDeviceState requires a product type in stateless mode.");
45
+ }
46
+ const result = await this.getDeviceStatesByType(type);
47
+ return result.device_list?.find((device) => device.device_id === deviceId);
48
+ }
49
+ /**
50
+ * `report` 会由 client/session 原样抛出,业务层可用这个 helper 来识别设备状态上报。
51
+ */
52
+ /** 判断一条 report 是否为设备状态上报。 */
53
+ isDeviceStateReport(message) {
54
+ return (message.msgClass === types_1.MsgClass.CONTROL_MGMT &&
55
+ message.msgName === types_1.MsgName.DEVICE_STATE_REPORT);
56
+ }
57
+ /**
58
+ * 从 report 中提取设备状态;如果不是设备状态上报,则返回 `undefined`。
59
+ */
60
+ getReportedDeviceState(message) {
61
+ if (!this.isDeviceStateReport(message)) {
62
+ return undefined;
63
+ }
64
+ return message.payload.device;
65
+ }
66
+ }
67
+ exports.ControlService = ControlService;
@@ -0,0 +1,26 @@
1
+ import { BaiweiSession } from "../core/BaiweiSession";
2
+ import { BaiweiDevice, BaiweiProductType, DeviceQuery } from "../types";
3
+ import { BaseService } from "./BaseService";
4
+ /**
5
+ * 设备目录查询层。
6
+ * 当前设计不缓存设备列表,每次调用都直接向网关查询。
7
+ */
8
+ export declare class DeviceService extends BaseService {
9
+ constructor(session: BaiweiSession);
10
+ queryDevices(): Promise<DeviceQuery>;
11
+ /**
12
+ * 兼容旧命名。
13
+ * 当前实现不会在 SDK 内缓存设备目录。
14
+ */
15
+ syncDevices(): Promise<DeviceQuery>;
16
+ /** 按产品类型过滤设备。 */
17
+ getDevicesByType(type: BaiweiProductType): Promise<BaiweiDevice[]>;
18
+ /**
19
+ * 查询并拍平全部设备。
20
+ */
21
+ listDevices(): Promise<BaiweiDevice[]>;
22
+ /**
23
+ * 查询单个设备,不在 SDK 内保留目录缓存。
24
+ */
25
+ getDeviceById(deviceId: string): Promise<BaiweiDevice | undefined>;
26
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeviceService = void 0;
4
+ const types_1 = require("../types");
5
+ const BaseService_1 = require("./BaseService");
6
+ /**
7
+ * 设备目录查询层。
8
+ * 当前设计不缓存设备列表,每次调用都直接向网关查询。
9
+ */
10
+ class DeviceService extends BaseService_1.BaseService {
11
+ constructor(session) {
12
+ super(session);
13
+ }
14
+ async queryDevices() {
15
+ return this.request({
16
+ msgClass: types_1.MsgClass.DEVICE_MGMT,
17
+ msgName: types_1.MsgName.DEVICE_QUERY,
18
+ });
19
+ }
20
+ /**
21
+ * 兼容旧命名。
22
+ * 当前实现不会在 SDK 内缓存设备目录。
23
+ */
24
+ async syncDevices() {
25
+ return this.queryDevices();
26
+ }
27
+ /** 按产品类型过滤设备。 */
28
+ async getDevicesByType(type) {
29
+ const response = await this.queryDevices();
30
+ const matched = response.type_list?.find((item) => item.product_type === type);
31
+ return matched?.device_list ?? [];
32
+ }
33
+ /**
34
+ * 查询并拍平全部设备。
35
+ */
36
+ async listDevices() {
37
+ const response = await this.queryDevices();
38
+ return flattenDevices(response.type_list ?? []);
39
+ }
40
+ /**
41
+ * 查询单个设备,不在 SDK 内保留目录缓存。
42
+ */
43
+ async getDeviceById(deviceId) {
44
+ const devices = await this.listDevices();
45
+ return devices.find((device) => device.device_id === deviceId);
46
+ }
47
+ }
48
+ exports.DeviceService = DeviceService;
49
+ function flattenDevices(typeList) {
50
+ return typeList.flatMap((item) => item.device_list ?? []);
51
+ }
@@ -0,0 +1,11 @@
1
+ import { BaiweiSession } from "../core/BaiweiSession";
2
+ import { BaseService } from "./BaseService";
3
+ /** 网关级操作封装,例如允许设备入网。 */
4
+ export declare class GatewayService extends BaseService {
5
+ constructor(session: BaiweiSession);
6
+ discovery(): void;
7
+ /** 打开入网窗口,允许新 Zigbee 设备在指定时间内加入。 */
8
+ permitZigbeeJoin(time?: number): Promise<unknown>;
9
+ /** 提前关闭入网窗口。 */
10
+ stopZigbeeJoin(): Promise<unknown>;
11
+ }