@ray-js/robot-data-stream 0.0.1-beta-1
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/README.md +69 -0
- package/lib/api/index.d.ts +2 -0
- package/lib/api/index.js +2 -0
- package/lib/api/p2pApi.d.ts +93 -0
- package/lib/api/p2pApi.js +342 -0
- package/lib/api/sweeperP2p.d.ts +157 -0
- package/lib/api/sweeperP2p.js +479 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +93 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +2 -0
- package/lib/utils/logger.d.ts +15 -0
- package/lib/utils/logger.js +44 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
English | [简体中文](./README-zh_CN.md)
|
|
2
|
+
|
|
3
|
+
# @ray-js/robot-data-stream
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@ray-js/robot-data-stream) [](https://www.npmjs.com/package/@ray-js/robot-data-stream)
|
|
6
|
+
|
|
7
|
+
> Robot P2P data stream hooks
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
$ npm install @ray-js/robot-data-stream
|
|
13
|
+
# or
|
|
14
|
+
$ yarn add @ray-js/robot-data-stream
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Develop
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
# watch compile component code
|
|
21
|
+
yarn watch
|
|
22
|
+
# watch compile demo
|
|
23
|
+
yarn start:tuya
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
|
|
30
|
+
import React, { useEffect } from 'react';
|
|
31
|
+
import { View, Text } from '@ray-js/ray';
|
|
32
|
+
import { useP2PDataStream, StreamDataNotificationCenter } from '@ray-js/robot-data-stream';
|
|
33
|
+
import styles from './index.module.less';
|
|
34
|
+
|
|
35
|
+
const DemoBlock = ({ devId }) => {
|
|
36
|
+
|
|
37
|
+
const onReceiveMapData = (data: string) => {
|
|
38
|
+
StreamDataNotificationCenter.emit('receiveMapData', data)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const onReceivePathData = (data:string) => {
|
|
42
|
+
StreamDataNotificationCenter.emit('receivePathData', data)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
|
|
48
|
+
useP2PDataStream(devId, onReceiveMapData, onReceivePathData);
|
|
49
|
+
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View className={styles.demoBlock}>
|
|
54
|
+
<View className={styles.demoBlockTitle}>
|
|
55
|
+
<Text className={styles.demoBlockTitleText}>{devId}</Text>
|
|
56
|
+
</View>
|
|
57
|
+
</View>
|
|
58
|
+
)
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default function Home() {
|
|
62
|
+
return (
|
|
63
|
+
<View className={styles.view}>
|
|
64
|
+
<DemoBlock devId="" />
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
package/lib/api/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2P 工具类
|
|
3
|
+
*/
|
|
4
|
+
export default class P2pApi {
|
|
5
|
+
isConnected: boolean;
|
|
6
|
+
offSessionStatusChange: () => void;
|
|
7
|
+
devId: string;
|
|
8
|
+
constructor();
|
|
9
|
+
/**
|
|
10
|
+
* 设备断开之后的重连
|
|
11
|
+
*/
|
|
12
|
+
reconnectP2p: (successCb?: () => void) => void;
|
|
13
|
+
/**
|
|
14
|
+
* 初始化P2P SDK
|
|
15
|
+
* @param id 用户Id
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
initP2pSdk: (devId: string) => Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* 连接设备
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
connectDevice: (successCb?: () => void, failCb?: () => void, completeCb?: () => void) => Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* 断开P2P设备连接
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
disconnectDevice: () => Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* 开始P2p流传输
|
|
31
|
+
* @param files
|
|
32
|
+
* @param albumName
|
|
33
|
+
* @param filePath
|
|
34
|
+
* @param successCb
|
|
35
|
+
* @param failedCb
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
downloadStream: (files: {
|
|
39
|
+
files: Array<string>;
|
|
40
|
+
}, albumName: string, successCb?: () => void, failedCb?: () => void) => Promise<unknown> | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* 监听p2p传输数据流
|
|
43
|
+
* @param callback
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
onP2pStreamPacketReceive: (callback: (...args: any[]) => void) => () => void;
|
|
47
|
+
/**
|
|
48
|
+
* 开始下载文件
|
|
49
|
+
* files : {"files":["filesname1", "filesname2", "filesname3" ]}
|
|
50
|
+
*/
|
|
51
|
+
downloadFile: (files: {
|
|
52
|
+
files: Array<string>;
|
|
53
|
+
}, albumName: string, filePath: string, successCb?: () => void, failedCb?: () => void) => Promise<unknown> | null;
|
|
54
|
+
/**
|
|
55
|
+
* 注册下载监听事件
|
|
56
|
+
* @param callback
|
|
57
|
+
* @returns
|
|
58
|
+
*/
|
|
59
|
+
onDownloadProgressUpdate: (callback: (...args: any[]) => void) => () => void;
|
|
60
|
+
/**
|
|
61
|
+
* 注册多文件下载进度监听
|
|
62
|
+
* @param callback
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
onDownloadTotalProgressUpdate: (callback: (...args: any[]) => void) => () => void;
|
|
66
|
+
/**
|
|
67
|
+
* 注册单文件下载进度完成监听
|
|
68
|
+
* @param callback
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
onFileDownloadComplete: (callback: (...args: any[]) => void) => () => void;
|
|
72
|
+
/**
|
|
73
|
+
* 注册设备因为其他异常断开连接的事件
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
onSessionStatusChange: (callback: (...args: any[]) => void) => () => void;
|
|
77
|
+
/**
|
|
78
|
+
* 取消进行下载
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
81
|
+
cancelDownloadTask: () => Promise<unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* 查询设备相册文件列表
|
|
84
|
+
* @param albumName
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
queryAlbumFileIndexs: (albumName: string) => Promise<unknown>;
|
|
88
|
+
/**
|
|
89
|
+
* P2p SDK 销毁
|
|
90
|
+
* @returns
|
|
91
|
+
*/
|
|
92
|
+
deInitP2PSDK: () => Promise<boolean>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/* eslint-disable prefer-promise-reject-errors */
|
|
2
|
+
import { p2p } from '@ray-js/ray';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import { Logger } from '@/utils';
|
|
5
|
+
/**
|
|
6
|
+
* P2P 工具类
|
|
7
|
+
*/
|
|
8
|
+
export default class P2pApi {
|
|
9
|
+
// P2p连接状态
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.isConnected = false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 设备断开之后的重连
|
|
17
|
+
*/
|
|
18
|
+
reconnectP2p = successCb => {
|
|
19
|
+
/**
|
|
20
|
+
* 监听到断开事件后
|
|
21
|
+
* 连接成功后设置flag为true
|
|
22
|
+
* 并清除定时器
|
|
23
|
+
*/
|
|
24
|
+
this.isConnected = false;
|
|
25
|
+
if (!this.isConnected) {
|
|
26
|
+
Logger.info('start p2p reconnect ==>', moment().format('YYYY-MM-DD HH:mm:ss'));
|
|
27
|
+
this.connectDevice(() => {
|
|
28
|
+
Logger.info('p2p reconnect success ==>', moment().format('YYYY-MM-DD HH:mm:ss'));
|
|
29
|
+
this.isConnected = true;
|
|
30
|
+
if (this.isConnected) {
|
|
31
|
+
typeof successCb === 'function' && successCb();
|
|
32
|
+
}
|
|
33
|
+
}, () => {}, () => {
|
|
34
|
+
Logger.log('reconnect complete ==>', String(this.isConnected));
|
|
35
|
+
if (!this.isConnected) {
|
|
36
|
+
Logger.warn('p2p reconnect failed ==>', moment().format('YYYY-MM-DD HH:mm:ss'));
|
|
37
|
+
this.reconnectP2p(successCb);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 初始化P2P SDK
|
|
45
|
+
* @param id 用户Id
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
initP2pSdk = async devId => {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
try {
|
|
51
|
+
this.devId = devId;
|
|
52
|
+
p2p.P2PSDKInit({
|
|
53
|
+
success: () => {
|
|
54
|
+
Logger.info('P2PSDKInit success');
|
|
55
|
+
resolve(true);
|
|
56
|
+
},
|
|
57
|
+
fail: params => {
|
|
58
|
+
Logger.warn('P2PSDKInit fail', params);
|
|
59
|
+
resolve(false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
} catch (e) {
|
|
63
|
+
reject(false);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 连接设备
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
connectDevice = (successCb, failCb, completeCb) => {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
try {
|
|
75
|
+
p2p.connectDevice({
|
|
76
|
+
deviceId: this.devId,
|
|
77
|
+
timeout: 5000,
|
|
78
|
+
mode: 0,
|
|
79
|
+
success: () => {
|
|
80
|
+
Logger.info('p2p connectDevice success');
|
|
81
|
+
this.isConnected = true;
|
|
82
|
+
typeof successCb === 'function' && successCb();
|
|
83
|
+
resolve(true);
|
|
84
|
+
},
|
|
85
|
+
fail: params => {
|
|
86
|
+
Logger.warn('p2p connectDevice failed ==>', params);
|
|
87
|
+
typeof failCb === 'function' && failCb();
|
|
88
|
+
resolve(false);
|
|
89
|
+
},
|
|
90
|
+
complete: () => {
|
|
91
|
+
typeof completeCb === 'function' && completeCb();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
Logger.error('p2p connectDevice occur an error ==>', e);
|
|
96
|
+
reject(false);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 断开P2P设备连接
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
105
|
+
disconnectDevice = () => {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
try {
|
|
108
|
+
p2p.disconnectDevice({
|
|
109
|
+
deviceId: this.devId,
|
|
110
|
+
success: () => {
|
|
111
|
+
this.isConnected = false;
|
|
112
|
+
Logger.info('p2p disconnectDevice success');
|
|
113
|
+
resolve(true);
|
|
114
|
+
},
|
|
115
|
+
fail: () => {
|
|
116
|
+
Logger.warn('p2p disconnectDevice failed');
|
|
117
|
+
resolve(false);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch (e) {
|
|
121
|
+
Logger.warn('p2p disconnectDevice occur an error ==>', e);
|
|
122
|
+
reject(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 开始P2p流传输
|
|
129
|
+
* @param files
|
|
130
|
+
* @param albumName
|
|
131
|
+
* @param filePath
|
|
132
|
+
* @param successCb
|
|
133
|
+
* @param failedCb
|
|
134
|
+
* @returns
|
|
135
|
+
*/
|
|
136
|
+
downloadStream = (files, albumName, successCb, failedCb) => {
|
|
137
|
+
try {
|
|
138
|
+
if (this.isConnected) {
|
|
139
|
+
return new Promise(resolve => {
|
|
140
|
+
p2p.downloadStream({
|
|
141
|
+
deviceId: this.devId,
|
|
142
|
+
albumName: albumName,
|
|
143
|
+
jsonfiles: JSON.stringify(files),
|
|
144
|
+
success: () => {
|
|
145
|
+
Logger.info('p2p downloadStream success');
|
|
146
|
+
typeof successCb === 'function' && successCb();
|
|
147
|
+
resolve(true);
|
|
148
|
+
},
|
|
149
|
+
fail: params => {
|
|
150
|
+
Logger.warn('p2p downloadStream failed ==>', params);
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
typeof failedCb === 'function' && failedCb();
|
|
153
|
+
}, 500);
|
|
154
|
+
resolve(false);
|
|
155
|
+
},
|
|
156
|
+
complete: () => {}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
Logger.warn('p2p downloadStream occur an error ==>', e);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 监听p2p传输数据流
|
|
167
|
+
* @param callback
|
|
168
|
+
* @returns
|
|
169
|
+
*/
|
|
170
|
+
onP2pStreamPacketReceive = callback => {
|
|
171
|
+
p2p.onStreamPacketReceive(callback);
|
|
172
|
+
return () => {
|
|
173
|
+
// 反注册监听
|
|
174
|
+
p2p.offStreamPacketReceive(callback);
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 开始下载文件
|
|
180
|
+
* files : {"files":["filesname1", "filesname2", "filesname3" ]}
|
|
181
|
+
*/
|
|
182
|
+
downloadFile = (files, albumName, filePath, successCb, failedCb) => {
|
|
183
|
+
try {
|
|
184
|
+
if (this.isConnected) {
|
|
185
|
+
return new Promise(resolve => {
|
|
186
|
+
p2p.downloadFile({
|
|
187
|
+
deviceId: this.devId,
|
|
188
|
+
albumName: albumName,
|
|
189
|
+
filePath: filePath,
|
|
190
|
+
jsonfiles: JSON.stringify(files),
|
|
191
|
+
success: () => {
|
|
192
|
+
Logger.info('p2p downloadFile success');
|
|
193
|
+
typeof successCb === 'function' && successCb();
|
|
194
|
+
resolve(true);
|
|
195
|
+
},
|
|
196
|
+
fail: params => {
|
|
197
|
+
Logger.warn('p2p downloadFile failed ==>', params);
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
typeof failedCb === 'function' && failedCb();
|
|
200
|
+
}, 500);
|
|
201
|
+
resolve(false);
|
|
202
|
+
},
|
|
203
|
+
complete: () => {}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
Logger.error('p2p downloadFile occur an error ==>', e);
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 注册下载监听事件
|
|
215
|
+
* @param callback
|
|
216
|
+
* @returns
|
|
217
|
+
*/
|
|
218
|
+
onDownloadProgressUpdate = callback => {
|
|
219
|
+
p2p.onDownloadProgressUpdate(callback);
|
|
220
|
+
return () => {
|
|
221
|
+
// 反注册监听
|
|
222
|
+
p2p.offDownloadProgressUpdate(callback);
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 注册多文件下载进度监听
|
|
228
|
+
* @param callback
|
|
229
|
+
* @returns
|
|
230
|
+
*/
|
|
231
|
+
onDownloadTotalProgressUpdate = callback => {
|
|
232
|
+
p2p.onDownloadTotalProgressUpdate(callback);
|
|
233
|
+
return () => {
|
|
234
|
+
// 反注册监听
|
|
235
|
+
p2p.offDownloadTotalProgressUpdate(callback);
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 注册单文件下载进度完成监听
|
|
241
|
+
* @param callback
|
|
242
|
+
* @returns
|
|
243
|
+
*/
|
|
244
|
+
onFileDownloadComplete = callback => {
|
|
245
|
+
p2p.onFileDownloadComplete(callback);
|
|
246
|
+
return () => {
|
|
247
|
+
// 反注册监听
|
|
248
|
+
p2p.offFileDownloadComplete(callback);
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 注册设备因为其他异常断开连接的事件
|
|
254
|
+
* @returns
|
|
255
|
+
*/
|
|
256
|
+
onSessionStatusChange = callback => {
|
|
257
|
+
p2p.onSessionStatusChange(callback);
|
|
258
|
+
this.offSessionStatusChange = () => {
|
|
259
|
+
// 反注册监听
|
|
260
|
+
p2p.offSessionStatusChange(callback);
|
|
261
|
+
};
|
|
262
|
+
return this.offSessionStatusChange;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 取消进行下载
|
|
267
|
+
* @returns
|
|
268
|
+
*/
|
|
269
|
+
cancelDownloadTask = () => {
|
|
270
|
+
return new Promise((resolve, reject) => {
|
|
271
|
+
try {
|
|
272
|
+
p2p.cancelDownloadTask({
|
|
273
|
+
deviceId: this.devId,
|
|
274
|
+
success: () => {
|
|
275
|
+
resolve(true);
|
|
276
|
+
},
|
|
277
|
+
fail: () => {
|
|
278
|
+
reject(false);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch (e) {
|
|
282
|
+
Logger.info('cancelDownloadTask occur an error', e);
|
|
283
|
+
reject(false);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 查询设备相册文件列表
|
|
290
|
+
* @param albumName
|
|
291
|
+
* @returns
|
|
292
|
+
*/
|
|
293
|
+
queryAlbumFileIndexs = albumName => {
|
|
294
|
+
return new Promise(resolve => {
|
|
295
|
+
p2p.queryAlbumFileIndexs({
|
|
296
|
+
deviceId: this.devId,
|
|
297
|
+
albumName,
|
|
298
|
+
success: params => {
|
|
299
|
+
Logger.info('queryAlbumFileIndexs ==>', params);
|
|
300
|
+
resolve(params);
|
|
301
|
+
},
|
|
302
|
+
fail: params => {
|
|
303
|
+
Logger.warn('queryAlbumFileIndexs failed ==>', params);
|
|
304
|
+
resolve(null);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* P2p SDK 销毁
|
|
312
|
+
* @returns
|
|
313
|
+
*/
|
|
314
|
+
deInitP2PSDK = async () => {
|
|
315
|
+
// 先销毁断开监听
|
|
316
|
+
typeof this.offSessionStatusChange === 'function' && this.offSessionStatusChange();
|
|
317
|
+
// 销毁初始化时,先进行断连操作
|
|
318
|
+
if (this.isConnected) {
|
|
319
|
+
await this.disconnectDevice();
|
|
320
|
+
}
|
|
321
|
+
return new Promise((resolve, reject) => {
|
|
322
|
+
try {
|
|
323
|
+
p2p.deInitSDK({
|
|
324
|
+
success: () => {
|
|
325
|
+
Logger.info('deInitP2pSDK success');
|
|
326
|
+
resolve(true);
|
|
327
|
+
},
|
|
328
|
+
fail: () => {
|
|
329
|
+
Logger.info('deInitP2pSDK failed');
|
|
330
|
+
resolve(false);
|
|
331
|
+
},
|
|
332
|
+
complete: () => {
|
|
333
|
+
resolve(true);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
} catch (e) {
|
|
337
|
+
Logger.error('deInitP2pSDK occur an error ==>', e);
|
|
338
|
+
reject(false);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import P2pApi from './p2pApi';
|
|
2
|
+
/**
|
|
3
|
+
* 基于P2p工具类的扫地机扩展实现
|
|
4
|
+
*/
|
|
5
|
+
interface FileInfo {
|
|
6
|
+
channel: number;
|
|
7
|
+
createTime: number;
|
|
8
|
+
dir: number;
|
|
9
|
+
duration: number;
|
|
10
|
+
filename: string;
|
|
11
|
+
idx: number;
|
|
12
|
+
type: number;
|
|
13
|
+
}
|
|
14
|
+
declare const FILE_NAME_MAP: {
|
|
15
|
+
readonly 'map.bin': {
|
|
16
|
+
readonly type: 0;
|
|
17
|
+
};
|
|
18
|
+
readonly 'cleanPath.bin': {
|
|
19
|
+
readonly type: 1;
|
|
20
|
+
};
|
|
21
|
+
readonly 'map.bin.stream': {
|
|
22
|
+
readonly type: 0;
|
|
23
|
+
};
|
|
24
|
+
readonly 'cleanPath.bin.stream': {
|
|
25
|
+
readonly type: 1;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare class SweeperP2p extends P2pApi {
|
|
29
|
+
file: {
|
|
30
|
+
items: Array<FileInfo>;
|
|
31
|
+
count: number;
|
|
32
|
+
} | undefined;
|
|
33
|
+
albumName: string;
|
|
34
|
+
streamFilePath: string;
|
|
35
|
+
dataFilePath: string;
|
|
36
|
+
mapBinData: string;
|
|
37
|
+
navPathBinData: string;
|
|
38
|
+
cleanPathBinData: string;
|
|
39
|
+
mapBinStream: string;
|
|
40
|
+
navPathBinStream: string;
|
|
41
|
+
cleanPathBinStream: string;
|
|
42
|
+
downloadType: number;
|
|
43
|
+
readingMap: boolean;
|
|
44
|
+
readingClean: boolean;
|
|
45
|
+
readingNav: boolean;
|
|
46
|
+
cacheDir: string;
|
|
47
|
+
fileIndex: number;
|
|
48
|
+
cacheData: any;
|
|
49
|
+
packetDataCacheMap: Map<keyof typeof FILE_NAME_MAP, Map<number, string>>;
|
|
50
|
+
packetSerialNumberCacheMap: Map<keyof typeof FILE_NAME_MAP, number>;
|
|
51
|
+
packetTotalMap: Map<keyof typeof FILE_NAME_MAP, number>;
|
|
52
|
+
onReceiveMapData: (data: string) => void;
|
|
53
|
+
onReceivePathData: (data: string) => void;
|
|
54
|
+
offFileDownloadComplete: () => void;
|
|
55
|
+
offP2pStreamPacketReceive: () => void;
|
|
56
|
+
offDownLoadProgressUpdate: () => void;
|
|
57
|
+
offTotalDownLoadProgressUpdate: () => void;
|
|
58
|
+
constructor();
|
|
59
|
+
private setStreamFilePath;
|
|
60
|
+
private setDataFilePath;
|
|
61
|
+
/**
|
|
62
|
+
* 获取流文件存储地址
|
|
63
|
+
* @param fileName
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
private getStreamFilePath;
|
|
67
|
+
/**
|
|
68
|
+
* 获取完整文件存储地址
|
|
69
|
+
* @param fileName
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
private getDataFilePath;
|
|
73
|
+
/**
|
|
74
|
+
* 创建文件路径文件夹
|
|
75
|
+
* @param filePath
|
|
76
|
+
* @returns
|
|
77
|
+
*/
|
|
78
|
+
private createFilePath;
|
|
79
|
+
/**
|
|
80
|
+
* 检查当前文件目录是否存在
|
|
81
|
+
* @param filePath
|
|
82
|
+
* @returns
|
|
83
|
+
*/
|
|
84
|
+
private checkIfDirIsExist;
|
|
85
|
+
/**
|
|
86
|
+
* 初始化文件目录
|
|
87
|
+
* @param filePath
|
|
88
|
+
* @returns
|
|
89
|
+
*/
|
|
90
|
+
private initFilePath;
|
|
91
|
+
/**
|
|
92
|
+
* 根据文件名返回对应的fileType
|
|
93
|
+
* @param filename
|
|
94
|
+
*/
|
|
95
|
+
private getFileType;
|
|
96
|
+
/**
|
|
97
|
+
* 设备连接状态发生改变
|
|
98
|
+
* @param data
|
|
99
|
+
*/
|
|
100
|
+
sessionStatusCallback: (data: {
|
|
101
|
+
deviceId: string;
|
|
102
|
+
status: number;
|
|
103
|
+
}) => void;
|
|
104
|
+
/**
|
|
105
|
+
* 开始进行文件下载
|
|
106
|
+
* @param downloadType
|
|
107
|
+
* 0: 下载断开 1: 持续下载
|
|
108
|
+
*/
|
|
109
|
+
startObserverSweeperDataByP2P: (downloadType: number, devId: string, onReceiveMapData: (data: string) => void, onReceivePathData: (data: string) => void) => Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* 注册下载有关的监听
|
|
112
|
+
*/
|
|
113
|
+
private registerP2pDownloadEvent;
|
|
114
|
+
/**
|
|
115
|
+
* 移除下载有关的监听
|
|
116
|
+
*/
|
|
117
|
+
private removeP2pDownloadEvent;
|
|
118
|
+
/**
|
|
119
|
+
* 单文件下载完成回调
|
|
120
|
+
* @param downloadComplete
|
|
121
|
+
*/
|
|
122
|
+
private fileDownloadCompleteCallback;
|
|
123
|
+
/**
|
|
124
|
+
* p2p数据流回调
|
|
125
|
+
* @param downloadComplete
|
|
126
|
+
*/
|
|
127
|
+
private p2pStreamPacketReceiveCallback;
|
|
128
|
+
/**
|
|
129
|
+
* 单文件下载完成回调
|
|
130
|
+
* @param data
|
|
131
|
+
*/
|
|
132
|
+
private fileDownloadProgressCallback;
|
|
133
|
+
/**
|
|
134
|
+
* 多文件下载进度监听
|
|
135
|
+
* @param data
|
|
136
|
+
*/
|
|
137
|
+
private fileTotalDownloadProgressCallback;
|
|
138
|
+
/**
|
|
139
|
+
* 从指定的文件路径获取文件
|
|
140
|
+
* @param fileName
|
|
141
|
+
* @param filePath
|
|
142
|
+
*/
|
|
143
|
+
private readFileFromPath;
|
|
144
|
+
private setReading;
|
|
145
|
+
private resetReading;
|
|
146
|
+
/**
|
|
147
|
+
* 查询返回的文件中是否包含了所需文件
|
|
148
|
+
* @param fileList
|
|
149
|
+
*/
|
|
150
|
+
private queryNeedFiles;
|
|
151
|
+
/**
|
|
152
|
+
* 停止文件下载
|
|
153
|
+
*/
|
|
154
|
+
stopObserverSweeperDataByP2P: () => Promise<unknown>;
|
|
155
|
+
}
|
|
156
|
+
declare const sweeperP2pInstance: SweeperP2p;
|
|
157
|
+
export default sweeperP2pInstance;
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/* eslint-disable no-shadow */
|
|
2
|
+
import P2pApi from './p2pApi';
|
|
3
|
+
import Base64 from 'base64-js';
|
|
4
|
+
import { padStart } from 'lodash-es';
|
|
5
|
+
import { Logger } from '@/utils';
|
|
6
|
+
/**
|
|
7
|
+
* 基于P2p工具类的扫地机扩展实现
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const FILE_NAME_MAP = {
|
|
11
|
+
'map.bin': {
|
|
12
|
+
type: 0
|
|
13
|
+
},
|
|
14
|
+
'cleanPath.bin': {
|
|
15
|
+
type: 1
|
|
16
|
+
},
|
|
17
|
+
'map.bin.stream': {
|
|
18
|
+
type: 0
|
|
19
|
+
},
|
|
20
|
+
'cleanPath.bin.stream': {
|
|
21
|
+
type: 1
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// 走p2p流传输(新) or 读取bin文件(旧)
|
|
26
|
+
const shouldDownloadStream = Boolean(ty.p2p.downloadStream && ty.p2p.onStreamPacketReceive);
|
|
27
|
+
export class SweeperP2p extends P2pApi {
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.file = undefined;
|
|
31
|
+
this.downloadType = 1;
|
|
32
|
+
this.fileIndex = -1; // -1 表示所有文件下载完成
|
|
33
|
+
this.albumName = 'ipc_sweeper_robot';
|
|
34
|
+
this.mapBinData = 'map.bin';
|
|
35
|
+
this.navPathBinData = 'navPath.bin';
|
|
36
|
+
this.cleanPathBinData = 'cleanPath.bin';
|
|
37
|
+
this.mapBinStream = 'map.bin.stream';
|
|
38
|
+
this.navPathBinStream = 'navPath.bin.stream';
|
|
39
|
+
this.cleanPathBinStream = 'cleanPath.bin.stream';
|
|
40
|
+
this.cacheDir = ty.env.USER_DATA_PATH;
|
|
41
|
+
this.readingMap = false;
|
|
42
|
+
this.readingClean = false;
|
|
43
|
+
this.readingNav = false;
|
|
44
|
+
this.cacheData = {};
|
|
45
|
+
this.packetTotalMap = new Map([['map.bin', -1], ['cleanPath.bin', -1], ['map.bin.stream', -1], ['cleanPath.bin.stream', -1]]);
|
|
46
|
+
this.packetSerialNumberCacheMap = new Map([['map.bin', -1], ['cleanPath.bin', -1], ['map.bin.stream', -1], ['cleanPath.bin.stream', -1]]);
|
|
47
|
+
this.packetDataCacheMap = new Map([['map.bin', new Map()], ['cleanPath.bin', new Map()], ['map.bin.stream', new Map()], ['cleanPath.bin.stream', new Map()]]);
|
|
48
|
+
}
|
|
49
|
+
setStreamFilePath = () => {
|
|
50
|
+
// this.streamFilePath = this.cacheDir + `/${this.albumName}/${devId}/stream`;
|
|
51
|
+
if (/usr/.test(this.cacheDir)) {
|
|
52
|
+
this.streamFilePath = this.cacheDir;
|
|
53
|
+
} else {
|
|
54
|
+
this.streamFilePath = this.cacheDir + 'usr';
|
|
55
|
+
}
|
|
56
|
+
// 检查存储文件目录是否存在
|
|
57
|
+
// this.initFilePath(this.streamFilePath);
|
|
58
|
+
};
|
|
59
|
+
setDataFilePath = () => {
|
|
60
|
+
// this.dataFilePath = this.cacheDir + `/${this.albumName}/${devId}/data`;
|
|
61
|
+
if (/usr/.test(this.cacheDir)) {
|
|
62
|
+
this.dataFilePath = this.cacheDir;
|
|
63
|
+
} else {
|
|
64
|
+
this.streamFilePath = this.cacheDir + 'usr';
|
|
65
|
+
}
|
|
66
|
+
// 检查存储文件目录是否存在
|
|
67
|
+
// this.initFilePath(this.dataFilePath);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 获取流文件存储地址
|
|
72
|
+
* @param fileName
|
|
73
|
+
* @returns
|
|
74
|
+
*/
|
|
75
|
+
getStreamFilePath = fileName => {
|
|
76
|
+
return this.streamFilePath + '/' + fileName;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 获取完整文件存储地址
|
|
81
|
+
* @param fileName
|
|
82
|
+
* @returns
|
|
83
|
+
*/
|
|
84
|
+
getDataFilePath = fileName => {
|
|
85
|
+
return this.dataFilePath + '/' + fileName;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 创建文件路径文件夹
|
|
90
|
+
* @param filePath
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
93
|
+
createFilePath = filePath => {
|
|
94
|
+
try {
|
|
95
|
+
ty.getFileSystemManager().mkdirSync({
|
|
96
|
+
dirPath: filePath,
|
|
97
|
+
recursive: true
|
|
98
|
+
});
|
|
99
|
+
console.log('mkdirSync success: filePath ==>', filePath);
|
|
100
|
+
return true;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.log('mkdirSync error ==>', e);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 检查当前文件目录是否存在
|
|
109
|
+
* @param filePath
|
|
110
|
+
* @returns
|
|
111
|
+
*/
|
|
112
|
+
checkIfDirIsExist = filePath => {
|
|
113
|
+
return new Promise(resolve => {
|
|
114
|
+
ty.getFileSystemManager().access({
|
|
115
|
+
path: filePath,
|
|
116
|
+
success(params) {
|
|
117
|
+
console.info('file access success ==>', params);
|
|
118
|
+
resolve(true);
|
|
119
|
+
},
|
|
120
|
+
fail(params) {
|
|
121
|
+
console.info('file access fail ==>', params);
|
|
122
|
+
resolve(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 初始化文件目录
|
|
130
|
+
* @param filePath
|
|
131
|
+
* @returns
|
|
132
|
+
*/
|
|
133
|
+
initFilePath = async filePath => {
|
|
134
|
+
const isDir = await this.checkIfDirIsExist(filePath);
|
|
135
|
+
if (!isDir) {
|
|
136
|
+
const result = this.createFilePath(filePath);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
return isDir;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 根据文件名返回对应的fileType
|
|
144
|
+
* @param filename
|
|
145
|
+
*/
|
|
146
|
+
getFileType = filename => {
|
|
147
|
+
if (filename.indexOf('map') !== -1) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
if (filename.indexOf('cleanPath') !== -1) {
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
if (filename.indexOf('navPath') !== -1) {
|
|
154
|
+
return 3;
|
|
155
|
+
}
|
|
156
|
+
return 2;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 设备连接状态发生改变
|
|
161
|
+
* @param data
|
|
162
|
+
*/
|
|
163
|
+
sessionStatusCallback = data => {
|
|
164
|
+
Logger.info('sessionStatusCallback ==>', data);
|
|
165
|
+
if (data) {
|
|
166
|
+
const {
|
|
167
|
+
status
|
|
168
|
+
} = data;
|
|
169
|
+
if (status < 0) {
|
|
170
|
+
Logger.info('receive disconnect notice ==>', status);
|
|
171
|
+
this.reconnectP2p(() => {
|
|
172
|
+
// 重连之后重新开启文件下载, 这里的成功不需要注册断开事件
|
|
173
|
+
this.startObserverSweeperDataByP2P(this.downloadType, this.devId, this.onReceiveMapData, this.onReceivePathData);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 开始进行文件下载
|
|
181
|
+
* @param downloadType
|
|
182
|
+
* 0: 下载断开 1: 持续下载
|
|
183
|
+
*/
|
|
184
|
+
startObserverSweeperDataByP2P = async (downloadType, devId, onReceiveMapData, onReceivePathData) => {
|
|
185
|
+
var _this$file$items;
|
|
186
|
+
if (![0, 1].some(item => item === downloadType)) {
|
|
187
|
+
Logger.warn('download type must be 0 or 1');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.onReceiveMapData = onReceiveMapData;
|
|
191
|
+
this.onReceivePathData = onReceivePathData;
|
|
192
|
+
Logger.info('startObserverSweeperDataByP2P ==>');
|
|
193
|
+
this.downloadType = downloadType;
|
|
194
|
+
this.setDataFilePath(devId);
|
|
195
|
+
this.setStreamFilePath(devId);
|
|
196
|
+
// 先移除监听,再重新注册
|
|
197
|
+
this.removeP2pDownloadEvent();
|
|
198
|
+
this.registerP2pDownloadEvent();
|
|
199
|
+
if (!this.file) {
|
|
200
|
+
//@ts-ignore
|
|
201
|
+
this.file = await this.queryAlbumFileIndexs(this.albumName);
|
|
202
|
+
}
|
|
203
|
+
if (this.file && ((_this$file$items = this.file.items) === null || _this$file$items === void 0 ? void 0 : _this$file$items.length) > 0) {
|
|
204
|
+
if (this.downloadType === 0) {
|
|
205
|
+
const exitFiles = this.queryNeedFiles(this.file.items);
|
|
206
|
+
if (exitFiles.length > 0) {
|
|
207
|
+
if (shouldDownloadStream) {
|
|
208
|
+
// 开启p2p流传输
|
|
209
|
+
this.downloadStream({
|
|
210
|
+
files: exitFiles
|
|
211
|
+
}, this.albumName);
|
|
212
|
+
} else if (await this.initFilePath(this.dataFilePath)) {
|
|
213
|
+
this.downloadFile({
|
|
214
|
+
files: exitFiles
|
|
215
|
+
}, this.albumName, this.dataFilePath);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else if (this.downloadType === 1) {
|
|
219
|
+
const exitFiles = this.queryNeedFiles(this.file.items);
|
|
220
|
+
if (exitFiles.length > 0) {
|
|
221
|
+
if (shouldDownloadStream) {
|
|
222
|
+
// 开启p2p流传输
|
|
223
|
+
this.downloadStream({
|
|
224
|
+
files: exitFiles
|
|
225
|
+
}, this.albumName);
|
|
226
|
+
} else if (await this.initFilePath(this.streamFilePath)) {
|
|
227
|
+
// 每次要下载前都需要先检查文件目录是否存在 防止中间过程被删除掉文件目录
|
|
228
|
+
this.downloadFile({
|
|
229
|
+
files: exitFiles
|
|
230
|
+
}, this.albumName, this.streamFilePath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 注册下载有关的监听
|
|
239
|
+
*/
|
|
240
|
+
registerP2pDownloadEvent = () => {
|
|
241
|
+
if (shouldDownloadStream) {
|
|
242
|
+
// p2p数据流监听
|
|
243
|
+
this.offP2pStreamPacketReceive = this.onP2pStreamPacketReceive(this.p2pStreamPacketReceiveCallback);
|
|
244
|
+
} else {
|
|
245
|
+
// 注册下载完成的监听
|
|
246
|
+
this.offFileDownloadComplete = this.onFileDownloadComplete(this.fileDownloadCompleteCallback);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// this.offDownLoadProgressUpdate = this.onDownloadProgressUpdate(
|
|
250
|
+
// this.fileDownloadProgressCallback
|
|
251
|
+
// );
|
|
252
|
+
// this.offTotalDownLoadProgressUpdate = this.onDownloadTotalProgressUpdate(
|
|
253
|
+
// this.fileTotalDownloadProgressCallback
|
|
254
|
+
// );
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 移除下载有关的监听
|
|
259
|
+
*/
|
|
260
|
+
removeP2pDownloadEvent = () => {
|
|
261
|
+
this.offFileDownloadComplete && this.offFileDownloadComplete();
|
|
262
|
+
this.offP2pStreamPacketReceive && this.offP2pStreamPacketReceive();
|
|
263
|
+
if (shouldDownloadStream) {
|
|
264
|
+
[...this.packetSerialNumberCacheMap.keys()].forEach(key => {
|
|
265
|
+
this.packetSerialNumberCacheMap.set(key, -1);
|
|
266
|
+
});
|
|
267
|
+
[...this.packetTotalMap.keys()].forEach(key => {
|
|
268
|
+
this.packetTotalMap.set(key, -1);
|
|
269
|
+
});
|
|
270
|
+
[...this.packetDataCacheMap.keys()].forEach(key => {
|
|
271
|
+
this.packetDataCacheMap.set(key, new Map());
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
this.cacheData = {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// this.offDownLoadProgressUpdate && this.offDownLoadProgressUpdate();
|
|
278
|
+
// this.offTotalDownLoadProgressUpdate && this.offTotalDownLoadProgressUpdate();
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 单文件下载完成回调
|
|
283
|
+
* @param downloadComplete
|
|
284
|
+
*/
|
|
285
|
+
fileDownloadCompleteCallback = data => {
|
|
286
|
+
if (data) {
|
|
287
|
+
const {
|
|
288
|
+
fileName,
|
|
289
|
+
index
|
|
290
|
+
} = data;
|
|
291
|
+
if (fileName) {
|
|
292
|
+
this.fileIndex = index;
|
|
293
|
+
const path = this.downloadType === 0 ? this.getDataFilePath(fileName) : this.getStreamFilePath(fileName);
|
|
294
|
+
this.readFileFromPath(fileName, path);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* p2p数据流回调
|
|
301
|
+
* @param downloadComplete
|
|
302
|
+
*/
|
|
303
|
+
p2pStreamPacketReceiveCallback = data => {
|
|
304
|
+
const {
|
|
305
|
+
fileName,
|
|
306
|
+
packetData,
|
|
307
|
+
fileSerialNumber,
|
|
308
|
+
packetIndex,
|
|
309
|
+
packetType
|
|
310
|
+
} = data;
|
|
311
|
+
const cachePacketMap = this.packetDataCacheMap.get(fileName);
|
|
312
|
+
const cacheSerialNumber = this.packetSerialNumberCacheMap.get(fileName);
|
|
313
|
+
|
|
314
|
+
// 说明收到了过时包的数据
|
|
315
|
+
if (fileSerialNumber < cacheSerialNumber) return;
|
|
316
|
+
|
|
317
|
+
// 说明收到了新的整包数据
|
|
318
|
+
if (fileSerialNumber > cacheSerialNumber) {
|
|
319
|
+
this.packetSerialNumberCacheMap.set(fileName, fileSerialNumber);
|
|
320
|
+
this.packetTotalMap.set(fileName, -1);
|
|
321
|
+
cachePacketMap.clear();
|
|
322
|
+
}
|
|
323
|
+
if (packetType === 2 || packetType === 3) {
|
|
324
|
+
// 收到了末尾包, packetIndex + 1代表这组包的总数
|
|
325
|
+
this.packetTotalMap.set(fileName, packetIndex + 1);
|
|
326
|
+
}
|
|
327
|
+
const packetTotal = this.packetTotalMap.get(fileName);
|
|
328
|
+
cachePacketMap.set(packetIndex, packetData);
|
|
329
|
+
if (packetTotal > -1) {
|
|
330
|
+
// 说明收到过末尾包了,只需要验证总包数是否吻合
|
|
331
|
+
const packetDatas = [...cachePacketMap.entries()];
|
|
332
|
+
if (packetDatas.length === packetTotal) {
|
|
333
|
+
packetDatas.sort((a, b) => a[0] - b[0]);
|
|
334
|
+
|
|
335
|
+
// 排序 - 组装 - 发出
|
|
336
|
+
const hexValue = packetDatas.map(_ref => {
|
|
337
|
+
let [index, data] = _ref;
|
|
338
|
+
return data;
|
|
339
|
+
}).join('');
|
|
340
|
+
const {
|
|
341
|
+
type
|
|
342
|
+
} = FILE_NAME_MAP[fileName];
|
|
343
|
+
if (this.cacheData[type] !== hexValue) {
|
|
344
|
+
if (type === 0) {
|
|
345
|
+
this.onReceiveMapData(hexValue);
|
|
346
|
+
}
|
|
347
|
+
if (type === 1) {
|
|
348
|
+
this.onReceivePathData(hexValue);
|
|
349
|
+
}
|
|
350
|
+
this.cacheData[type] = hexValue;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 单文件下载完成回调
|
|
358
|
+
* @param data
|
|
359
|
+
*/
|
|
360
|
+
fileDownloadProgressCallback = () => {
|
|
361
|
+
// console.log('fileDownloadProgressCallback', data);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 多文件下载进度监听
|
|
366
|
+
* @param data
|
|
367
|
+
*/
|
|
368
|
+
fileTotalDownloadProgressCallback = () => {
|
|
369
|
+
// console.log('fileTotalDownloadProgressCallback', data );
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 从指定的文件路径获取文件
|
|
374
|
+
* @param fileName
|
|
375
|
+
* @param filePath
|
|
376
|
+
*/
|
|
377
|
+
readFileFromPath = (fileName, filePath) => {
|
|
378
|
+
if (!this.setReading(fileName)) return;
|
|
379
|
+
try {
|
|
380
|
+
ty.getFileSystemManager().readFile({
|
|
381
|
+
filePath,
|
|
382
|
+
encoding: 'base64',
|
|
383
|
+
position: 0,
|
|
384
|
+
success: params => {
|
|
385
|
+
this.resetReading(fileName);
|
|
386
|
+
const bytes = Base64.toByteArray(params.data);
|
|
387
|
+
const hexValue = _(bytes).map(d => padStart(d.toString(16), 2, '0')).value().join('');
|
|
388
|
+
const type = this.getFileType(fileName);
|
|
389
|
+
if (this.cacheData[type] !== hexValue) {
|
|
390
|
+
if (hexValue.length === 0) {
|
|
391
|
+
Logger.warn('receive empty data');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (type === 0) {
|
|
395
|
+
this.onReceiveMapData(hexValue);
|
|
396
|
+
}
|
|
397
|
+
if (type === 1) {
|
|
398
|
+
this.onReceivePathData(hexValue);
|
|
399
|
+
}
|
|
400
|
+
this.cacheData[type] = hexValue;
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
fail: e => {
|
|
404
|
+
Logger.warn('readFileFromPath failed ==>', e);
|
|
405
|
+
this.resetReading(fileName);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
} catch (e) {
|
|
409
|
+
this.resetReading(fileName);
|
|
410
|
+
Logger.error('readFileFromPath ==>', e);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
setReading = fileName => {
|
|
414
|
+
if (fileName.indexOf('map') !== -1) {
|
|
415
|
+
if (this.readingMap === true) return false;
|
|
416
|
+
this.readingMap = true;
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
if (fileName.indexOf('cleanPath') !== -1) {
|
|
420
|
+
if (this.readingClean === true) return false;
|
|
421
|
+
this.readingClean = true;
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (fileName.indexOf('navPath') !== -1) {
|
|
425
|
+
if (this.readingNav === true) return false;
|
|
426
|
+
this.readingNav = true;
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
return true;
|
|
430
|
+
};
|
|
431
|
+
resetReading = fileName => {
|
|
432
|
+
if (fileName.indexOf('map') !== -1) {
|
|
433
|
+
this.readingMap = false;
|
|
434
|
+
} else if (fileName.indexOf('cleanPath') !== -1) {
|
|
435
|
+
this.readingClean = false;
|
|
436
|
+
} else if (fileName.indexOf('navPath') !== -1) {
|
|
437
|
+
this.readingNav = false;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* 查询返回的文件中是否包含了所需文件
|
|
443
|
+
* @param fileList
|
|
444
|
+
*/
|
|
445
|
+
queryNeedFiles = fileList => {
|
|
446
|
+
const exitFiles = [];
|
|
447
|
+
const streamPattern = /bin.stream$/;
|
|
448
|
+
const dataPattern = /bin$/;
|
|
449
|
+
fileList && fileList.forEach(item => {
|
|
450
|
+
if (this.downloadType === 0) {
|
|
451
|
+
if (dataPattern.test(item.filename)) {
|
|
452
|
+
exitFiles.push(item.filename);
|
|
453
|
+
}
|
|
454
|
+
} else if (this.downloadType === 1) {
|
|
455
|
+
if (streamPattern.test(item.filename)) {
|
|
456
|
+
exitFiles.push(item.filename);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return exitFiles;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 停止文件下载
|
|
465
|
+
*/
|
|
466
|
+
stopObserverSweeperDataByP2P = async () => {
|
|
467
|
+
try {
|
|
468
|
+
this.removeP2pDownloadEvent();
|
|
469
|
+
const isCancel = await this.cancelDownloadTask();
|
|
470
|
+
Logger.info('cancelDownloadTask ==>', isCancel);
|
|
471
|
+
return isCancel;
|
|
472
|
+
} catch (e) {
|
|
473
|
+
Logger.error('stopObserverSweeperDataByP2P occur error ==>', e);
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const sweeperP2pInstance = new SweeperP2p();
|
|
479
|
+
export default sweeperP2pInstance;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const useP2PDataStream: (devId: string, onReceiveMapData: (data: string) => void, onReceivePathData: (data: string) => void) => void;
|
|
2
|
+
declare const StreamDataNotificationCenter: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
|
|
3
|
+
export { useP2PDataStream, StreamDataNotificationCenter };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import mitt from 'mitt';
|
|
3
|
+
import { SweeperP2pInstance } from '@/api';
|
|
4
|
+
import { Logger } from './utils';
|
|
5
|
+
const useP2PDataStream = (devId, onReceiveMapData, onReceivePathData) => {
|
|
6
|
+
const isInit = useRef(null);
|
|
7
|
+
const offSessionStatusChange = useRef(null);
|
|
8
|
+
const isAppOnBackground = useRef(false);
|
|
9
|
+
const timer = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (devId.startsWith('vdevo')) {
|
|
12
|
+
Logger.warn('virtual device cannot use p2p');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
isInitP2p();
|
|
16
|
+
onEnterBackground();
|
|
17
|
+
onEnterForeground();
|
|
18
|
+
return () => {
|
|
19
|
+
unmount();
|
|
20
|
+
timer && clearInterval(timer);
|
|
21
|
+
};
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* p2p连接
|
|
26
|
+
*/
|
|
27
|
+
const isInitP2p = async () => {
|
|
28
|
+
Logger.info('hooks has been started initP2p');
|
|
29
|
+
isInit.current = await SweeperP2pInstance.initP2pSdk(devId);
|
|
30
|
+
if (isInit.current) {
|
|
31
|
+
SweeperP2pInstance.connectDevice(() => {
|
|
32
|
+
SweeperP2pInstance.startObserverSweeperDataByP2P(1, devId, onReceiveMapData, onReceivePathData);
|
|
33
|
+
offSessionStatusChange.current = SweeperP2pInstance.onSessionStatusChange(SweeperP2pInstance.sessionStatusCallback);
|
|
34
|
+
}, () => {
|
|
35
|
+
SweeperP2pInstance.reconnectP2p(() => {
|
|
36
|
+
SweeperP2pInstance.startObserverSweeperDataByP2P(1, devId, onReceiveMapData, onReceivePathData);
|
|
37
|
+
// 这里失败重连需要注册断开重连的事件
|
|
38
|
+
offSessionStatusChange.current = SweeperP2pInstance.onSessionStatusChange(SweeperP2pInstance.sessionStatusCallback);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 进入后台时断开P2P连接
|
|
46
|
+
*/
|
|
47
|
+
const onEnterBackground = () => {
|
|
48
|
+
ty.onAppHide(() => {
|
|
49
|
+
Logger.info('hooks onAppHide');
|
|
50
|
+
isAppOnBackground.current = true;
|
|
51
|
+
if (isInit.current) {
|
|
52
|
+
// 判断进入后台之后,维持定时器,如果进入后台超过2分钟, 则断开P2P
|
|
53
|
+
timer.current = setTimeout(() => {
|
|
54
|
+
Logger.info('background timer has been exe');
|
|
55
|
+
if (isAppOnBackground.current) {
|
|
56
|
+
unmount();
|
|
57
|
+
}
|
|
58
|
+
clearTimeout(timer.current);
|
|
59
|
+
timer.current = null;
|
|
60
|
+
}, 2 * 60 * 1000);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* p2p断开
|
|
67
|
+
*/
|
|
68
|
+
const unmount = async () => {
|
|
69
|
+
Logger.info('hooks has been started unmount');
|
|
70
|
+
isInit.current = false;
|
|
71
|
+
if (offSessionStatusChange.current) {
|
|
72
|
+
offSessionStatusChange.current();
|
|
73
|
+
offSessionStatusChange.current = null;
|
|
74
|
+
}
|
|
75
|
+
await SweeperP2pInstance.stopObserverSweeperDataByP2P();
|
|
76
|
+
await SweeperP2pInstance.deInitP2PSDK();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 进入前台时开启P2P连接
|
|
81
|
+
*/
|
|
82
|
+
const onEnterForeground = () => {
|
|
83
|
+
ty.onAppShow(() => {
|
|
84
|
+
Logger.info('hooks onAppShow');
|
|
85
|
+
isAppOnBackground.current = false;
|
|
86
|
+
if (!isInit.current) {
|
|
87
|
+
isInitP2p();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const StreamDataNotificationCenter = mitt();
|
|
93
|
+
export { useP2PDataStream, StreamDataNotificationCenter };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare class Logger {
|
|
2
|
+
options: {
|
|
3
|
+
performance: string;
|
|
4
|
+
error: string;
|
|
5
|
+
info: string;
|
|
6
|
+
warn: string;
|
|
7
|
+
};
|
|
8
|
+
log(color: string, title: string, ...args: any): void;
|
|
9
|
+
performance(title: string, ...args: any): void;
|
|
10
|
+
warn(title: string, ...args: any): void;
|
|
11
|
+
error(title: string, ...args: any): void;
|
|
12
|
+
info(title: string, ...args: any): void;
|
|
13
|
+
}
|
|
14
|
+
declare const logger: Logger;
|
|
15
|
+
export default logger;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
2
|
+
class Logger {
|
|
3
|
+
options = {
|
|
4
|
+
performance: '#00cca3',
|
|
5
|
+
error: '#f81c1c',
|
|
6
|
+
info: '#5091f3',
|
|
7
|
+
warn: '#ffaa00'
|
|
8
|
+
};
|
|
9
|
+
log(color, title) {
|
|
10
|
+
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
|
|
11
|
+
args[_key - 2] = arguments[_key];
|
|
12
|
+
}
|
|
13
|
+
console.log(`%c 【Robot Data Stream Log】: ${title}`, `background: ${color}; color: #FFFFFF; font-size: 20px`, ...args);
|
|
14
|
+
}
|
|
15
|
+
performance(title) {
|
|
16
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
17
|
+
args[_key2 - 1] = arguments[_key2];
|
|
18
|
+
}
|
|
19
|
+
this.log(this.options.performance, title, ...args);
|
|
20
|
+
}
|
|
21
|
+
warn(title) {
|
|
22
|
+
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
23
|
+
args[_key3 - 1] = arguments[_key3];
|
|
24
|
+
}
|
|
25
|
+
this.log(this.options.warn, title, ...args);
|
|
26
|
+
}
|
|
27
|
+
error(title) {
|
|
28
|
+
if (title === 'system error') {
|
|
29
|
+
debugger;
|
|
30
|
+
}
|
|
31
|
+
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
|
32
|
+
args[_key4 - 1] = arguments[_key4];
|
|
33
|
+
}
|
|
34
|
+
this.log(this.options.error, title, ...args);
|
|
35
|
+
}
|
|
36
|
+
info(title) {
|
|
37
|
+
for (var _len5 = arguments.length, args = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
|
|
38
|
+
args[_key5 - 1] = arguments[_key5];
|
|
39
|
+
}
|
|
40
|
+
this.log(this.options.info, title, ...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const logger = new Logger();
|
|
44
|
+
export default logger;
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ray-js/robot-data-stream",
|
|
3
|
+
"version": "0.0.1-beta-1",
|
|
4
|
+
"description": "扫地机P2P数据流标准化组件",
|
|
5
|
+
"main": "lib/index",
|
|
6
|
+
"files": [
|
|
7
|
+
"lib"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"types": "lib/index.d.ts",
|
|
11
|
+
"maintainers": [
|
|
12
|
+
"tuya_npm",
|
|
13
|
+
{
|
|
14
|
+
"name": "tuyafe",
|
|
15
|
+
"email": "tuyafe@tuya.com"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
|
|
20
|
+
"build": "patch-package && ray build --type=component",
|
|
21
|
+
"watch": "ray start --type=component --output ./example/src/lib",
|
|
22
|
+
"build:tuya": "ray build ./example",
|
|
23
|
+
"build:wechat": "ray build ./example --target=wechat",
|
|
24
|
+
"build:web": "ray build ./example --target=web",
|
|
25
|
+
"build:native": "ray build ./example --target=native",
|
|
26
|
+
"start:native": "ray start ./example -t native --verbose",
|
|
27
|
+
"start:tuya": "ray start ./example",
|
|
28
|
+
"start:wechat": "ray start ./example -t wechat --verbose",
|
|
29
|
+
"start:web": "ray start ./example -t web",
|
|
30
|
+
"prepublishOnly": "yarn build",
|
|
31
|
+
"release-it": "standard-version"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@ray-js/ray": "^1.5.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"clsx": "^1.2.1",
|
|
38
|
+
"lodash-es": "^4.17.21",
|
|
39
|
+
"moment": "^2.30.1",
|
|
40
|
+
"mitt": "^3.0.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@commitlint/cli": "^7.2.1",
|
|
44
|
+
"@commitlint/config-conventional": "^9.0.1",
|
|
45
|
+
"@ray-js/cli": "^1.5.20",
|
|
46
|
+
"@ray-js/ray": "^1.5.0",
|
|
47
|
+
"core-js": "^3.19.1",
|
|
48
|
+
"eslint-config-tuya-panel": "^0.4.2",
|
|
49
|
+
"husky": "^1.2.0",
|
|
50
|
+
"lint-staged": "^10.2.11",
|
|
51
|
+
"patch-package": "^8.0.0",
|
|
52
|
+
"standard-version": "9.3.2"
|
|
53
|
+
},
|
|
54
|
+
"resolutions": {
|
|
55
|
+
"@ray-js/builder-mp": "1.4.15"
|
|
56
|
+
},
|
|
57
|
+
"husky": {
|
|
58
|
+
"hooks": {
|
|
59
|
+
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS --config commitlint.config.js",
|
|
60
|
+
"pre-commit": "lint-staged"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"lint-staged": {
|
|
64
|
+
"*.{ts,tsx,js,jsx}": [
|
|
65
|
+
"eslint --fix",
|
|
66
|
+
"git add"
|
|
67
|
+
],
|
|
68
|
+
"*.{json,md,yml,yaml}": [
|
|
69
|
+
"prettier --write",
|
|
70
|
+
"git add"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|