@ray-js/robot-data-stream 0.0.17-beta.10 → 0.0.17-beta.12

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 (45) hide show
  1. package/README.md +23 -1
  2. package/lib/api/p2pApi.d.ts +15 -5
  3. package/lib/api/sweeperP2p.d.ts +1 -1
  4. package/lib/index.d.ts +9 -8
  5. package/lib/index.js +2 -0
  6. package/lib/mqtt/createCommonOptions.d.ts +2 -20
  7. package/lib/mqtt/createCommonOptions.js +17 -5
  8. package/lib/mqtt/defineRobotCommand.d.ts +52 -0
  9. package/lib/mqtt/defineRobotCommand.js +71 -0
  10. package/lib/mqtt/hooks/useStructuredMessage.d.ts +5 -1
  11. package/lib/mqtt/hooks/useStructuredMessage.js +22 -10
  12. package/lib/mqtt/index.d.ts +6 -1
  13. package/lib/mqtt/index.js +10 -1
  14. package/lib/mqtt/promise.d.ts +21 -6
  15. package/lib/mqtt/promise.js +47 -12
  16. package/lib/mqtt/type/protocols/carpetProtocol.d.ts +2 -0
  17. package/lib/mqtt/type/protocols/index.d.ts +1 -0
  18. package/lib/mqtt/type/protocols/index.js +1 -0
  19. package/lib/mqtt/useCarpet.js +12 -4
  20. package/lib/mqtt/useCarpetClean.js +6 -2
  21. package/lib/mqtt/useCruise.js +3 -1
  22. package/lib/mqtt/useCustomCommand.d.ts +39 -0
  23. package/lib/mqtt/useCustomCommand.js +45 -0
  24. package/lib/mqtt/useDeviceModel.js +3 -1
  25. package/lib/mqtt/useFurnitureModel.js +3 -1
  26. package/lib/mqtt/useHistoryMap.js +9 -3
  27. package/lib/mqtt/usePartDivision.js +3 -1
  28. package/lib/mqtt/usePartMerge.js +3 -1
  29. package/lib/mqtt/usePassword.js +6 -2
  30. package/lib/mqtt/usePurifierSchedule.js +3 -1
  31. package/lib/mqtt/useQuiteHours.js +3 -1
  32. package/lib/mqtt/useResetMap.js +3 -1
  33. package/lib/mqtt/useRoomProperty.js +3 -1
  34. package/lib/mqtt/useSchedule.js +3 -1
  35. package/lib/mqtt/useSelectRoomClean.js +3 -1
  36. package/lib/mqtt/useSpotClean.js +3 -1
  37. package/lib/mqtt/useSpotPolygons.js +3 -1
  38. package/lib/mqtt/useThreshold.js +3 -1
  39. package/lib/mqtt/useVirtualArea.js +3 -1
  40. package/lib/mqtt/useVirtualWall.js +3 -1
  41. package/lib/mqtt/useVoice.js +3 -1
  42. package/lib/mqtt/useWifiMap.js +3 -1
  43. package/lib/mqtt/useZoneClean.js +3 -1
  44. package/lib/utils/index.d.ts +7 -7
  45. package/package.json +5 -1
package/README.md CHANGED
@@ -76,4 +76,26 @@ export default function Home() {
76
76
  );
77
77
  }
78
78
 
79
- ```
79
+ ```
80
+
81
+ ## Custom Commands (extension point for customized firmware)
82
+
83
+ Commands that only exist on a customized firmware fork do NOT belong in this library's built-in hooks. Declare, send and listen to them in your own repo:
84
+
85
+ ```typescript
86
+ import { useCustomCommand, defineRobotCommand } from '@ray-js/robot-data-stream';
87
+
88
+ // One-off send (any reqType; must be used within MqttProvider)
89
+ const { sendCustomCommand } = useCustomCommand();
90
+ await sendCustomCommand<Res>('peakValleyChargeQry');
91
+
92
+ // Declarative: one file per command, returns { useSend, useResponse } shaped like built-in hooks
93
+ export const peakValleyCharge = defineRobotCommand<Req, Res>({
94
+ reqType: 'peakValleyChargeSet',
95
+ responseType: 'peakValleyChargeQry',
96
+ });
97
+ ```
98
+
99
+ Key semantics: MQTT + LAN JSON socket channels only (no DP `command_trans`); reqType + taskId response matching (when firmware echoes taskId, concurrent same-reqType requests do not cross-wire; responses without taskId pass through, degrading to first-response matching in that case); `data`-envelope unwrapping and `success`/`errCode` normalization are handled by the library; built-in protocol payload/response types are exported from the package root for type reuse. No runtime validation, no loading/retry — same contract as built-in hooks.
100
+
101
+ Full reference: `docs/reference/custom-commands.md`.
@@ -58,7 +58,9 @@ export default class P2pApi {
58
58
  * @param callback
59
59
  * @returns
60
60
  */
61
- onP2pStreamPacketReceive: (callback: (...args: any[]) => void) => () => void;
61
+ onP2pStreamPacketReceive: (callback: {
62
+ (...args: any[]): void;
63
+ }) => () => void;
62
64
  /**
63
65
  * 开始下载文件
64
66
  * files : {"files":["filesname1", "filesname2", "filesname3" ]}
@@ -71,24 +73,32 @@ export default class P2pApi {
71
73
  * @param callback
72
74
  * @returns
73
75
  */
74
- onDownloadProgressUpdate: (callback: (...args: any[]) => void) => () => void;
76
+ onDownloadProgressUpdate: (callback: {
77
+ (...args: any[]): void;
78
+ }) => () => void;
75
79
  /**
76
80
  * 注册多文件下载进度监听
77
81
  * @param callback
78
82
  * @returns
79
83
  */
80
- onDownloadTotalProgressUpdate: (callback: (...args: any[]) => void) => () => void;
84
+ onDownloadTotalProgressUpdate: (callback: {
85
+ (...args: any[]): void;
86
+ }) => () => void;
81
87
  /**
82
88
  * 注册单文件下载进度完成监听
83
89
  * @param callback
84
90
  * @returns
85
91
  */
86
- onFileDownloadComplete: (callback: (...args: any[]) => void) => () => void;
92
+ onFileDownloadComplete: (callback: {
93
+ (...args: any[]): void;
94
+ }) => () => void;
87
95
  /**
88
96
  * 注册设备因为其他异常断开连接的事件
89
97
  * @returns
90
98
  */
91
- onSessionStatusChange: (callback: (...args: any[]) => void) => () => void;
99
+ onSessionStatusChange: (callback: {
100
+ (...args: any[]): void;
101
+ }) => () => void;
92
102
  /**
93
103
  * 取消进行下载
94
104
  * @returns
@@ -133,7 +133,7 @@ export declare class SweeperP2p extends P2pApi {
133
133
  * @param downloadType
134
134
  * 0: 下载断开 1: 持续下载
135
135
  */
136
- startObserverSweeperDataByP2P: (downloadType: number, devId: string, onReceiveMapData: (data: string) => void, onReceivePathData: (data: string) => void, onReceiveAIPicData?: ((data: string) => void) | undefined, onReceiveAIPicHDData?: ((data: string) => void) | undefined, onReceiveWifiMapData?: ((data: string) => void) | undefined, onDefineStructuredMode?: ((isStructured: boolean) => void) | undefined) => Promise<void>;
136
+ startObserverSweeperDataByP2P: (downloadType: number, devId: string, onReceiveMapData: (data: string) => void, onReceivePathData: (data: string) => void, onReceiveAIPicData?: (data: string) => void, onReceiveAIPicHDData?: (data: string) => void, onReceiveWifiMapData?: (data: string) => void, onDefineStructuredMode?: (isStructured: boolean) => void) => Promise<void>;
137
137
  /**
138
138
  * 在下载过程中继续添加下载文件
139
139
  * @param files
package/lib/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  export * from './mqtt';
2
2
  export * from './mqtt/type/requestType';
3
+ export { pointsToString, pickWaterFields } from './utils';
3
4
  type TOnLogger = (type: 'warn' | 'error' | 'info', data: string) => void;
4
5
  declare const useP2PDataStream: (devId: string, onReceiveMapData: (data: string) => void, onReceivePathData: (data: string) => void, opt?: {
5
- logTag?: string | undefined;
6
- enableCustomLog?: boolean | undefined;
7
- onReceiveAIPicData?: ((data: string) => void) | undefined;
8
- onReceiveAIPicHDData?: ((data: string) => void) | undefined;
9
- onReceiveWifiMapData?: ((data: string) => void) | undefined;
10
- onLogger?: TOnLogger | undefined;
11
- onDefineStructuredMode?: ((isStructured: boolean) => void) | undefined;
12
- } | undefined) => {
6
+ logTag?: string;
7
+ enableCustomLog?: boolean;
8
+ onReceiveAIPicData?: (data: string) => void;
9
+ onReceiveAIPicHDData?: (data: string) => void;
10
+ onReceiveWifiMapData?: (data: string) => void;
11
+ onLogger?: TOnLogger;
12
+ onDefineStructuredMode?: (isStructured: boolean) => void;
13
+ }) => {
13
14
  appendDownloadStreamDuringTask: (files: Array<string>, successCb?: () => void, failedCb?: () => void) => Promise<boolean> | void;
14
15
  };
15
16
  declare const StreamDataNotificationCenter: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
package/lib/index.js CHANGED
@@ -7,6 +7,8 @@ import { trace } from './trace';
7
7
  import { emitter, logger, logP2PDataIfEnabled } from './utils';
8
8
  export * from './mqtt';
9
9
  export * from './mqtt/type/requestType';
10
+ // 手拼标准指令 payload(自定义指令的字段级扩展场景)需要复用的纯工具
11
+ export { pointsToString, pickWaterFields } from './utils';
10
12
  const useP2PDataStream = (devId, onReceiveMapData, onReceivePathData, opt) => {
11
13
  const traceId = `${devId}_${Date.now()}`;
12
14
  global.robotPanelTraceId = traceId;
@@ -43,25 +43,7 @@ export interface CreateSetCommonParamsOutput {
43
43
  options: MqttOptions;
44
44
  /** 协议号 */
45
45
  protocol: ProtocolEnum;
46
+ /** 本次下发生成的 taskId(已拼进 message;LAN 场景 message 为 JSON 字符串,从这里取) */
47
+ taskId: string;
46
48
  }
47
- /**
48
- * 创建 MQTT 通用参数
49
- *
50
- * @param params - 参数对象
51
- * @param params.deviceId - 设备ID
52
- * @param params.reqType - 请求类型
53
- * @param params.message - 消息体(可选)
54
- * @param params.version - 版本号(可选,默认为 1.0.0)
55
- * @param params.options - 选项(可选)
56
- * @returns MQTT 通用参数对象
57
- *
58
- * @example
59
- * ```ts
60
- * const params = createSetCommonParams({
61
- * deviceId: 'device123',
62
- * reqType: 'devInfoQry',
63
- * message: { customField: 'value' }
64
- * });
65
- * ```
66
- */
67
49
  export declare const createSetCommonParams: ({ deviceId, reqType, isLocalOnLine, message, version, options, }: CreateSetCommonParamsInput) => CreateSetCommonParamsOutput;
@@ -42,6 +42,13 @@ const DEFAULT_VERSION = '1.0.0';
42
42
  * });
43
43
  * ```
44
44
  */
45
+ /**
46
+ * 同毫秒内的自增序号:保证并发下发(如双击连发)也能拿到互不相同的 taskId,
47
+ * 否则同一毫秒内的两条请求 taskId 相同,响应匹配无法区分。
48
+ * 固件侧 taskId 按不透明字符串暂存并原样带回(__mqtt_task_id_pack),追加后缀不影响协议。
49
+ */
50
+ let taskSequence = 0;
51
+ const createTaskId = () => `${Date.now()}_${++taskSequence}`;
45
52
  export const createSetCommonParams = _ref => {
46
53
  let {
47
54
  deviceId,
@@ -51,12 +58,16 @@ export const createSetCommonParams = _ref => {
51
58
  version = DEFAULT_VERSION,
52
59
  options = {}
53
60
  } = _ref;
54
- const taskId = Date.now();
55
- const messageData = _objectSpread({
61
+ const taskId = createTaskId();
62
+
63
+ // taskId 由本库统一生成并权威覆盖:message 先展开,避免调用方(或照协议文档抄示例的
64
+ // 定制开发者)在 message 里夹带 taskId 覆盖线上值——那会让回执与固件回显永远对不上,
65
+ // 双匹配直接退化成必然超时。回执 taskId 始终与线上 message.taskId 一致。
66
+ const messageData = _objectSpread(_objectSpread({}, message), {}, {
56
67
  reqType,
57
68
  version,
58
- taskId: `${taskId}`
59
- }, message);
69
+ taskId
70
+ });
60
71
  return {
61
72
  deviceId,
62
73
  message: !isLocalOnLine ? messageData : JSON.stringify({
@@ -64,6 +75,7 @@ export const createSetCommonParams = _ref => {
64
75
  reqType
65
76
  }),
66
77
  options,
67
- protocol: ProtocolEnum.appToRobot
78
+ protocol: ProtocolEnum.appToRobot,
79
+ taskId
68
80
  };
69
81
  };
@@ -0,0 +1,52 @@
1
+ import { BaseResponse } from './type/protocols/base';
2
+ /**
3
+ * 指令声明配置
4
+ */
5
+ export interface RobotCommandConfig {
6
+ /** 下发 reqType */
7
+ reqType: string;
8
+ /**
9
+ * 响应匹配的事件名(reqType)。
10
+ * 缺省与下发同名;set 类指令回复走 qry 事件名、
11
+ * 或回复 reqType 与下发不同名(如 aiObjectIgnoreSet → aiObjectIgnoreRst)时必填。
12
+ */
13
+ responseType?: string;
14
+ /** 协议版本号,默认 '1.0.0' */
15
+ version?: string;
16
+ /** 超时时间(毫秒),默认 10000 */
17
+ timeout?: number;
18
+ }
19
+ /**
20
+ * defineRobotCommand 的返回:两个独立 hook,在组件内分别调用
21
+ */
22
+ export interface DefinedRobotCommand<TReq = Record<string, any>, TRes extends BaseResponse = BaseResponse> {
23
+ /**
24
+ * 发送 hook,与内置 hook 同构(Promise 命令函数、无 loading 态)。
25
+ * 固件侧指令标准化入库后,从 useSend 换成对应内置 useXxx ≈ 改一行 import。
26
+ */
27
+ useSend: () => {
28
+ send: (data: TReq) => Promise<TRes>;
29
+ };
30
+ /**
31
+ * 类型化监听 hook:订阅 responseType(缺省 reqType)的上报,
32
+ * 是 StreamDataNotificationCenter.on 的类型化薄包装。
33
+ * handler 随渲染更新,订阅本身只在挂载期间维持一份。
34
+ */
35
+ useResponse: (handler: (data: TRes) => void) => void;
36
+ }
37
+ /**
38
+ * 声明一条自定义指令。
39
+ *
40
+ * 面向定制面板:协议描述(reqType / 响应名 / 类型)归消费方,库只负责执行。
41
+ * 在模块顶层调用一次,组件内使用返回的 useSend / useResponse。
42
+ * 完整示例见 docs/reference/custom-commands.md。
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * export const peakValleyCharge = defineRobotCommand<SetPeakValleyData, PeakValleyResponse>({
47
+ * reqType: 'peakValleyChargeSet',
48
+ * responseType: 'peakValleyChargeQry',
49
+ * });
50
+ * ```
51
+ */
52
+ export declare const defineRobotCommand: <TReq extends Record<string, any> = Record<string, any>, TRes extends BaseResponse = BaseResponse>(config: RobotCommandConfig) => DefinedRobotCommand<TReq, TRes>;
@@ -0,0 +1,71 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ // 自定义指令声明工厂:把「一条指令 = reqType + 类型」固化成可复用的 hook 集
3
+ import { useEffect, useRef } from 'react';
4
+ import { emitter } from '../utils';
5
+ import { normalizeResultFields, unwrapMqttResponse } from './promise';
6
+ import { useCustomCommand } from './useCustomCommand';
7
+
8
+ /**
9
+ * 指令声明配置
10
+ */
11
+
12
+ /**
13
+ * defineRobotCommand 的返回:两个独立 hook,在组件内分别调用
14
+ */
15
+
16
+ /**
17
+ * 声明一条自定义指令。
18
+ *
19
+ * 面向定制面板:协议描述(reqType / 响应名 / 类型)归消费方,库只负责执行。
20
+ * 在模块顶层调用一次,组件内使用返回的 useSend / useResponse。
21
+ * 完整示例见 docs/reference/custom-commands.md。
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * export const peakValleyCharge = defineRobotCommand<SetPeakValleyData, PeakValleyResponse>({
26
+ * reqType: 'peakValleyChargeSet',
27
+ * responseType: 'peakValleyChargeQry',
28
+ * });
29
+ * ```
30
+ */
31
+ export const defineRobotCommand = config => {
32
+ const {
33
+ reqType,
34
+ responseType,
35
+ version,
36
+ timeout
37
+ } = config;
38
+ const listenType = responseType || reqType;
39
+ return {
40
+ useSend: () => {
41
+ const {
42
+ sendCustomCommand
43
+ } = useCustomCommand();
44
+ return {
45
+ send: data => sendCustomCommand(reqType, data, {
46
+ responseType,
47
+ version,
48
+ timeout
49
+ })
50
+ };
51
+ },
52
+ useResponse: handler => {
53
+ const handlerRef = useRef(handler);
54
+ useEffect(() => {
55
+ handlerRef.current = handler;
56
+ });
57
+ useEffect(() => {
58
+ // 与 normalResolve 同一套归一(unwrapMqttResponse + normalizeResultFields):
59
+ // LAN socket 通道不经过 mqttProvider 归一,这里补齐,保证两条通道形状一致
60
+ const wrapped = message => {
61
+ const payload = unwrapMqttResponse(message);
62
+ handlerRef.current(_objectSpread(_objectSpread({}, payload), normalizeResultFields(payload)));
63
+ };
64
+ emitter.on(listenType, wrapped);
65
+ return () => {
66
+ emitter.off(listenType, wrapped);
67
+ };
68
+ }, [listenType]);
69
+ }
70
+ };
71
+ };
@@ -5,7 +5,11 @@ import { BaseResponse } from '../type/protocols/base';
5
5
  * 提供统一的 sendMqttMessage 方法,自动从 Context 获取 devId 和配置
6
6
  */
7
7
  export declare const useStructuredMessage: () => {
8
- sendStructuredMessage: <T extends BaseResponse>(reqType: string, message: Record<string, any> | undefined, resolve: (value: T) => void, reject: (reason?: any) => void, responseType?: string) => void;
8
+ sendStructuredMessage: <T extends BaseResponse>(reqType: string, message: Record<string, any> | undefined, resolve: (value: T) => void, reject: (reason?: any) => void, options?: {
9
+ responseType?: string;
10
+ timeout?: number;
11
+ version?: string;
12
+ }) => void;
9
13
  };
10
14
  /**
11
15
  * 错误处理工具函数
@@ -91,12 +91,13 @@ export const useStructuredMessage = () => {
91
91
  };
92
92
 
93
93
  // 创建消息参数
94
- const createMessageParams = (reqType, message, useLocal) => {
94
+ const createMessageParams = (reqType, message, useLocal, version) => {
95
95
  return createSetCommonParams({
96
96
  deviceId: devId,
97
97
  reqType,
98
98
  message,
99
- isLocalOnLine: useLocal
99
+ isLocalOnLine: useLocal,
100
+ version
100
101
  });
101
102
  };
102
103
 
@@ -139,7 +140,12 @@ export const useStructuredMessage = () => {
139
140
  /**
140
141
  * 发送结构化消息
141
142
  */
142
- const sendStructuredMessage = useCallback((reqType, message, resolve, reject, responseType) => {
143
+ const sendStructuredMessage = useCallback((reqType, message, resolve, reject, options) => {
144
+ const {
145
+ responseType,
146
+ timeout,
147
+ version
148
+ } = options !== null && options !== void 0 ? options : {};
143
149
  // 参数验证
144
150
  if (!devId) {
145
151
  reject(new Error('设备ID (devId) 为必填项'));
@@ -162,14 +168,24 @@ export const useStructuredMessage = () => {
162
168
  method,
163
169
  useLocal
164
170
  } = selectedMethod;
165
- const messageParams = createMessageParams(reqType, message, useLocal);
171
+ const messageParams = createMessageParams(reqType, message, useLocal, version);
166
172
 
167
173
  // 成功回调
168
174
 
169
175
  // 失败回调
170
176
 
171
- // 发送消息
172
- method.send(messageParams, () => {
177
+ // 处理响应:先挂监听再发送,避免回复先于监听注册到达而丢响应;
178
+ // taskId 参与匹配,同 reqType 并发请求不会互相串包
179
+
180
+ normalResolve(responseType || reqType, messageParams.taskId, resolve, reject, timeout);
181
+
182
+ // 发送消息(taskId 是给调用方的回执,不进传输参数)
183
+ method.send({
184
+ deviceId: messageParams.deviceId,
185
+ message: messageParams.message,
186
+ options: messageParams.options,
187
+ protocol: messageParams.protocol
188
+ }, () => {
173
189
  const channelName = useLocal ? '局域网' : 'WiFi MQTT';
174
190
  const fallbackSuffix = method === mqttCommunication && preferredChannel === COMMUNICATION_CHANNELS.LAN ? ' (备用)' : '';
175
191
  logCommunication(LOG_LEVEL.INFO, reqType, `${channelName} 消息发送成功${fallbackSuffix}`, messageParams);
@@ -178,10 +194,6 @@ export const useStructuredMessage = () => {
178
194
  error: JSON.stringify(error)
179
195
  }));
180
196
  });
181
-
182
- // 处理响应
183
-
184
- normalResolve(responseType || reqType, resolve, reject);
185
197
  } catch (error) {
186
198
  logCommunication(LOG_LEVEL.ERROR, reqType, `发送消息时发生异常: ${error}`);
187
199
  reject(error);
@@ -24,4 +24,9 @@ import { useCruise } from './useCruise';
24
24
  import { useSpotPolygons } from './useSpotPolygons';
25
25
  import { useMcsRoomInfo } from './useMcsRoomInfo';
26
26
  import { useThreshold } from './useThreshold';
27
- export { useCarpet, useCarpetClean, useVoice, MqttProvider, useDeviceModel, useFurnitureModel, useDevInfo, usePartMerge, usePartDivision, usePassword, useWifiMap, useQuiteHours, useVirtualArea, useSpotClean, useZoneClean, useSelectRoomClean, useVirtualWall, useSchedule, useHistoryMap, useRoomProperty, useResetMap, usePurifierSchedule, useCruise, useSpotPolygons, useMcsRoomInfo, useThreshold, };
27
+ import { useCustomCommand } from './useCustomCommand';
28
+ import { defineRobotCommand } from './defineRobotCommand';
29
+ export { useCarpet, useCarpetClean, useVoice, MqttProvider, useDeviceModel, useFurnitureModel, useDevInfo, usePartMerge, usePartDivision, usePassword, useWifiMap, useQuiteHours, useVirtualArea, useSpotClean, useZoneClean, useSelectRoomClean, useVirtualWall, useSchedule, useHistoryMap, useRoomProperty, useResetMap, usePurifierSchedule, useCruise, useSpotPolygons, useMcsRoomInfo, useThreshold, useCustomCommand, defineRobotCommand, };
30
+ export type { CustomCommandOptions, TSendCustomCommand } from './useCustomCommand';
31
+ export type { RobotCommandConfig, DefinedRobotCommand } from './defineRobotCommand';
32
+ export * from './type/protocols';
package/lib/mqtt/index.js CHANGED
@@ -24,4 +24,13 @@ import { useCruise } from './useCruise';
24
24
  import { useSpotPolygons } from './useSpotPolygons';
25
25
  import { useMcsRoomInfo } from './useMcsRoomInfo';
26
26
  import { useThreshold } from './useThreshold';
27
- export { useCarpet, useCarpetClean, useVoice, MqttProvider, useDeviceModel, useFurnitureModel, useDevInfo, usePartMerge, usePartDivision, usePassword, useWifiMap, useQuiteHours, useVirtualArea, useSpotClean, useZoneClean, useSelectRoomClean, useVirtualWall, useSchedule, useHistoryMap, useRoomProperty, useResetMap, usePurifierSchedule, useCruise, useSpotPolygons, useMcsRoomInfo, useThreshold };
27
+ import { useCustomCommand } from './useCustomCommand';
28
+ import { defineRobotCommand } from './defineRobotCommand';
29
+ export { useCarpet, useCarpetClean, useVoice, MqttProvider, useDeviceModel, useFurnitureModel, useDevInfo, usePartMerge, usePartDivision, usePassword, useWifiMap, useQuiteHours, useVirtualArea, useSpotClean, useZoneClean, useSelectRoomClean, useVirtualWall, useSchedule, useHistoryMap, useRoomProperty, useResetMap, usePurifierSchedule, useCruise, useSpotPolygons, useMcsRoomInfo, useThreshold, useCustomCommand, defineRobotCommand };
30
+
31
+ // 自定义指令相关类型
32
+
33
+ // 内置指令的 payload / 响应类型公共导出:
34
+ // 自定义指令在标准指令上加字段(手拼完整 payload 复用类型)、或包装内置 hook 时直接 import,
35
+ // 不再需要手抄(此前消费方因未导出抄过 SetRoomPropertyData)
36
+ export * from './type/protocols';
@@ -54,15 +54,30 @@ export interface MqttResponseMessage {
54
54
  export type PromiseWithRejection = Promise<any> & {
55
55
  reject: (reason?: MqttError) => void;
56
56
  };
57
+ /**
58
+ * 拆掉响应外层的「信封」:仅当外层是纯信封形状(除 data / reqType 外没有别的字段,
59
+ * 且 data 为对象)才取内层——LAN 通道与部分固件的 set-ack 会把真实载荷包在 data 里。
60
+ * 业务 payload 自带 data 对象字段时不误拆,保证两条通道推出一致形状。
61
+ */
62
+ export declare const unwrapMqttResponse: (message: any) => MqttResponseMessage;
63
+ /**
64
+ * 归一 success / errCode:errCode 优先,缺省按 success 推导(success === false → -1,否则 0)。
65
+ */
66
+ export declare const normalizeResultFields: (payload: MqttResponseMessage) => {
67
+ errCode: number;
68
+ success: boolean;
69
+ };
57
70
  /**
58
71
  * 创建 MQTT 请求的 Promise,监听响应消息
59
72
  *
60
- * ⚠️ 注意:此函数不再使用 taskId 进行匹配,只要接收到对应 reqType 的响应就认为匹配
61
- * ⚠️ 风险:如果同时发起多个相同 reqType 的请求,可能会匹配到错误的响应
62
- * ⚠️ 建议:确保业务上不会同时发起多个相同类型的请求
73
+ * 匹配语义(taskId + reqType 双匹配):
74
+ * - 事件按 reqType 订阅;
75
+ * - 下发方传了 taskId、且响应携带 taskId 时,必须等值才视为本次请求的响应,
76
+ * 其余响应忽略并继续等待——同 reqType 并发请求不会互相串包;
77
+ * - 响应缺 taskId(部分固件不回 echo)时放行,避免退化成必然超时。
63
78
  *
64
79
  * @param reqType 请求类型,用于匹配响应消息
65
- * @param taskId 任务ID(已废弃,保留参数以保持向后兼容)
80
+ * @param taskId 下发时生成的任务ID,用于并发区分;传 undefined 退化为 reqType 首响应匹配
66
81
  * @param resolve Promise 的 resolve 回调
67
82
  * @param reject Promise 的 reject 回调
68
83
  * @param timeout 超时时间(毫秒),默认 10 秒
@@ -70,9 +85,9 @@ export type PromiseWithRejection = Promise<any> & {
70
85
  * @example
71
86
  * ```typescript
72
87
  * return new Promise((resolve, reject) => {
73
- * normalResolve('carpetQry', '', resolve, reject);
88
+ * normalResolve('carpetQry', '1730375500113_1', resolve, reject);
74
89
  * });
75
90
  * ```
76
91
  */
77
- export declare function normalResolve(reqType: string, resolve: (value: any) => void, reject: (reason?: any) => void, timeout?: number): void;
92
+ export declare function normalResolve(reqType: string, taskId: string | undefined, resolve: (value: any) => void, reject: (reason?: any) => void, timeout?: number): void;
78
93
  export type MyError = MqttError;
@@ -1,3 +1,5 @@
1
+ import "core-js/modules/esnext.iterator.constructor.js";
2
+ import "core-js/modules/esnext.iterator.every.js";
1
3
  import { emitter, logger } from '../utils';
2
4
 
3
5
  /**
@@ -86,15 +88,41 @@ export function dispatchUnhandledRejection(reason) {
86
88
  * Promise 扩展类型,支持手动拒绝
87
89
  */
88
90
 
91
+ /**
92
+ * 拆掉响应外层的「信封」:仅当外层是纯信封形状(除 data / reqType 外没有别的字段,
93
+ * 且 data 为对象)才取内层——LAN 通道与部分固件的 set-ack 会把真实载荷包在 data 里。
94
+ * 业务 payload 自带 data 对象字段时不误拆,保证两条通道推出一致形状。
95
+ */
96
+ export const unwrapMqttResponse = message => {
97
+ if (message != null && typeof message === 'object' && message.data != null && typeof message.data === 'object' && Object.keys(message).every(key => key === 'data' || key === 'reqType')) {
98
+ return message.data;
99
+ }
100
+ return message !== null && message !== void 0 ? message : {};
101
+ };
102
+
103
+ /**
104
+ * 归一 success / errCode:errCode 优先,缺省按 success 推导(success === false → -1,否则 0)。
105
+ */
106
+ export const normalizeResultFields = payload => {
107
+ var _payload$errCode, _payload$success;
108
+ const errCode = (_payload$errCode = payload.errCode) !== null && _payload$errCode !== void 0 ? _payload$errCode : payload.success === false ? -1 : 0;
109
+ return {
110
+ errCode,
111
+ success: (_payload$success = payload.success) !== null && _payload$success !== void 0 ? _payload$success : errCode === 0
112
+ };
113
+ };
114
+
89
115
  /**
90
116
  * 创建 MQTT 请求的 Promise,监听响应消息
91
117
  *
92
- * ⚠️ 注意:此函数不再使用 taskId 进行匹配,只要接收到对应 reqType 的响应就认为匹配
93
- * ⚠️ 风险:如果同时发起多个相同 reqType 的请求,可能会匹配到错误的响应
94
- * ⚠️ 建议:确保业务上不会同时发起多个相同类型的请求
118
+ * 匹配语义(taskId + reqType 双匹配):
119
+ * - 事件按 reqType 订阅;
120
+ * - 下发方传了 taskId、且响应携带 taskId 时,必须等值才视为本次请求的响应,
121
+ * 其余响应忽略并继续等待——同 reqType 并发请求不会互相串包;
122
+ * - 响应缺 taskId(部分固件不回 echo)时放行,避免退化成必然超时。
95
123
  *
96
124
  * @param reqType 请求类型,用于匹配响应消息
97
- * @param taskId 任务ID(已废弃,保留参数以保持向后兼容)
125
+ * @param taskId 下发时生成的任务ID,用于并发区分;传 undefined 退化为 reqType 首响应匹配
98
126
  * @param resolve Promise 的 resolve 回调
99
127
  * @param reject Promise 的 reject 回调
100
128
  * @param timeout 超时时间(毫秒),默认 10 秒
@@ -102,12 +130,12 @@ export function dispatchUnhandledRejection(reason) {
102
130
  * @example
103
131
  * ```typescript
104
132
  * return new Promise((resolve, reject) => {
105
- * normalResolve('carpetQry', '', resolve, reject);
133
+ * normalResolve('carpetQry', '1730375500113_1', resolve, reject);
106
134
  * });
107
135
  * ```
108
136
  */
109
- export function normalResolve(reqType, resolve, reject) {
110
- let timeout = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DEFAULT_TIMEOUT;
137
+ export function normalResolve(reqType, taskId, resolve, reject) {
138
+ let timeout = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_TIMEOUT;
111
139
  let isResolved = false;
112
140
 
113
141
  // 设置超时定时器
@@ -123,14 +151,21 @@ export function normalResolve(reqType, resolve, reject) {
123
151
  }
124
152
  }, timeout);
125
153
  const handle = message => {
126
- var _response$errCode;
127
- const response = typeof message.data === 'object' ? message.data : message;
154
+ const response = unwrapMqttResponse(message);
128
155
 
129
156
  // 如果已经处理过,直接返回(避免重复处理)
130
157
  if (isResolved) {
131
158
  return;
132
159
  }
133
160
 
161
+ // taskId 匹配:响应携带 taskId 且与本次下发不一致时忽略,继续等待本请求的响应
162
+ if (taskId && response.taskId != null && `${response.taskId}` !== `${taskId}`) {
163
+ logger('info', {
164
+ msg: `${reqType} normalResolve: taskId mismatch, keep waiting (${response.taskId} != ${taskId})`
165
+ });
166
+ return;
167
+ }
168
+
134
169
  // 标记为已处理(防止其他监听器处理)
135
170
  isResolved = true;
136
171
  clearTimeout(timer);
@@ -140,9 +175,9 @@ export function normalResolve(reqType, resolve, reject) {
140
175
 
141
176
  // 移除监听器(使用函数引用确保移除正确的监听器)
142
177
  emitter.off(reqType, handle);
143
-
144
- // 优先使用 errCode,如果没有则使用 success 字段判断
145
- const errCode = (_response$errCode = response.errCode) !== null && _response$errCode !== void 0 ? _response$errCode : response.success === false ? -1 : 0;
178
+ const {
179
+ errCode
180
+ } = normalizeResultFields(response);
146
181
  if (errCode === 0) {
147
182
  resolve(response);
148
183
  } else {
@@ -38,6 +38,7 @@ export interface SetCarpetData {
38
38
  }
39
39
  export interface AddCarpetResponse extends BaseResponse {
40
40
  ids: StringOrNumber[];
41
+ mapId?: number;
41
42
  }
42
43
  export interface CarpetQueryResponse extends BaseResponse {
43
44
  num: number;
@@ -49,6 +50,7 @@ export interface CarpetQueryResponse extends BaseResponse {
49
50
  autoBoost?: number[];
50
51
  fineCleaning?: number[];
51
52
  sideBrushRotating?: number[];
53
+ mapId?: number;
52
54
  }
53
55
  export type TQueryCarpet = (data?: QueryCarpetData) => Promise<CarpetQueryResponse>;
54
56
  export type TAddCarpet = (data: AddCarpetData) => Promise<AddCarpetResponse>;
@@ -1,3 +1,4 @@
1
+ export * from './base';
1
2
  export * from './scheduleProtocol';
2
3
  export * from './carpetProtocol';
3
4
  export * from './zoneCleanProtocol';
@@ -1,3 +1,4 @@
1
+ export * from './base';
1
2
  export * from './scheduleProtocol';
2
3
  export * from './carpetProtocol';
3
4
  export * from './zoneCleanProtocol';
@@ -78,7 +78,9 @@ export const useCarpet = () => {
78
78
  const sendData = pickBy(processedData, value => value !== undefined);
79
79
  sendStructuredMessage(CarpetEnum.update, {
80
80
  carpetInfo: sendData
81
- }, resolve, error => reject(handleMqttError(error, 'Failed to update carpet')), CarpetEnum.update);
81
+ }, resolve, error => reject(handleMqttError(error, 'Failed to update carpet')), {
82
+ responseType: CarpetEnum.update
83
+ });
82
84
  } catch (error) {
83
85
  reject(handleMqttError(error, 'Failed to update carpet'));
84
86
  }
@@ -91,7 +93,9 @@ export const useCarpet = () => {
91
93
  carpetInfo: {
92
94
  ids: data.ids
93
95
  }
94
- }, resolve, error => reject(handleMqttError(error, 'Failed to delete carpet')), CarpetEnum.delete);
96
+ }, resolve, error => reject(handleMqttError(error, 'Failed to delete carpet')), {
97
+ responseType: CarpetEnum.delete
98
+ });
95
99
  } catch (error) {
96
100
  reject(handleMqttError(error, 'Failed to delete carpet'));
97
101
  }
@@ -101,7 +105,9 @@ export const useCarpet = () => {
101
105
  let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
102
106
  return new Promise((resolve, reject) => {
103
107
  try {
104
- sendStructuredMessage(CarpetEnum.query, data, resolve, error => reject(handleMqttError(error, 'Failed to query carpet')), CarpetEnum.query);
108
+ sendStructuredMessage(CarpetEnum.query, data, resolve, error => reject(handleMqttError(error, 'Failed to query carpet')), {
109
+ responseType: CarpetEnum.query
110
+ });
105
111
  } catch (error) {
106
112
  reject(handleMqttError(error, 'Failed to query carpet'));
107
113
  }
@@ -121,7 +127,9 @@ export const useCarpet = () => {
121
127
  ids: carpetInfo.ids,
122
128
  type: carpetInfo.type
123
129
  };
124
- sendStructuredMessage(CarpetEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set carpet')), CarpetEnum.query);
130
+ sendStructuredMessage(CarpetEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set carpet')), {
131
+ responseType: CarpetEnum.query
132
+ });
125
133
  } catch (error) {
126
134
  reject(handleMqttError(error, 'Failed to set carpet'));
127
135
  }
@@ -27,7 +27,9 @@ export const useCarpetClean = () => {
27
27
  requestCarpetClean: () => {
28
28
  return new Promise((resolve, reject) => {
29
29
  try {
30
- sendStructuredMessage(CarpetCleanEnum.query, {}, resolve, error => reject(handleMqttError(error, 'Failed to request carpet clean')), CarpetCleanEnum.query);
30
+ sendStructuredMessage(CarpetCleanEnum.query, {}, resolve, error => reject(handleMqttError(error, 'Failed to request carpet clean')), {
31
+ responseType: CarpetCleanEnum.query
32
+ });
31
33
  } catch (error) {
32
34
  reject(handleMqttError(error, 'Failed to request carpet clean'));
33
35
  }
@@ -38,7 +40,9 @@ export const useCarpetClean = () => {
38
40
  try {
39
41
  sendStructuredMessage(CarpetCleanEnum.set, {
40
42
  ids: data
41
- }, resolve, error => reject(handleMqttError(error, 'Failed to set carpet clean')), CarpetCleanEnum.set);
43
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set carpet clean')), {
44
+ responseType: CarpetCleanEnum.set
45
+ });
42
46
  } catch (error) {
43
47
  reject(handleMqttError(error, 'Failed to set carpet clean'));
44
48
  }
@@ -56,7 +56,9 @@ export const useCruise = () => {
56
56
  reject(new Error('cruiseType is required when cmd is "start"'));
57
57
  return;
58
58
  }
59
- sendStructuredMessage(CruiseEnum.set, createCruisePayload(message), resolve, error => reject(handleMqttError(error, 'Failed to set cruise')), CruiseEnum.query // 响应类型是 query,不是 set
59
+ sendStructuredMessage(CruiseEnum.set, createCruisePayload(message), resolve, error => reject(handleMqttError(error, 'Failed to set cruise')), {
60
+ responseType: CruiseEnum.query
61
+ } // 响应类型是 query,不是 set
60
62
  );
61
63
  } catch (error) {
62
64
  reject(handleMqttError(error, 'Failed to set cruise'));
@@ -0,0 +1,39 @@
1
+ import { BaseResponse } from './type/protocols/base';
2
+ /**
3
+ * 自定义指令选项
4
+ */
5
+ export interface CustomCommandOptions {
6
+ /**
7
+ * 响应匹配的事件名(reqType)。
8
+ * 缺省与下发 reqType 同名;set 类指令的回复走 qry 事件名、
9
+ * 或回复 reqType 与下发不同名(如 aiObjectIgnoreSet → aiObjectIgnoreRst)时必填。
10
+ */
11
+ responseType?: string;
12
+ /** 超时时间(毫秒),默认 10000 */
13
+ timeout?: number;
14
+ /** 协议版本号,默认 '1.0.0' */
15
+ version?: string;
16
+ }
17
+ /**
18
+ * 自定义指令发送函数类型
19
+ */
20
+ export type TSendCustomCommand = <TRes extends BaseResponse = BaseResponse>(reqType: string, data?: Record<string, any>, options?: CustomCommandOptions) => Promise<TRes>;
21
+ /**
22
+ * 自定义指令 Hook:在 MqttProvider 内任意组件使用,获得 sendCustomCommand。
23
+ *
24
+ * 与内置 hook 同一契约:返回 Promise 命令函数,loading / 重试 / toast 由业务侧自理。
25
+ * 走 MQTT + LAN JSON socket 双通道(复用 preferredChannel 策略);
26
+ * DP command_trans 二进制路线不支持(useMqtt=false 时直接 reject)。
27
+ * 响应按 reqType + taskId 双匹配,同 reqType 并发下发不会互相串包。
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const { sendCustomCommand } = useCustomCommand();
32
+ *
33
+ * // 下发查询并等待回复
34
+ * const res = await sendCustomCommand<PeakValleyResponse>('peakValleyChargeQry');
35
+ * ```
36
+ */
37
+ export declare const useCustomCommand: () => {
38
+ sendCustomCommand: TSendCustomCommand;
39
+ };
@@ -0,0 +1,45 @@
1
+ // 自定义指令:不经过内置 hook 的任意 reqType 下发出口
2
+ import { useCallback } from 'react';
3
+ import { handleMqttError, useStructuredMessage } from './hooks/useStructuredMessage';
4
+
5
+ /**
6
+ * 自定义指令选项
7
+ */
8
+
9
+ /**
10
+ * 自定义指令发送函数类型
11
+ */
12
+
13
+ /**
14
+ * 自定义指令 Hook:在 MqttProvider 内任意组件使用,获得 sendCustomCommand。
15
+ *
16
+ * 与内置 hook 同一契约:返回 Promise 命令函数,loading / 重试 / toast 由业务侧自理。
17
+ * 走 MQTT + LAN JSON socket 双通道(复用 preferredChannel 策略);
18
+ * DP command_trans 二进制路线不支持(useMqtt=false 时直接 reject)。
19
+ * 响应按 reqType + taskId 双匹配,同 reqType 并发下发不会互相串包。
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const { sendCustomCommand } = useCustomCommand();
24
+ *
25
+ * // 下发查询并等待回复
26
+ * const res = await sendCustomCommand<PeakValleyResponse>('peakValleyChargeQry');
27
+ * ```
28
+ */
29
+ export const useCustomCommand = () => {
30
+ const {
31
+ sendStructuredMessage
32
+ } = useStructuredMessage();
33
+ const sendCustomCommand = useCallback(function (reqType) {
34
+ let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
35
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
36
+ return new Promise((resolve, reject) => {
37
+ sendStructuredMessage(reqType, data,
38
+ // Promise 的 resolve 入参是 TRes 的超集(含 PromiseLike),收窄传给发送层
39
+ resolve, error => reject(handleMqttError(error, `Failed to send custom command ${reqType}`)), options);
40
+ });
41
+ }, [sendStructuredMessage]);
42
+ return {
43
+ sendCustomCommand
44
+ };
45
+ };
@@ -36,7 +36,9 @@ export const useDeviceModel = () => {
36
36
  try {
37
37
  sendStructuredMessage(DeviceModelEnum.set, {
38
38
  modelInfos: data
39
- }, resolve, error => reject(handleMqttError(error, 'Failed to set device model')), DeviceModelEnum.query);
39
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set device model')), {
40
+ responseType: DeviceModelEnum.query
41
+ });
40
42
  } catch (error) {
41
43
  reject(handleMqttError(error, 'Failed to set device model'));
42
44
  }
@@ -36,7 +36,9 @@ export const useFurnitureModel = () => {
36
36
  try {
37
37
  sendStructuredMessage(FurnitureModelEnum.set, {
38
38
  modelInfos: data
39
- }, resolve, error => reject(handleMqttError(error, 'Failed to set furniture model')), FurnitureModelEnum.query);
39
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set furniture model')), {
40
+ responseType: FurnitureModelEnum.query
41
+ });
40
42
  } catch (error) {
41
43
  reject(handleMqttError(error, 'Failed to set furniture model'));
42
44
  }
@@ -48,7 +48,9 @@ export const useHistoryMap = () => {
48
48
  if (useMqtt) {
49
49
  sendStructuredMessage(DeleteMapEnum.set, {
50
50
  mapId
51
- }, resolve, error => reject(handleMqttError(error, 'Failed to delete history map')), DeleteMapEnum.rst // 响应类型是 rst,不是 set
51
+ }, resolve, error => reject(handleMqttError(error, 'Failed to delete history map')), {
52
+ responseType: DeleteMapEnum.rst
53
+ } // 响应类型是 rst,不是 set
52
54
  );
53
55
  return;
54
56
  }
@@ -81,7 +83,9 @@ export const useHistoryMap = () => {
81
83
  urlLen: url.length
82
84
  };
83
85
  if (useMqtt) {
84
- sendStructuredMessage(UseMapEnum.set, mapData, resolve, error => reject(handleMqttError(error, 'Failed to change current map')), UseMapEnum.query // 响应类型是 query,不是 set
86
+ sendStructuredMessage(UseMapEnum.set, mapData, resolve, error => reject(handleMqttError(error, 'Failed to change current map')), {
87
+ responseType: UseMapEnum.query
88
+ } // 响应类型是 query,不是 set
85
89
  );
86
90
  return;
87
91
  }
@@ -113,7 +117,9 @@ export const useHistoryMap = () => {
113
117
  return new Promise((resolve, reject) => {
114
118
  try {
115
119
  if (useMqtt) {
116
- sendStructuredMessage(SaveCurrentMapEnum.set, data, resolve, error => reject(handleMqttError(error, 'Failed to save current map')), SaveCurrentMapEnum.query // 响应类型是 query,不是 set
120
+ sendStructuredMessage(SaveCurrentMapEnum.set, data, resolve, error => reject(handleMqttError(error, 'Failed to save current map')), {
121
+ responseType: SaveCurrentMapEnum.query
122
+ } // 响应类型是 query,不是 set
117
123
  );
118
124
  return;
119
125
  }
@@ -36,7 +36,9 @@ export const usePartDivision = () => {
36
36
  sendStructuredMessage(PartDivisionEnum.set, {
37
37
  lines: [pointStr],
38
38
  ids: [roomId]
39
- }, resolve, error => reject(handleMqttError(error, 'Failed to set part division')), PartDivisionEnum.query);
39
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set part division')), {
40
+ responseType: PartDivisionEnum.query
41
+ });
40
42
  return;
41
43
  }
42
44
  const roomIdNumber = typeof roomId === 'string' ? Number(roomId) : roomId;
@@ -31,7 +31,9 @@ export const usePartMerge = () => {
31
31
  if (useMqtt) {
32
32
  sendStructuredMessage(PartMergeEnum.set, {
33
33
  ids
34
- }, resolve, error => reject(handleMqttError(error, 'Failed to set part merge')), PartMergeEnum.query // 响应类型是 query,不是 set
34
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set part merge')), {
35
+ responseType: PartMergeEnum.query
36
+ } // 响应类型是 query,不是 set
35
37
  );
36
38
  return;
37
39
  }
@@ -64,7 +64,9 @@ export const usePassword = () => {
64
64
  oldPassword: params.oldPassword ? md5(params.oldPassword).toString() : undefined,
65
65
  taskId: (_params$taskId = params.taskId) !== null && _params$taskId !== void 0 ? _params$taskId : String(Date.now())
66
66
  };
67
- sendStructuredMessage(PasswordEnum.set, passwordData, resolve, error => reject(handleMqttError(error, 'Failed to set password')), PasswordEnum.rst // 响应类型是 rst,不是 set
67
+ sendStructuredMessage(PasswordEnum.set, passwordData, resolve, error => reject(handleMqttError(error, 'Failed to set password')), {
68
+ responseType: PasswordEnum.rst
69
+ } // 响应类型是 rst,不是 set
68
70
  );
69
71
  } catch (error) {
70
72
  reject(handleMqttError(error, 'Failed to set password'));
@@ -86,7 +88,9 @@ export const usePassword = () => {
86
88
  password: md5(params.password).toString(),
87
89
  taskId: (_params$taskId2 = params.taskId) !== null && _params$taskId2 !== void 0 ? _params$taskId2 : String(Date.now())
88
90
  };
89
- sendStructuredMessage(PasswordEnum.check, passwordData, resolve, error => reject(handleMqttError(error, 'Failed to check password')), PasswordEnum.checkRst // 响应类型是 checkRst,不是 check
91
+ sendStructuredMessage(PasswordEnum.check, passwordData, resolve, error => reject(handleMqttError(error, 'Failed to check password')), {
92
+ responseType: PasswordEnum.checkRst
93
+ } // 响应类型是 checkRst,不是 check
90
94
  );
91
95
  } catch (error) {
92
96
  reject(handleMqttError(error, 'Failed to check password'));
@@ -55,7 +55,9 @@ export const usePurifierSchedule = () => {
55
55
  }
56
56
  sendStructuredMessage(PurifierScheduleEnum.set, _objectSpread(_objectSpread({}, message), {}, {
57
57
  num: (_message$num = message.num) !== null && _message$num !== void 0 ? _message$num : message.list.length
58
- }), resolve, error => reject(handleMqttError(error, 'Failed to set purifier schedule')), PurifierScheduleEnum.query // 响应类型是 query,不是 set
58
+ }), resolve, error => reject(handleMqttError(error, 'Failed to set purifier schedule')), {
59
+ responseType: PurifierScheduleEnum.query
60
+ } // 响应类型是 query,不是 set
59
61
  );
60
62
  } catch (error) {
61
63
  reject(handleMqttError(error, 'Failed to set purifier schedule'));
@@ -52,7 +52,9 @@ export const useQuiteHours = () => {
52
52
  time: [`${h0},${m0}`, `${h1},${m1}`],
53
53
  active,
54
54
  day
55
- }, resolve, error => reject(handleMqttError(error, 'Failed to set quiet hours')), QuietHoursEnum.query // 响应类型是 query,不是 set
55
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set quiet hours')), {
56
+ responseType: QuietHoursEnum.query
57
+ } // 响应类型是 query,不是 set
56
58
  );
57
59
  return;
58
60
  }
@@ -28,7 +28,9 @@ export const useResetMap = () => {
28
28
  return new Promise((resolve, reject) => {
29
29
  try {
30
30
  if (useMqtt) {
31
- sendStructuredMessage(ResetCurrMapEnum.set, {}, resolve, error => reject(handleMqttError(error, 'Failed to reset map')), ResetCurrMapEnum.query // 响应类型是 query,不是 set
31
+ sendStructuredMessage(ResetCurrMapEnum.set, {}, resolve, error => reject(handleMqttError(error, 'Failed to reset map')), {
32
+ responseType: ResetCurrMapEnum.query
33
+ } // 响应类型是 query,不是 set
32
34
  );
33
35
  return;
34
36
  }
@@ -73,7 +73,9 @@ export const useRoomProperty = () => {
73
73
  }), data.polygons && {
74
74
  polygons: data.polygons
75
75
  }), pickWaterFields(len, data.cisterns, data.waterValues));
76
- sendStructuredMessage(RoomPropertyEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set room property')), RoomPropertyEnum.query // 响应类型是 query,不是 set
76
+ sendStructuredMessage(RoomPropertyEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set room property')), {
77
+ responseType: RoomPropertyEnum.query
78
+ } // 响应类型是 query,不是 set
77
79
  );
78
80
  } catch (error) {
79
81
  reject(handleMqttError(error, 'Failed to set room property'));
@@ -65,7 +65,9 @@ export const useSchedule = () => {
65
65
  if (useMqtt) {
66
66
  sendStructuredMessage(ScheduleEnum.set, _objectSpread(_objectSpread({}, message), {}, {
67
67
  list
68
- }), resolve, error => reject(handleMqttError(error, 'Failed to set schedule')), ScheduleEnum.query // 响应类型是 query,不是 set
68
+ }), resolve, error => reject(handleMqttError(error, 'Failed to set schedule')), {
69
+ responseType: ScheduleEnum.query
70
+ } // 响应类型是 query,不是 set
69
71
  );
70
72
  return;
71
73
  }
@@ -107,7 +107,9 @@ export const useSelectRoomClean = () => {
107
107
  routePreferences: prefs.map(p => p !== null && p !== void 0 ? p : 'standard')
108
108
  }), {}, {
109
109
  num: rooms.length
110
- }), resolve, error => reject(handleMqttError(error, 'Failed to set room clean')), RoomCleanSetEnum.query // 响应类型是 query,不是 set
110
+ }), resolve, error => reject(handleMqttError(error, 'Failed to set room clean')), {
111
+ responseType: RoomCleanSetEnum.query
112
+ } // 响应类型是 query,不是 set
111
113
  );
112
114
  return;
113
115
  }
@@ -80,7 +80,9 @@ export const useSpotClean = () => {
80
80
  names
81
81
  }), spotNames && {
82
82
  spotNames
83
- }), resolve, error => reject(handleMqttError(error, 'Failed to set spot clean')), SpotCleanEnum.query // 响应类型是 query,不是 set
83
+ }), resolve, error => reject(handleMqttError(error, 'Failed to set spot clean')), {
84
+ responseType: SpotCleanEnum.query
85
+ } // 响应类型是 query,不是 set
84
86
  );
85
87
  return;
86
88
  }
@@ -38,7 +38,9 @@ export const useSpotPolygons = () => {
38
38
  reject(new Error('useMqtt is not used'));
39
39
  return;
40
40
  }
41
- sendStructuredMessage(SpotPolygonsEnum.set, createSpotPolygonsPayload(message), resolve, error => reject(handleMqttError(error, 'Failed to set spot polygons')), SpotPolygonsEnum.query);
41
+ sendStructuredMessage(SpotPolygonsEnum.set, createSpotPolygonsPayload(message), resolve, error => reject(handleMqttError(error, 'Failed to set spot polygons')), {
42
+ responseType: SpotPolygonsEnum.query
43
+ });
42
44
  } catch (error) {
43
45
  reject(handleMqttError(error, 'Failed to set spot polygons'));
44
46
  }
@@ -77,7 +77,9 @@ export const useThreshold = () => {
77
77
  polygons,
78
78
  types,
79
79
  names
80
- }, resolve, error => reject(handleMqttError(error, 'Failed to set threshold')), ThresholdEnum.query // 响应类型是 query,不是 set
80
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set threshold')), {
81
+ responseType: ThresholdEnum.query
82
+ } // 响应类型是 query,不是 set
81
83
  );
82
84
  } catch (error) {
83
85
  reject(handleMqttError(error, 'Failed to set threshold'));
@@ -96,7 +96,9 @@ export const useVirtualArea = () => {
96
96
  num: data.length,
97
97
  modes: modes,
98
98
  names: names
99
- }, resolve, error => reject(handleMqttError(error, 'Failed to set virtual area')), VirtualAreaEnum.query // 响应类型是 query,不是 set
99
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set virtual area')), {
100
+ responseType: VirtualAreaEnum.query
101
+ } // 响应类型是 query,不是 set
100
102
  );
101
103
  return;
102
104
  }
@@ -77,7 +77,9 @@ export const useVirtualWall = () => {
77
77
  points: data.map(points => pointsToString(points, origin)),
78
78
  num: data.length,
79
79
  mode: new Array(data.length).fill('0')
80
- }, resolve, error => reject(handleMqttError(error, 'Failed to set virtual wall')), VirtualWallEnum.query // 响应类型是 query,不是 set
80
+ }, resolve, error => reject(handleMqttError(error, 'Failed to set virtual wall')), {
81
+ responseType: VirtualWallEnum.query
82
+ } // 响应类型是 query,不是 set
81
83
  );
82
84
  return;
83
85
  }
@@ -71,7 +71,9 @@ export const useVoice = () => {
71
71
  const sendData = _objectSpread(_objectSpread({}, message), {}, {
72
72
  urlLen: message.url.length
73
73
  });
74
- sendStructuredMessage(VoiceLanguageEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set voice')), VoiceLanguageEnum.query);
74
+ sendStructuredMessage(VoiceLanguageEnum.set, sendData, resolve, error => reject(handleMqttError(error, 'Failed to set voice')), {
75
+ responseType: VoiceLanguageEnum.query
76
+ });
75
77
  return;
76
78
  }
77
79
 
@@ -41,7 +41,9 @@ export const useWifiMap = () => {
41
41
  taskId: data.taskId || String(Date.now()),
42
42
  switch: (_data$switch = data.switch) !== null && _data$switch !== void 0 ? _data$switch : true
43
43
  };
44
- sendStructuredMessage(WifiMapEnum.set, wifiMapData, resolve, error => reject(handleMqttError(error, 'Failed to set WiFi map')), WifiMapEnum.rst // 响应类型是 rst,不是 set
44
+ sendStructuredMessage(WifiMapEnum.set, wifiMapData, resolve, error => reject(handleMqttError(error, 'Failed to set WiFi map')), {
45
+ responseType: WifiMapEnum.rst
46
+ } // 响应类型是 rst,不是 set
45
47
  );
46
48
  } catch (error) {
47
49
  reject(handleMqttError(error, 'Failed to set WiFi map'));
@@ -131,7 +131,9 @@ export const useZoneClean = () => {
131
131
  polygons
132
132
  }, commonFields), {}, {
133
133
  names
134
- }), resolve, error => reject(handleMqttError(error, 'Failed to set zone clean')), ZoneCleanEnum.query);
134
+ }), resolve, error => reject(handleMqttError(error, 'Failed to set zone clean')), {
135
+ responseType: ZoneCleanEnum.query
136
+ });
135
137
  return;
136
138
  }
137
139
 
@@ -6,27 +6,27 @@ export declare const emitter: import("mitt").Emitter<Record<import("mitt").Event
6
6
  export declare const pointsToString: (points: Point[], origin?: Point) => string;
7
7
  export declare const isJSONString: (str: any) => boolean;
8
8
  export declare const parseJSON: (str: any) => any;
9
- export declare const logger: (type: 'warn' | 'error' | 'info', params: {
10
- [key: string]: any;
9
+ export declare const logger: (type: "warn" | "error" | "info", params: {
11
10
  msg: string;
12
- }, onLogger?: ((type: 'warn' | 'error' | 'info', v: string) => void) | undefined) => void;
11
+ [key: string]: any;
12
+ }, onLogger?: (type: "warn" | "error" | "info", v: string) => void) => void;
13
13
  /**
14
14
  * 创建绑定了 onLogger 的 logger 函数
15
15
  * @param onLogger - 可选的日志回调函数
16
16
  * @returns 返回一个简化的 logger 函数
17
17
  */
18
- export declare const createLogger: (onLogger?: ((type: 'warn' | 'error' | 'info', v: string) => void) | undefined) => {
18
+ export declare const createLogger: (onLogger?: (type: "warn" | "error" | "info", v: string) => void) => {
19
19
  info: (params: {
20
- [key: string]: any;
21
20
  msg: string;
21
+ [key: string]: any;
22
22
  }) => void;
23
23
  warn: (params: {
24
- [key: string]: any;
25
24
  msg: string;
25
+ [key: string]: any;
26
26
  }) => void;
27
27
  error: (params: {
28
- [key: string]: any;
29
28
  msg: string;
29
+ [key: string]: any;
30
30
  }) => void;
31
31
  };
32
32
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/robot-data-stream",
3
- "version": "0.0.17-beta.10",
3
+ "version": "0.0.17-beta.12",
4
4
  "description": "扫地机P2P数据流标准化组件",
5
5
  "main": "lib/index",
6
6
  "files": [
@@ -17,6 +17,8 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "lint": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
20
+ "test": "vitest --run",
21
+ "typecheck": "tsc -p tsconfig.typecheck.json",
20
22
  "build": "patch-package && ray build --type=component",
21
23
  "watch": "ray start --type=component --output ./example/src/lib",
22
24
  "build:tuya": "ray build ./example",
@@ -63,6 +65,8 @@
63
65
  "patch-package": "^8.0.0",
64
66
  "prettier": "^3.8.0",
65
67
  "standard-version": "9.3.2",
68
+ "typescript": "^5.0.0",
69
+ "vitest": "^2.1.4",
66
70
  "vitepress": "^1.6.4"
67
71
  },
68
72
  "husky": {