rvis-aiui-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/components/image/index.ink +44 -0
- package/components/list/index.ink +198 -0
- package/components/list/readme.md +71 -0
- package/components/markdown/index.ink +109 -0
- package/components/markdown/parser.js +149 -0
- package/components/markdown/readme.md +50 -0
- package/components/model-list/index.ink +181 -0
- package/components/model-list/readme.md +111 -0
- package/components/paragraph/index.ink +61 -0
- package/components/paragraph/readme.md +17 -0
- package/components/table/index.ink +272 -0
- package/components/table/readme.md +83 -0
- package/package.json +35 -0
- package/sdk/README.md +1013 -0
- package/sdk/api-map.js +25 -0
- package/sdk/core/client.js +641 -0
- package/sdk/core/constants.js +10 -0
- package/sdk/core/transport.js +55 -0
- package/sdk/index.js +19 -0
- package/sdk/modules/audio/index.js +177 -0
- package/sdk/modules/audio/readme.md +136 -0
- package/sdk/modules/camera/index.js +380 -0
- package/sdk/modules/camera/readme.md +302 -0
- package/sdk/modules/device-context/index.js +206 -0
- package/sdk/modules/device-context/readme.md +243 -0
- package/sdk/modules/face/index.js +108 -0
- package/sdk/modules/face/readme.md +196 -0
- package/sdk/modules/motion/index.js +116 -0
- package/sdk/modules/motion/readme.md +148 -0
- package/sdk/modules/notification/index.js +192 -0
- package/sdk/modules/notification/readme.md +175 -0
- package/sdk/modules/offline-command/index.js +265 -0
- package/sdk/modules/offline-command/readme.md +143 -0
- package/sdk/modules/screen/index.js +64 -0
- package/sdk/modules/screen/readme.md +110 -0
- package/sdk/modules/tts/index.js +75 -0
- package/sdk/modules/tts/readme.md +162 -0
- package/sdk/utils/api-builder.js +16 -0
- package/sdk/utils/case.js +19 -0
- package/sdk/utils/errors.js +32 -0
- package/sdk/utils/events.js +22 -0
- package/sdk/utils/message.js +63 -0
- package/sdk/utils/request-id.js +18 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Face API
|
|
2
|
+
|
|
3
|
+
Face 模块通过 `glass3.face` 暴露持续人脸识别能力。
|
|
4
|
+
|
|
5
|
+
页面必须在 `onMessage` 中调用 `glass3.handleMessage(messageEvent)`,识别事件才能被关联到对应请求。
|
|
6
|
+
|
|
7
|
+
## API 列表
|
|
8
|
+
|
|
9
|
+
| API | 说明 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `face.startRecognize` | 开启人脸识别并持续接收结果。 |
|
|
12
|
+
| `face.stopRecognize` | 停止指定的人脸识别请求。 |
|
|
13
|
+
|
|
14
|
+
## `face.startRecognize`
|
|
15
|
+
|
|
16
|
+
该接口没有业务参数。由于第二个位置是调用选项,注册 `onEvent` 时第一个参数传空对象:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const recognition = await glass3.face.startRecognize(
|
|
20
|
+
{},
|
|
21
|
+
{
|
|
22
|
+
onEvent(eventResult) {
|
|
23
|
+
console.log('face event:', JSON.stringify(eventResult));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
不需要事件回调时也可以直接调用:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const recognition = await glass3.face.startRecognize();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
SDK 发送给 Native 的 `args` 始终为 `{}`。传入非空参数会抛出参数错误。
|
|
36
|
+
|
|
37
|
+
### 返回值
|
|
38
|
+
|
|
39
|
+
请求通过校验后,Promise 在收到以下任一消息时完成:
|
|
40
|
+
|
|
41
|
+
- `kind: "event"` 且 `data.result.state: "started"`;
|
|
42
|
+
- `kind: "response"` 且 `result.state: "started"`。
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
{
|
|
46
|
+
requestId: '识别启动 UUID',
|
|
47
|
+
ok: true
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Promise 完成后,识别结果仍会使用同一个 requestId 持续进入 `onEvent`。response 不会作为 event 传给 `onEvent`。
|
|
52
|
+
|
|
53
|
+
### 事件
|
|
54
|
+
|
|
55
|
+
`onEvent` 只接收 Native 消息中的 `message.data.result`。所有下划线字段都会递归转换为驼峰。
|
|
56
|
+
|
|
57
|
+
启动事件:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
{ ok: true, state: 'started' }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
识别结果示例:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
{
|
|
67
|
+
label: '330…1234|张三|职工',
|
|
68
|
+
labelParts: ['330…1234', '张三', '职工'],
|
|
69
|
+
personId: '330…1234',
|
|
70
|
+
personIdCard: '330…1234',
|
|
71
|
+
personName: '张三',
|
|
72
|
+
personType: '职工',
|
|
73
|
+
similarity: 0.97,
|
|
74
|
+
regScore: 97,
|
|
75
|
+
trackId: 3,
|
|
76
|
+
sdkFrameId: 1024,
|
|
77
|
+
faceModel: {
|
|
78
|
+
frameWidth: 1080,
|
|
79
|
+
frameHeight: 1440,
|
|
80
|
+
frameId: 1024,
|
|
81
|
+
trackId: 3,
|
|
82
|
+
faceScore: 0.99,
|
|
83
|
+
iqaScore: 0.8,
|
|
84
|
+
rect: {
|
|
85
|
+
left: 100,
|
|
86
|
+
top: 200,
|
|
87
|
+
right: 300,
|
|
88
|
+
bottom: 400
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
异常事件:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
{
|
|
98
|
+
ok: false,
|
|
99
|
+
errorCode: 500,
|
|
100
|
+
error: 'recognition failed'
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
启动前收到错误会拒绝 Promise。启动后的错误会先进入 `onEvent`,随后结束本次事件监听。
|
|
105
|
+
|
|
106
|
+
原 start request 收到以下事件时,SDK 会在透传事件后清理监听:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
{ ok: true, state: 'stopped' }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Native 映射:`rokid.tools / invoke / faceRecognize / start`。
|
|
113
|
+
|
|
114
|
+
## `face.stopRecognize`
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const result = await glass3.face.stopRecognize({
|
|
118
|
+
requestId: recognition.requestId
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| `requestId` | `string` | 是 | `startRecognize` 返回的识别启动 ID。只允许传该字段。 |
|
|
125
|
+
|
|
126
|
+
SDK 会将其转换为 Native 参数:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
{
|
|
130
|
+
targetRequestId: recognition.requestId
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
stop 调用会生成自己的独立 requestId,并等待该 stop request 的以下任一回执:
|
|
135
|
+
|
|
136
|
+
- `kind: "event"` 且 `data.result.state: "stopped"`;
|
|
137
|
+
- `kind: "response"` 且 `result.state: "stopped"`。
|
|
138
|
+
|
|
139
|
+
SDK 会将两种消息结构统一为:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
{
|
|
143
|
+
requestId: '本次 stop UUID',
|
|
144
|
+
ok: true,
|
|
145
|
+
state: 'stopped'
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
stop 调用和 start 的事件监听相互独立。Native 还会向原 start request 发送 `state: "stopped"`,由 start 监听自行结束。
|
|
150
|
+
|
|
151
|
+
Native 映射:`rokid.tools / invoke / faceRecognize / stop`。
|
|
152
|
+
|
|
153
|
+
## 完整示例
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import glass3 from 'rvis-aiui-kit';
|
|
157
|
+
|
|
158
|
+
let recognizeRequestId = null;
|
|
159
|
+
|
|
160
|
+
export default {
|
|
161
|
+
onMessage(messageEvent) {
|
|
162
|
+
glass3.handleMessage(messageEvent);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async start() {
|
|
166
|
+
const result = await glass3.face.startRecognize({}, {
|
|
167
|
+
onEvent(eventResult) {
|
|
168
|
+
console.log('face event:', JSON.stringify(eventResult));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
recognizeRequestId = result.requestId;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
stop() {
|
|
175
|
+
if (!recognizeRequestId) return;
|
|
176
|
+
|
|
177
|
+
glass3.face.stopRecognize({
|
|
178
|
+
requestId: recognizeRequestId
|
|
179
|
+
}).catch(error => {
|
|
180
|
+
if (error.code !== 'CALL_DISPOSED') {
|
|
181
|
+
console.error('stopRecognize failed:', JSON.stringify(error));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
recognizeRequestId = null;
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
onUnload() {
|
|
188
|
+
this.stop();
|
|
189
|
+
glass3.dispose();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 错误
|
|
195
|
+
|
|
196
|
+
参数错误会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。Native 事件返回 `ok: false` 时,尚未完成的 Promise 会以 `NATIVE_EVENT_ERROR` 拒绝。
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPLETION_ACCEPTED,
|
|
3
|
+
COMPLETION_EVENT
|
|
4
|
+
} from '../../core/constants.js';
|
|
5
|
+
import {
|
|
6
|
+
snakeStringToCamelCase,
|
|
7
|
+
snakeToCamelCase
|
|
8
|
+
} from '../../utils/case.js';
|
|
9
|
+
import { Glass3Error } from '../../utils/errors.js';
|
|
10
|
+
|
|
11
|
+
function createParameterError(message) {
|
|
12
|
+
return new Glass3Error(message, {
|
|
13
|
+
code: 'INVALID_PARAMS',
|
|
14
|
+
stage: 'params',
|
|
15
|
+
namespace: 'rokid.tools',
|
|
16
|
+
method: 'invoke'
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildStartDetectArgs(args) {
|
|
21
|
+
if (args === undefined || args === null) return {};
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
typeof args !== 'object' ||
|
|
25
|
+
Array.isArray(args) ||
|
|
26
|
+
Object.keys(args).length !== 0
|
|
27
|
+
) {
|
|
28
|
+
throw createParameterError('motion.startDetect does not accept parameters');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildStopDetectArgs(args) {
|
|
35
|
+
if (
|
|
36
|
+
!args ||
|
|
37
|
+
typeof args !== 'object' ||
|
|
38
|
+
Array.isArray(args) ||
|
|
39
|
+
typeof args.requestId !== 'string' ||
|
|
40
|
+
!args.requestId.trim()
|
|
41
|
+
) {
|
|
42
|
+
throw createParameterError('motion.stopDetect requires a requestId');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const inputFields = Object.keys(args);
|
|
46
|
+
if (inputFields.length !== 1 || inputFields[0] !== 'requestId') {
|
|
47
|
+
throw createParameterError('motion.stopDetect only supports requestId');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
targetRequestId: args.requestId
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function transformMotionEventResult(result) {
|
|
56
|
+
const converted = snakeToCamelCase(result);
|
|
57
|
+
if (!converted || typeof converted !== 'object') return converted;
|
|
58
|
+
|
|
59
|
+
if (typeof converted.motionState === 'string') {
|
|
60
|
+
converted.motionState = snakeStringToCamelCase(converted.motionState);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof converted.previous === 'string') {
|
|
64
|
+
converted.previous = snakeStringToCamelCase(converted.previous);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return converted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const motionStartDetect = {
|
|
71
|
+
publicModule: 'motion',
|
|
72
|
+
publicMethod: 'startDetect',
|
|
73
|
+
|
|
74
|
+
namespace: 'rokid.tools',
|
|
75
|
+
method: 'invoke',
|
|
76
|
+
toolName: 'motionDetect',
|
|
77
|
+
toolAction: 'start',
|
|
78
|
+
|
|
79
|
+
completion: COMPLETION_ACCEPTED,
|
|
80
|
+
eventNamespace: 'rokid.tools',
|
|
81
|
+
eventName: 'toolResult',
|
|
82
|
+
eventToolActions: ['start', 'stop'],
|
|
83
|
+
buildArgs: buildStartDetectArgs,
|
|
84
|
+
transformEventResult: transformMotionEventResult,
|
|
85
|
+
isTerminalResult(result) {
|
|
86
|
+
return Boolean(
|
|
87
|
+
result && (result.ok === false || result.state === 'stopped')
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const motionStopDetect = {
|
|
93
|
+
publicModule: 'motion',
|
|
94
|
+
publicMethod: 'stopDetect',
|
|
95
|
+
|
|
96
|
+
namespace: 'rokid.tools',
|
|
97
|
+
method: 'invoke',
|
|
98
|
+
toolName: 'motionDetect',
|
|
99
|
+
toolAction: 'stop',
|
|
100
|
+
|
|
101
|
+
completion: COMPLETION_EVENT,
|
|
102
|
+
eventNamespace: 'rokid.tools',
|
|
103
|
+
eventName: 'toolResult',
|
|
104
|
+
terminalStates: ['stopped'],
|
|
105
|
+
responseCompletionStates: ['stopped'],
|
|
106
|
+
buildArgs: buildStopDetectArgs,
|
|
107
|
+
transformEventResult: snakeToCamelCase,
|
|
108
|
+
selectEventResult(eventData) {
|
|
109
|
+
return Object.assign(
|
|
110
|
+
{ requestId: eventData.requestId },
|
|
111
|
+
snakeToCamelCase(eventData.result)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default [motionStartDetect, motionStopDetect];
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Motion API
|
|
2
|
+
|
|
3
|
+
Motion 模块通过 `glass3.motion` 暴露运动状态检测能力。
|
|
4
|
+
|
|
5
|
+
页面必须在 `onMessage` 中调用 `glass3.handleMessage(messageEvent)`,状态变化事件才能被关联到对应请求。
|
|
6
|
+
|
|
7
|
+
| API | 说明 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `motion.startDetect` | 开启运动检测并持续接收状态变化。 |
|
|
10
|
+
| `motion.stopDetect` | 停止指定的运动检测请求。 |
|
|
11
|
+
|
|
12
|
+
## `motion.startDetect`
|
|
13
|
+
|
|
14
|
+
开启运动状态检测。该接口没有业务参数,注册 `onEvent` 时第一个参数传空对象:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const detection = await glass3.motion.startDetect(
|
|
18
|
+
{},
|
|
19
|
+
{
|
|
20
|
+
onEvent(eventResult) {
|
|
21
|
+
console.log('motion event:', JSON.stringify(eventResult));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
不需要事件回调时也可以直接调用:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const detection = await glass3.motion.startDetect();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
SDK 发送给 Native 的 `args` 始终为 `{}`。传入非空参数会抛出参数错误。
|
|
34
|
+
|
|
35
|
+
### 返回值
|
|
36
|
+
|
|
37
|
+
协议没有单独的启动确认事件,因此 Promise 在 fetch 返回 Native 已受理后完成,不等待第一个运动状态事件:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
{
|
|
41
|
+
requestId: '运动检测启动 UUID',
|
|
42
|
+
ok: true
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 状态事件
|
|
47
|
+
|
|
48
|
+
同一个 requestId 的状态变化会持续进入 `onEvent`。回调只接收 Native 消息中的 `message.data.result`:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
{
|
|
52
|
+
ok: true,
|
|
53
|
+
motionState: 'walk',
|
|
54
|
+
previous: 'still',
|
|
55
|
+
moving: true,
|
|
56
|
+
timestamp: 123456789
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
运动状态包括:
|
|
61
|
+
|
|
62
|
+
- `still`
|
|
63
|
+
- `walk`
|
|
64
|
+
- `run`
|
|
65
|
+
- `headTurnLeft`
|
|
66
|
+
- `headTurnRight`
|
|
67
|
+
|
|
68
|
+
SDK 会递归地把事件结果中的下划线字段名转换成驼峰,例如 `motion_state` 转换为 `motionState`、`error_code` 转换为 `errorCode`。`motionState` 和 `previous` 的状态值也会转换成驼峰,例如 Native 的 `head_turn_left` 会作为 `headTurnLeft` 进入 `onEvent`。其他字符串值保持不变。
|
|
69
|
+
|
|
70
|
+
Native 只在状态发生变化时上报。首个事件的 `previous` 为 `unknown`,SDK 不对事件进行去重或合并。
|
|
71
|
+
|
|
72
|
+
### Native 映射
|
|
73
|
+
|
|
74
|
+
| 字段 | 值 |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `namespace` | `rokid.tools` |
|
|
77
|
+
| `method` | `invoke` |
|
|
78
|
+
| `toolName` | `motionDetect` |
|
|
79
|
+
| `toolAction` | `start` |
|
|
80
|
+
|
|
81
|
+
请求协议版本为字符串 `"2.0.0"`。
|
|
82
|
+
|
|
83
|
+
### 错误和监听释放
|
|
84
|
+
|
|
85
|
+
- 参数不是空对象时,Promise 会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。
|
|
86
|
+
- fetch 失败、响应协议不正确或 Native 未受理时,Promise 会按通用 RPC 错误规则抛出 `Glass3Error`。
|
|
87
|
+
- Promise 已受理后收到 `{ ok: false }` 事件时,该结果会先进入 `onEvent`,随后 SDK 清理本次事件监听。
|
|
88
|
+
- 未传 `onEvent` 时,SDK 不保留事件监听。
|
|
89
|
+
- 如果原 start requestId 收到 `state: "stopped"` 终止事件,该结果会先进入 `onEvent`,随后 SDK 清理本次事件监听。
|
|
90
|
+
|
|
91
|
+
## `motion.stopDetect`
|
|
92
|
+
|
|
93
|
+
停止一次由 `motion.startDetect` 发起的运动检测:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const result = await glass3.motion.stopDetect({
|
|
97
|
+
requestId: detection.requestId
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 参数
|
|
102
|
+
|
|
103
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| `requestId` | `string` | 是 | `motion.startDetect` 返回的启动 requestId。只允许传该字段。 |
|
|
106
|
+
|
|
107
|
+
SDK 会将参数转换成 Native 的 `args`:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
{
|
|
111
|
+
targetRequestId: detection.requestId
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
stop 调用会生成自己的独立 requestId,不会复用 `targetRequestId`。
|
|
116
|
+
|
|
117
|
+
### 返回值
|
|
118
|
+
|
|
119
|
+
请求通过校验后,Promise 在收到 stop 自己的 requestId 对应的以下任一消息时完成:
|
|
120
|
+
|
|
121
|
+
- `kind: "event"` 且 `data.result.state: "stopped"`;
|
|
122
|
+
- `kind: "response"` 且 `result.state: "stopped"`。
|
|
123
|
+
|
|
124
|
+
SDK 会将两种消息结构统一为:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
{
|
|
128
|
+
requestId: '本次 stop 调用的 UUID',
|
|
129
|
+
ok: true,
|
|
130
|
+
state: 'stopped'
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Native 映射
|
|
135
|
+
|
|
136
|
+
| 字段 | 值 |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `namespace` | `rokid.tools` |
|
|
139
|
+
| `method` | `invoke` |
|
|
140
|
+
| `toolName` | `motionDetect` |
|
|
141
|
+
| `toolAction` | `stop` |
|
|
142
|
+
|
|
143
|
+
stop 与 start 的 SDK 调用相互独立。stop 只等待自己的回执,不主动处理 start 的 Promise 或回调。
|
|
144
|
+
|
|
145
|
+
### 错误
|
|
146
|
+
|
|
147
|
+
- requestId 缺失、不是非空字符串或包含其他参数时,Promise 会抛出错误码为 `INVALID_PARAMS` 的 `Glass3Error`。
|
|
148
|
+
- fetch 失败、Native 未受理或事件返回 `ok: false` 时,Promise 会按通用 RPC 错误规则抛出 `Glass3Error`。
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { COMPLETION_EVENT } from '../../core/constants.js';
|
|
2
|
+
import { Glass3Error } from '../../utils/errors.js';
|
|
3
|
+
|
|
4
|
+
const ALLOWED_FIELDS = [
|
|
5
|
+
'title',
|
|
6
|
+
'text',
|
|
7
|
+
'level',
|
|
8
|
+
'position',
|
|
9
|
+
'durationMs',
|
|
10
|
+
'animation',
|
|
11
|
+
'resident'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const LEVELS = ['active', 'critical'];
|
|
15
|
+
|
|
16
|
+
const POSITIONS = [
|
|
17
|
+
'topLeft',
|
|
18
|
+
'topCenter',
|
|
19
|
+
'topRight',
|
|
20
|
+
'bottomLeft',
|
|
21
|
+
'bottomCenter',
|
|
22
|
+
'bottomRight'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const ANIMATIONS = [
|
|
26
|
+
'idle',
|
|
27
|
+
'blink',
|
|
28
|
+
'thinking',
|
|
29
|
+
'calm',
|
|
30
|
+
'nod',
|
|
31
|
+
'bow',
|
|
32
|
+
'salute',
|
|
33
|
+
'photo',
|
|
34
|
+
'face',
|
|
35
|
+
'scan',
|
|
36
|
+
'success',
|
|
37
|
+
'listening',
|
|
38
|
+
'speaking',
|
|
39
|
+
'enter',
|
|
40
|
+
'exit'
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function createParameterError(message) {
|
|
44
|
+
return new Glass3Error(message, {
|
|
45
|
+
code: 'INVALID_PARAMS',
|
|
46
|
+
stage: 'params',
|
|
47
|
+
namespace: 'rokid.tools',
|
|
48
|
+
method: 'invoke'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeText(input, name) {
|
|
53
|
+
if (input[name] === undefined) return '';
|
|
54
|
+
if (typeof input[name] !== 'string') {
|
|
55
|
+
throw createParameterError(`notification.show ${name} must be a string`);
|
|
56
|
+
}
|
|
57
|
+
return input[name];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildNotificationArgs(args) {
|
|
61
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
62
|
+
throw createParameterError('notification.show parameters must be an object');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const inputFields = Object.keys(args);
|
|
66
|
+
inputFields.forEach(name => {
|
|
67
|
+
if (ALLOWED_FIELDS.indexOf(name) === -1) {
|
|
68
|
+
throw createParameterError(`notification.show does not support ${name}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const title = normalizeText(args, 'title');
|
|
73
|
+
const text = normalizeText(args, 'text');
|
|
74
|
+
const hasTitle = args.title !== undefined;
|
|
75
|
+
const hasText = args.text !== undefined;
|
|
76
|
+
if (!hasTitle && !hasText) {
|
|
77
|
+
throw createParameterError(
|
|
78
|
+
'notification.show requires title or text'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let level = args.level === undefined ? 'active' : args.level;
|
|
83
|
+
if (LEVELS.indexOf(level) === -1) {
|
|
84
|
+
console.warn('[glass3] notification.show unsupported level; using active:', level);
|
|
85
|
+
level = 'active';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
args.position !== undefined &&
|
|
90
|
+
POSITIONS.indexOf(args.position) === -1
|
|
91
|
+
) {
|
|
92
|
+
throw createParameterError('notification.show position is invalid');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
args.durationMs !== undefined &&
|
|
97
|
+
(!Number.isInteger(args.durationMs) || args.durationMs <= 0)
|
|
98
|
+
) {
|
|
99
|
+
throw createParameterError(
|
|
100
|
+
'notification.show durationMs must be a positive integer'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const animation = args.animation === undefined ? 'idle' : args.animation;
|
|
105
|
+
if (ANIMATIONS.indexOf(animation) === -1) {
|
|
106
|
+
throw createParameterError(
|
|
107
|
+
`notification.show animation must be one of: ${ANIMATIONS.join(', ')}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
args.resident !== undefined &&
|
|
113
|
+
typeof args.resident !== 'boolean'
|
|
114
|
+
) {
|
|
115
|
+
throw createParameterError('notification.show resident must be a boolean');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nativeArgs = { level, animation };
|
|
119
|
+
if (hasTitle) nativeArgs.title = title;
|
|
120
|
+
if (hasText) nativeArgs.text = text;
|
|
121
|
+
if (args.position !== undefined) nativeArgs.position = args.position;
|
|
122
|
+
if (args.durationMs !== undefined) nativeArgs.durationMs = args.durationMs;
|
|
123
|
+
if (args.resident !== undefined) nativeArgs.resident = args.resident;
|
|
124
|
+
|
|
125
|
+
return nativeArgs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function buildHideNotificationArgs(args) {
|
|
129
|
+
if (
|
|
130
|
+
!args ||
|
|
131
|
+
typeof args !== 'object' ||
|
|
132
|
+
Array.isArray(args) ||
|
|
133
|
+
typeof args.requestId !== 'string' ||
|
|
134
|
+
!args.requestId.trim()
|
|
135
|
+
) {
|
|
136
|
+
throw createParameterError('notification.hide requires a requestId');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const inputFields = Object.keys(args);
|
|
140
|
+
if (inputFields.length !== 1 || inputFields[0] !== 'requestId') {
|
|
141
|
+
throw createParameterError('notification.hide only supports requestId');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { targetRequestId: args.requestId };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function selectEventResult(eventData) {
|
|
148
|
+
return {
|
|
149
|
+
requestId: eventData.requestId,
|
|
150
|
+
ok: eventData.result.ok
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const notificationShow = {
|
|
155
|
+
publicModule: 'notification',
|
|
156
|
+
publicMethod: 'show',
|
|
157
|
+
|
|
158
|
+
namespace: 'rokid.tools',
|
|
159
|
+
method: 'invoke',
|
|
160
|
+
toolName: 'showNotification',
|
|
161
|
+
toolAction: 'start',
|
|
162
|
+
|
|
163
|
+
completion: COMPLETION_EVENT,
|
|
164
|
+
eventNamespace: 'rokid.tools',
|
|
165
|
+
eventName: 'toolResult',
|
|
166
|
+
isTerminalResult(result) {
|
|
167
|
+
return result.ok === true;
|
|
168
|
+
},
|
|
169
|
+
buildArgs: buildNotificationArgs,
|
|
170
|
+
selectEventResult
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const notificationHide = {
|
|
174
|
+
publicModule: 'notification',
|
|
175
|
+
publicMethod: 'hide',
|
|
176
|
+
|
|
177
|
+
namespace: 'rokid.tools',
|
|
178
|
+
method: 'invoke',
|
|
179
|
+
toolName: 'showNotification',
|
|
180
|
+
toolAction: 'stop',
|
|
181
|
+
|
|
182
|
+
completion: COMPLETION_EVENT,
|
|
183
|
+
eventNamespace: 'rokid.tools',
|
|
184
|
+
eventName: 'toolResult',
|
|
185
|
+
isTerminalResult(result) {
|
|
186
|
+
return result.ok === true;
|
|
187
|
+
},
|
|
188
|
+
buildArgs: buildHideNotificationArgs,
|
|
189
|
+
selectEventResult
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export default [notificationShow, notificationHide];
|