@espressif/rainmaker-neo-base-sdk 1.0.0 → 1.2.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/LICENSE +1 -1
- package/README.md +40 -4
- package/dist/cjs/ESPDevice.js +6 -0
- package/dist/cjs/ESPDevice.js.map +1 -1
- package/dist/cjs/ESPRMNeoBase.js +1 -1
- package/dist/cjs/ESPRMNeoNode.js +33 -14
- package/dist/cjs/ESPRMNeoNode.js.map +1 -1
- package/dist/cjs/index.js +14 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/methods/ESPDevice/Provision.js +7 -0
- package/dist/cjs/methods/ESPDevice/Provision.js.map +1 -1
- package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js +26 -0
- package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
- package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js +46 -0
- package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
- package/dist/cjs/methods/ESPRMNeoNode/Sync.js +4 -1
- package/dist/cjs/methods/ESPRMNeoNode/Sync.js.map +1 -1
- package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js +8 -9
- package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
- package/dist/cjs/proto/esp_rmaker_chal_resp.js +5 -0
- package/dist/cjs/proto/esp_rmaker_chal_resp.js.map +1 -1
- package/dist/cjs/proto/rmaker_local_ctrl.js +273 -0
- package/dist/cjs/proto/rmaker_local_ctrl.js.map +1 -0
- package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
- package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
- package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
- package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
- package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js +183 -116
- package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
- package/dist/cjs/services/ESPTransport/LocalControlSession.js +62 -0
- package/dist/cjs/services/ESPTransport/LocalControlSession.js.map +1 -0
- package/dist/cjs/services/NodeMQTTOrchestrator.js +2 -5
- package/dist/cjs/services/NodeMQTTOrchestrator.js.map +1 -1
- package/dist/cjs/types/transport.js +24 -0
- package/dist/cjs/types/transport.js.map +1 -1
- package/dist/cjs/utils/constants.js +80 -4
- package/dist/cjs/utils/constants.js.map +1 -1
- package/dist/cjs/utils/eventSubscriptionUtils.js +48 -4
- package/dist/cjs/utils/eventSubscriptionUtils.js.map +1 -1
- package/dist/cjs/utils/nodeUtils.js +12 -1
- package/dist/cjs/utils/nodeUtils.js.map +1 -1
- package/dist/cjs/utils/waitForNodeOnline.js +7 -6
- package/dist/cjs/utils/waitForNodeOnline.js.map +1 -1
- package/dist/esm/ESPDevice.js +6 -0
- package/dist/esm/ESPDevice.js.map +1 -1
- package/dist/esm/ESPRMNeoBase.js +1 -1
- package/dist/esm/ESPRMNeoNode.js +33 -14
- package/dist/esm/ESPRMNeoNode.js.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/methods/ESPDevice/Provision.js +7 -0
- package/dist/esm/methods/ESPDevice/Provision.js.map +1 -1
- package/dist/esm/methods/ESPDevice/ResetWifiStatus.js +24 -0
- package/dist/esm/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
- package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js +44 -0
- package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
- package/dist/esm/methods/ESPRMNeoNode/Sync.js +4 -1
- package/dist/esm/methods/ESPRMNeoNode/Sync.js.map +1 -1
- package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js +9 -10
- package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
- package/dist/esm/proto/esp_rmaker_chal_resp.js +5 -0
- package/dist/esm/proto/esp_rmaker_chal_resp.js.map +1 -1
- package/dist/esm/proto/rmaker_local_ctrl.js +271 -0
- package/dist/esm/proto/rmaker_local_ctrl.js.map +1 -0
- package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
- package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
- package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
- package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
- package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js +184 -117
- package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
- package/dist/esm/services/ESPTransport/LocalControlSession.js +59 -0
- package/dist/esm/services/ESPTransport/LocalControlSession.js.map +1 -0
- package/dist/esm/services/NodeMQTTOrchestrator.js +2 -5
- package/dist/esm/services/NodeMQTTOrchestrator.js.map +1 -1
- package/dist/esm/types/transport.js +24 -1
- package/dist/esm/types/transport.js.map +1 -1
- package/dist/esm/utils/constants.js +74 -5
- package/dist/esm/utils/constants.js.map +1 -1
- package/dist/esm/utils/eventSubscriptionUtils.js +49 -5
- package/dist/esm/utils/eventSubscriptionUtils.js.map +1 -1
- package/dist/esm/utils/nodeUtils.js +12 -1
- package/dist/esm/utils/nodeUtils.js.map +1 -1
- package/dist/esm/utils/waitForNodeOnline.js +7 -6
- package/dist/esm/utils/waitForNodeOnline.js.map +1 -1
- package/dist/types/ESPDevice.d.ts +10 -0
- package/dist/types/ESPRMNeoBase.d.ts +1 -1
- package/dist/types/ESPRMNeoNode.d.ts +17 -5
- package/dist/types/methods/ESPDevice/ResetWifiStatus.d.ts +18 -0
- package/dist/types/methods/ESPDevice/RetryNetworkCredentials.d.ts +22 -0
- package/dist/types/methods/ESPDevice/index.d.ts +2 -0
- package/dist/types/methods/ESPRMNeoUser/MQTT/AssumeRole.d.ts +25 -15
- package/dist/types/proto/rmaker_local_ctrl.d.ts +137 -0
- package/dist/types/services/ESPRMNeoMqtt/ESPRMNeoMqtt.d.ts +10 -0
- package/dist/types/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.d.ts +4 -3
- package/dist/types/services/ESPTransport/ESPLocalControlTransport.d.ts +70 -23
- package/dist/types/services/ESPTransport/LocalControlSession.d.ts +24 -0
- package/dist/types/services/NodeMQTTOrchestrator.d.ts +2 -2
- package/dist/types/services/interfaces/MQTTTransport.d.ts +10 -0
- package/dist/types/types/discovery.d.ts +1 -1
- package/dist/types/types/localControl.d.ts +26 -1
- package/dist/types/types/provision.d.ts +10 -0
- package/dist/types/types/transport.d.ts +24 -2
- package/dist/types/utils/baseUtils.d.ts +1 -1
- package/dist/types/utils/constants.d.ts +74 -5
- package/dist/types/utils/eventSubscriptionUtils.d.ts +19 -3
- package/dist/types/utils/waitForNodeOnline.d.ts +9 -3
- package/package.json +1 -1
- package/dist/cjs/proto/constants.js +0 -24
- package/dist/cjs/proto/constants.js.map +0 -1
- package/dist/cjs/proto/esp_local_ctrl.js +0 -787
- package/dist/cjs/proto/esp_local_ctrl.js.map +0 -1
- package/dist/esm/proto/constants.js +0 -24
- package/dist/esm/proto/constants.js.map +0 -1
- package/dist/esm/proto/esp_local_ctrl.js +0 -758
- package/dist/esm/proto/esp_local_ctrl.js.map +0 -1
- package/dist/types/proto/constants.d.ts +0 -15
- package/dist/types/proto/esp_local_ctrl.d.ts +0 -283
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
5
|
+
*
|
|
6
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Codec for the `rmaker_local_ctrl` endpoint protocol, mirroring the firmware's
|
|
10
|
+
* `local_ctrl.proto` schema.
|
|
11
|
+
*
|
|
12
|
+
* Hand-rolled like {@link ClaimingProtoHelper} rather than generated: the
|
|
13
|
+
* schema is small, and generated `google-protobuf` modules cannot be pulled
|
|
14
|
+
* into a downstream Metro/RN bundle (see the note in `utils/export.ts`).
|
|
15
|
+
*
|
|
16
|
+
* Field numbers are the wire contract with deployed firmware — treat them as
|
|
17
|
+
* frozen:
|
|
18
|
+
* - `RMakerLocalCtrlPayload`: 1 = msg (varint), 10 = cmdGetData, 11 = respGetData
|
|
19
|
+
* - `CmdGetData`: 1 = DataType (varint), 2 = Offset (varint),
|
|
20
|
+
* 3 = Timestamp (varint), 4 = HasTimestamp (varint)
|
|
21
|
+
* - `RespGetData`: 1 = Status (varint), 2 = Buf
|
|
22
|
+
* - `PayloadBuf`: 1 = Offset (varint), 2 = Payload (bytes), 3 = TotalLen (varint)
|
|
23
|
+
*
|
|
24
|
+
* `Timestamp` / `HasTimestamp` are reserved for a future signed-response
|
|
25
|
+
* extension and are ignored by current firmware, so the encoder omits them.
|
|
26
|
+
*
|
|
27
|
+
* Only `get_params` / `get_config` use this schema; `set_params` carries raw
|
|
28
|
+
* JSON on the wire.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Status returned by the device for a data read.
|
|
32
|
+
*/
|
|
33
|
+
exports.RMakerLocalCtrlStatus = void 0;
|
|
34
|
+
(function (RMakerLocalCtrlStatus) {
|
|
35
|
+
RMakerLocalCtrlStatus[RMakerLocalCtrlStatus["Success"] = 0] = "Success";
|
|
36
|
+
RMakerLocalCtrlStatus[RMakerLocalCtrlStatus["Fail"] = 1] = "Fail";
|
|
37
|
+
RMakerLocalCtrlStatus[RMakerLocalCtrlStatus["InvalidParam"] = 2] = "InvalidParam";
|
|
38
|
+
RMakerLocalCtrlStatus[RMakerLocalCtrlStatus["NoMemory"] = 3] = "NoMemory";
|
|
39
|
+
})(exports.RMakerLocalCtrlStatus || (exports.RMakerLocalCtrlStatus = {}));
|
|
40
|
+
/**
|
|
41
|
+
* Selects which document a read targets.
|
|
42
|
+
*/
|
|
43
|
+
exports.RMakerLocalCtrlDataType = void 0;
|
|
44
|
+
(function (RMakerLocalCtrlDataType) {
|
|
45
|
+
/** The node's params JSON, as served by `get_params`. */
|
|
46
|
+
RMakerLocalCtrlDataType[RMakerLocalCtrlDataType["TypeParams"] = 0] = "TypeParams";
|
|
47
|
+
/** The node's config JSON, as served by `get_config`. */
|
|
48
|
+
RMakerLocalCtrlDataType[RMakerLocalCtrlDataType["TypeConfig"] = 1] = "TypeConfig";
|
|
49
|
+
})(exports.RMakerLocalCtrlDataType || (exports.RMakerLocalCtrlDataType = {}));
|
|
50
|
+
/**
|
|
51
|
+
* Message type discriminator carried on `RMakerLocalCtrlPayload.msg`.
|
|
52
|
+
*/
|
|
53
|
+
exports.RMakerLocalCtrlMsgType = void 0;
|
|
54
|
+
(function (RMakerLocalCtrlMsgType) {
|
|
55
|
+
RMakerLocalCtrlMsgType[RMakerLocalCtrlMsgType["TypeCmdGetData"] = 0] = "TypeCmdGetData";
|
|
56
|
+
RMakerLocalCtrlMsgType[RMakerLocalCtrlMsgType["TypeRespGetData"] = 1] = "TypeRespGetData";
|
|
57
|
+
})(exports.RMakerLocalCtrlMsgType || (exports.RMakerLocalCtrlMsgType = {}));
|
|
58
|
+
/** Tag bytes written by the request encoder: `(fieldNumber << 3) | wireType`. */
|
|
59
|
+
const TAG_FIELD_1_VARINT = 0x08;
|
|
60
|
+
const TAG_FIELD_2_VARINT = 0x10;
|
|
61
|
+
const TAG_FIELD_10_BYTES = 0x52;
|
|
62
|
+
const WIRE_TYPE_VARINT = 0;
|
|
63
|
+
const WIRE_TYPE_BYTES = 2;
|
|
64
|
+
/**
|
|
65
|
+
* Encoder/decoder for `rmaker_local_ctrl` frames.
|
|
66
|
+
*/
|
|
67
|
+
class RMakerLocalCtrlProtoHelper {
|
|
68
|
+
/**
|
|
69
|
+
* Builds a `CmdGetData` request for one fragment.
|
|
70
|
+
*
|
|
71
|
+
* An `offset` of 0 makes the device (re)generate and cache the document;
|
|
72
|
+
* subsequent offsets are served from that cache.
|
|
73
|
+
*
|
|
74
|
+
* @param dataType - Document to read.
|
|
75
|
+
* @param offset - Byte offset to read from.
|
|
76
|
+
* @returns The serialized request.
|
|
77
|
+
*/
|
|
78
|
+
static createGetDataRequest(dataType, offset) {
|
|
79
|
+
const cmdGetData = this.concat([
|
|
80
|
+
// Both fields are written explicitly, including their proto3 zero values,
|
|
81
|
+
// matching how the reference clients frame this command.
|
|
82
|
+
new Uint8Array([TAG_FIELD_1_VARINT]),
|
|
83
|
+
this.encodeVarint(dataType),
|
|
84
|
+
new Uint8Array([TAG_FIELD_2_VARINT]),
|
|
85
|
+
this.encodeVarint(offset),
|
|
86
|
+
]);
|
|
87
|
+
return this.concat([
|
|
88
|
+
new Uint8Array([TAG_FIELD_1_VARINT]),
|
|
89
|
+
this.encodeVarint(exports.RMakerLocalCtrlMsgType.TypeCmdGetData),
|
|
90
|
+
new Uint8Array([TAG_FIELD_10_BYTES]),
|
|
91
|
+
this.encodeVarint(cmdGetData.length),
|
|
92
|
+
cmdGetData,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parses a `RespGetData` frame from the device.
|
|
97
|
+
*
|
|
98
|
+
* @param data - Raw response bytes.
|
|
99
|
+
* @returns The parsed payload. Absent fields keep their proto3 defaults.
|
|
100
|
+
*/
|
|
101
|
+
static parseGetDataResponse(data) {
|
|
102
|
+
const result = {
|
|
103
|
+
msg: exports.RMakerLocalCtrlMsgType.TypeCmdGetData,
|
|
104
|
+
};
|
|
105
|
+
let index = 0;
|
|
106
|
+
while (index < data.length) {
|
|
107
|
+
const tag = data[index++];
|
|
108
|
+
const fieldNumber = tag >> 3;
|
|
109
|
+
const wireType = tag & 0x07;
|
|
110
|
+
if (fieldNumber === 1 && wireType === WIRE_TYPE_VARINT) {
|
|
111
|
+
const { value, newIndex } = this.readVarint(data, index);
|
|
112
|
+
result.msg = value;
|
|
113
|
+
index = newIndex;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (fieldNumber === 11 && wireType === WIRE_TYPE_BYTES) {
|
|
117
|
+
const { value: length, newIndex } = this.readVarint(data, index);
|
|
118
|
+
result.respGetData = this.parseRespGetData(data.slice(newIndex, newIndex + length));
|
|
119
|
+
index = newIndex + length;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
index = this.skipField(data, index, wireType);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Whether the device reported a successful read.
|
|
128
|
+
*
|
|
129
|
+
* @param response - Parsed response.
|
|
130
|
+
*/
|
|
131
|
+
static isSuccess(response) {
|
|
132
|
+
return response.respGetData?.status === exports.RMakerLocalCtrlStatus.Success;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Returns the device's status as its enum name, for diagnostics.
|
|
136
|
+
*
|
|
137
|
+
* @param response - Parsed response.
|
|
138
|
+
* @returns The status name, or `"Unknown"` when absent.
|
|
139
|
+
*/
|
|
140
|
+
static getStatus(response) {
|
|
141
|
+
const status = response.respGetData?.status;
|
|
142
|
+
return status === undefined
|
|
143
|
+
? "Unknown"
|
|
144
|
+
: (exports.RMakerLocalCtrlStatus[status] ?? `Unknown(${status})`);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Offset the device answered with, which must match the requested offset.
|
|
148
|
+
*
|
|
149
|
+
* @param response - Parsed response.
|
|
150
|
+
*/
|
|
151
|
+
static getOffset(response) {
|
|
152
|
+
return response.respGetData?.buf?.offset ?? 0;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Full document length reported by the device.
|
|
156
|
+
*
|
|
157
|
+
* @param response - Parsed response.
|
|
158
|
+
*/
|
|
159
|
+
static getTotalLen(response) {
|
|
160
|
+
return response.respGetData?.buf?.totalLen ?? 0;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fragment bytes carried by the response.
|
|
164
|
+
*
|
|
165
|
+
* @param response - Parsed response.
|
|
166
|
+
*/
|
|
167
|
+
static getPayload(response) {
|
|
168
|
+
return response.respGetData?.buf?.payload ?? new Uint8Array(0);
|
|
169
|
+
}
|
|
170
|
+
// Private helper methods
|
|
171
|
+
static concat(parts) {
|
|
172
|
+
const totalLength = parts.reduce((sum, part) => sum + part.length, 0);
|
|
173
|
+
const result = new Uint8Array(totalLength);
|
|
174
|
+
let pos = 0;
|
|
175
|
+
for (const part of parts) {
|
|
176
|
+
result.set(part, pos);
|
|
177
|
+
pos += part.length;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
static encodeVarint(value) {
|
|
182
|
+
const bytes = [];
|
|
183
|
+
let remaining = value;
|
|
184
|
+
while (remaining > 127) {
|
|
185
|
+
bytes.push((remaining & 0x7f) | 0x80);
|
|
186
|
+
remaining >>>= 7;
|
|
187
|
+
}
|
|
188
|
+
bytes.push(remaining & 0x7f);
|
|
189
|
+
return new Uint8Array(bytes);
|
|
190
|
+
}
|
|
191
|
+
static readVarint(data, index) {
|
|
192
|
+
let value = 0;
|
|
193
|
+
let shift = 0;
|
|
194
|
+
let byte;
|
|
195
|
+
do {
|
|
196
|
+
byte = data[index++];
|
|
197
|
+
value |= (byte & 0x7f) << shift;
|
|
198
|
+
shift += 7;
|
|
199
|
+
} while (byte & 0x80);
|
|
200
|
+
return { value, newIndex: index };
|
|
201
|
+
}
|
|
202
|
+
/** Advances past a field this codec does not read. */
|
|
203
|
+
static skipField(data, index, wireType) {
|
|
204
|
+
if (wireType === WIRE_TYPE_VARINT) {
|
|
205
|
+
return this.readVarint(data, index).newIndex;
|
|
206
|
+
}
|
|
207
|
+
if (wireType === WIRE_TYPE_BYTES) {
|
|
208
|
+
const { value: length, newIndex } = this.readVarint(data, index);
|
|
209
|
+
return newIndex + length;
|
|
210
|
+
}
|
|
211
|
+
return index + 1;
|
|
212
|
+
}
|
|
213
|
+
static parseRespGetData(data) {
|
|
214
|
+
// In proto3 an absent status defaults to 0, i.e. Success.
|
|
215
|
+
const result = { status: exports.RMakerLocalCtrlStatus.Success };
|
|
216
|
+
let index = 0;
|
|
217
|
+
while (index < data.length) {
|
|
218
|
+
const tag = data[index++];
|
|
219
|
+
const fieldNumber = tag >> 3;
|
|
220
|
+
const wireType = tag & 0x07;
|
|
221
|
+
if (fieldNumber === 1 && wireType === WIRE_TYPE_VARINT) {
|
|
222
|
+
const { value, newIndex } = this.readVarint(data, index);
|
|
223
|
+
result.status = value;
|
|
224
|
+
index = newIndex;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (fieldNumber === 2 && wireType === WIRE_TYPE_BYTES) {
|
|
228
|
+
const { value: length, newIndex } = this.readVarint(data, index);
|
|
229
|
+
result.buf = this.parsePayloadBuf(data.slice(newIndex, newIndex + length));
|
|
230
|
+
index = newIndex + length;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
index = this.skipField(data, index, wireType);
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
static parsePayloadBuf(data) {
|
|
238
|
+
const result = {
|
|
239
|
+
offset: 0,
|
|
240
|
+
payload: new Uint8Array(0),
|
|
241
|
+
totalLen: 0,
|
|
242
|
+
};
|
|
243
|
+
let index = 0;
|
|
244
|
+
while (index < data.length) {
|
|
245
|
+
const tag = data[index++];
|
|
246
|
+
const fieldNumber = tag >> 3;
|
|
247
|
+
const wireType = tag & 0x07;
|
|
248
|
+
if (fieldNumber === 1 && wireType === WIRE_TYPE_VARINT) {
|
|
249
|
+
const { value, newIndex } = this.readVarint(data, index);
|
|
250
|
+
result.offset = value;
|
|
251
|
+
index = newIndex;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (fieldNumber === 2 && wireType === WIRE_TYPE_BYTES) {
|
|
255
|
+
const { value: length, newIndex } = this.readVarint(data, index);
|
|
256
|
+
result.payload = data.slice(newIndex, newIndex + length);
|
|
257
|
+
index = newIndex + length;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (fieldNumber === 3 && wireType === WIRE_TYPE_VARINT) {
|
|
261
|
+
const { value, newIndex } = this.readVarint(data, index);
|
|
262
|
+
result.totalLen = value;
|
|
263
|
+
index = newIndex;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
index = this.skipField(data, index, wireType);
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
exports.RMakerLocalCtrlProtoHelper = RMakerLocalCtrlProtoHelper;
|
|
273
|
+
//# sourceMappingURL=rmaker_local_ctrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rmaker_local_ctrl.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -87,6 +87,19 @@ class ESPRMNeoMqtt {
|
|
|
87
87
|
isConnected() {
|
|
88
88
|
return this.#adapter.isConnected();
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Registers a transport connection-status listener when the underlying
|
|
92
|
+
* adapter supports it; otherwise returns a no-op unsubscribe.
|
|
93
|
+
*
|
|
94
|
+
* @param callback - Invoked with `{ connected }` on status changes
|
|
95
|
+
* @returns Unsubscribe function
|
|
96
|
+
*/
|
|
97
|
+
onConnectionStatusChange(callback) {
|
|
98
|
+
if (typeof this.#adapter.onConnectionStatusChange === "function") {
|
|
99
|
+
return this.#adapter.onConnectionStatusChange(callback);
|
|
100
|
+
}
|
|
101
|
+
return () => { };
|
|
102
|
+
}
|
|
90
103
|
/**
|
|
91
104
|
* Publish a message to a topic.
|
|
92
105
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ESPRMNeoMqtt.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ESPRMNeoMqtt.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -14,9 +14,10 @@ var constants = require('../../../utils/constants.js');
|
|
|
14
14
|
* adapter (see {@link ESPRMNeoBase.setLocalDiscoveryAdapter}).
|
|
15
15
|
*
|
|
16
16
|
* - With no discovery config, the default local protocol is used
|
|
17
|
-
* (mDNS service `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* (mDNS service `_esp_rmaker_ctrl._tcp.` in the `local` domain — the
|
|
18
|
+
* service advertised by RainMaker Neo firmware).
|
|
19
|
+
* - A custom {@link DiscoveryParamsInterface} can be supplied to browse any
|
|
20
|
+
* other service type.
|
|
20
21
|
*/
|
|
21
22
|
class ESPDiscoveryManager {
|
|
22
23
|
/** Discovery parameters (service type / domain) passed to the adapter. */
|
|
@@ -31,7 +32,7 @@ class ESPDiscoveryManager {
|
|
|
31
32
|
throw new Error("ESPLocalDiscoveryAdapter not set");
|
|
32
33
|
}
|
|
33
34
|
this.params = discoveryConfig || {
|
|
34
|
-
serviceType: constants.ServiceType.
|
|
35
|
+
serviceType: constants.ServiceType.ESP_RMAKER_LOCAL_CTRL_TCP,
|
|
35
36
|
domain: discovery.ESPDiscoveryProtocol.local,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ESPDiscoveryManager.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ESPDiscoveryManager.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,156 +1,223 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var esp_local_ctrl = require('../../proto/esp_local_ctrl.js');
|
|
3
|
+
var rmaker_local_ctrl = require('../../proto/rmaker_local_ctrl.js');
|
|
4
|
+
var transport = require('../../types/transport.js');
|
|
6
5
|
var constants = require('../../utils/constants.js');
|
|
7
6
|
var TransformEncoding = require('../ESPRMNeoHelpers/TransformEncoding.js');
|
|
7
|
+
var LocalControlSession = require('./LocalControlSession.js');
|
|
8
8
|
|
|
9
9
|
/*
|
|
10
10
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
11
11
|
*
|
|
12
12
|
* SPDX-License-Identifier: Apache-2.0
|
|
13
13
|
*/
|
|
14
|
-
/** Property index of the writable "params" property exposed by RMNeo local-control firmware. */
|
|
15
|
-
const LOCAL_CONTROL_PARAMS_INDEX = 1;
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
15
|
+
* Tail of the in-flight fragmented-read chain, per node id.
|
|
16
|
+
*
|
|
17
|
+
* Module-level on purpose: {@link delegatedTransportHandler} builds a fresh
|
|
18
|
+
* transport for every call, so a queue held on the instance would serialize
|
|
19
|
+
* nothing. Keyed by node, because the transfer cache is per device.
|
|
20
|
+
*/
|
|
21
|
+
const nodeReadQueues = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Runs `read` after every fragmented read already queued for `nodeId`.
|
|
24
|
+
*
|
|
25
|
+
* A failed read must not wedge the queue, so the chain advances on rejection
|
|
26
|
+
* too, while the caller still sees the original rejection. The map entry is
|
|
27
|
+
* dropped once the chain drains, so a long-lived app doesn't retain one promise
|
|
28
|
+
* per node it has ever talked to.
|
|
29
|
+
*
|
|
30
|
+
* @param nodeId - Node whose reads are being serialized.
|
|
31
|
+
* @param read - Pull loop to run once the node is free.
|
|
32
|
+
*/
|
|
33
|
+
function enqueueNodeRead(nodeId, read) {
|
|
34
|
+
const previous = nodeReadQueues.get(nodeId) ?? Promise.resolve();
|
|
35
|
+
const result = previous.then(read, read);
|
|
36
|
+
const tail = result.then(() => undefined, () => undefined);
|
|
37
|
+
nodeReadQueues.set(nodeId, tail);
|
|
38
|
+
void tail.then(() => {
|
|
39
|
+
// Only the current tail clears the entry; otherwise a newer read owns it.
|
|
40
|
+
if (nodeReadQueues.get(nodeId) === tail) {
|
|
41
|
+
nodeReadQueues.delete(nodeId);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/** Session endpoints of the `rmaker_local_ctrl` protocol, passed to the adapter. */
|
|
47
|
+
const RMAKER_SESSION_OPTIONS = {
|
|
48
|
+
protocol: transport.ESPLocalControlProtocol.rmakerLocalCtrl,
|
|
49
|
+
sessionPath: constants.RMakerLocalCtrlEndpoint.SESSION,
|
|
50
|
+
versionPath: constants.RMakerLocalCtrlEndpoint.VERSION,
|
|
51
|
+
versionKey: constants.RMAKER_LOCAL_CTRL_VERSION_KEY,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The built-in `local` transport, speaking the `rmaker_local_ctrl` endpoint
|
|
55
|
+
* protocol over the app-supplied {@link ESPRMNeoBase.ESPLocalControlAdapter}:
|
|
56
|
+
*
|
|
57
|
+
* - `set_params` carries the same raw JSON body as a cloud set-params call and
|
|
58
|
+
* answers `{"status":"success"}` / `{"status":"fail","description":…}`.
|
|
59
|
+
* - `get_params` / `get_config` exchange protobuf `CmdGetData`/`RespGetData` and
|
|
60
|
+
* are fragmented — the client pulls fixed-size chunks by offset until
|
|
61
|
+
* `TotalLen` is covered.
|
|
62
|
+
*
|
|
63
|
+
* Connection metadata (`baseUrl`, `securityType`, `pop`, and `username` for
|
|
64
|
+
* sec2) is supplied via the transport config by {@link
|
|
65
|
+
* delegatedTransportHandler}; security 0 is not offered by this protocol.
|
|
22
66
|
*/
|
|
23
67
|
class ESPLocalControlTransport {
|
|
24
|
-
payload;
|
|
25
68
|
metadata;
|
|
26
|
-
propertyInfo = {};
|
|
27
69
|
constructor(transportConfig) {
|
|
28
70
|
this.metadata = transportConfig.metadata ?? {};
|
|
29
71
|
}
|
|
30
72
|
get adapter() {
|
|
31
|
-
|
|
32
|
-
if (!adapter) {
|
|
33
|
-
throw new Error("Local control adapter is not configured");
|
|
34
|
-
}
|
|
35
|
-
return adapter;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Connects to the node, retrying on failure. RMNeo's adapter resolves on a
|
|
39
|
-
* successful connection and rejects otherwise, so a resolved call is treated
|
|
40
|
-
* as connected.
|
|
41
|
-
*/
|
|
42
|
-
async connectWithRetry(nodeId, baseUrl, securityType, pop, username, maxRetries = 3) {
|
|
43
|
-
let attempt = 0;
|
|
44
|
-
let lastError;
|
|
45
|
-
while (attempt < maxRetries) {
|
|
46
|
-
try {
|
|
47
|
-
await this.adapter.connect(nodeId, baseUrl, securityType, pop, username);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
lastError = error;
|
|
52
|
-
attempt += 1;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
56
|
-
throw new Error(`Failed to connect after ${maxRetries} attempts: ${message}`);
|
|
73
|
+
return LocalControlSession.getLocalControlAdapter();
|
|
57
74
|
}
|
|
58
75
|
async ensureConnected(nodeId) {
|
|
59
|
-
|
|
60
|
-
if (!isConnected) {
|
|
61
|
-
await this.connectWithRetry(nodeId, this.metadata.baseUrl, this.metadata.securityType ?? 0, this.metadata.pop, this.metadata.username);
|
|
62
|
-
}
|
|
76
|
+
await LocalControlSession.ensureLocalControlSession(this.adapter, nodeId, this.metadata, RMAKER_SESSION_OPTIONS);
|
|
63
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Applies params over `set_params`.
|
|
80
|
+
*
|
|
81
|
+
* @param payload - `{ node_id, payload }`, where `payload` is the
|
|
82
|
+
* `{ <deviceOrServiceName>: { <paramName>: value } }` map to write.
|
|
83
|
+
* @throws {Error} When the device reports a non-success status.
|
|
84
|
+
*/
|
|
64
85
|
async setParam(payload, _nodeRef) {
|
|
65
|
-
|
|
66
|
-
await this.ensureConnected(
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
86
|
+
const nodeId = payload?.node_id;
|
|
87
|
+
await this.ensureConnected(nodeId);
|
|
88
|
+
const request = TransformEncoding.uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload?.payload ?? {})));
|
|
89
|
+
const response = await this.adapter.sendData(nodeId, constants.RMakerLocalCtrlEndpoint.SET_PARAMS, request);
|
|
90
|
+
this.assertSetParamsAccepted(response);
|
|
71
91
|
return { message: "Parameters updated successfully", statusCode: 200 };
|
|
72
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Reads the node's full params document over `get_params`.
|
|
95
|
+
*
|
|
96
|
+
* @param payload - `{ node_id }` identifying the node to read.
|
|
97
|
+
* @returns The params JSON, keyed by device/service name.
|
|
98
|
+
*/
|
|
73
99
|
async getParams(payload, _nodeRef) {
|
|
74
|
-
|
|
75
|
-
await this.ensureConnected(
|
|
76
|
-
return this.
|
|
100
|
+
const nodeId = payload?.node_id;
|
|
101
|
+
await this.ensureConnected(nodeId);
|
|
102
|
+
return this.readJsonDocument(nodeId, rmaker_local_ctrl.RMakerLocalCtrlDataType.TypeParams, constants.RMakerLocalCtrlEndpoint.GET_PARAMS);
|
|
77
103
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
prop.index = LOCAL_CONTROL_PARAMS_INDEX;
|
|
90
|
-
prop.value = new TextEncoder().encode(JSON.stringify(json ?? {}));
|
|
91
|
-
cmd.props.push(prop);
|
|
92
|
-
message.cmd_set_prop_vals = cmd;
|
|
93
|
-
return TransformEncoding.uint8ArrayToBase64(message.serialize());
|
|
94
|
-
}
|
|
95
|
-
processSetPropertyResponse(response) {
|
|
96
|
-
const deserialized = esp_local_ctrl.LocalCtrlMessage.deserialize(TransformEncoding.base64ToUint8Array(response));
|
|
97
|
-
return deserialized.resp_set_prop_vals.status === constants$1.Status.Success;
|
|
104
|
+
/**
|
|
105
|
+
* Reads the node's config document over `get_config`. Not part of
|
|
106
|
+
* {@link ESPTransportInterface} — the node config normally comes from the
|
|
107
|
+
* cloud; this serves LAN-only flows.
|
|
108
|
+
*
|
|
109
|
+
* @param nodeId - Node to read from.
|
|
110
|
+
* @returns The node config JSON.
|
|
111
|
+
*/
|
|
112
|
+
async getConfig(nodeId) {
|
|
113
|
+
await this.ensureConnected(nodeId);
|
|
114
|
+
return this.readJsonDocument(nodeId, rmaker_local_ctrl.RMakerLocalCtrlDataType.TypeConfig, constants.RMakerLocalCtrlEndpoint.GET_CONFIG);
|
|
98
115
|
}
|
|
99
|
-
// ──
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
// ── set ──────────────────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* Validates a `set_params` raw-JSON response.
|
|
119
|
+
*
|
|
120
|
+
* @throws {Error} When the body is unparseable or reports a failure.
|
|
121
|
+
*/
|
|
122
|
+
assertSetParamsAccepted(response) {
|
|
123
|
+
const text = new TextDecoder().decode(TransformEncoding.base64ToUint8Array(response ?? ""));
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(text);
|
|
106
127
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
buildGetPropertyCountRequest() {
|
|
114
|
-
const request = new esp_local_ctrl.LocalCtrlMessage();
|
|
115
|
-
request.msg = esp_local_ctrl.LocalCtrlMsgType.TypeCmdGetPropertyCount;
|
|
116
|
-
request.cmd_get_prop_count = new esp_local_ctrl.CmdGetPropertyCount();
|
|
117
|
-
return TransformEncoding.uint8ArrayToBase64(request.serialize());
|
|
118
|
-
}
|
|
119
|
-
processGetPropertyCountResponse(response) {
|
|
120
|
-
const deserialized = esp_local_ctrl.LocalCtrlMessage.deserialize(TransformEncoding.base64ToUint8Array(response));
|
|
121
|
-
if (deserialized.resp_get_prop_count.status !== constants$1.Status.Success) {
|
|
122
|
-
throw new Error("Failed to retrieve property count from device");
|
|
128
|
+
catch {
|
|
129
|
+
throw new Error(`Unexpected set_params response from device: ${text || "<empty>"}`);
|
|
130
|
+
}
|
|
131
|
+
if (parsed?.status !== constants.RMakerLocalCtrlSetParamsStatus.SUCCESS) {
|
|
132
|
+
throw new Error(parsed?.description ??
|
|
133
|
+
"Failed to set device params over local control (rmaker_local_ctrl)");
|
|
123
134
|
}
|
|
124
|
-
return deserialized.resp_get_prop_count.count;
|
|
125
135
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
// ── get ──────────────────────────────────────────────────────────────────
|
|
137
|
+
/**
|
|
138
|
+
* Pulls a fragmented document and parses it as JSON, serialized against every
|
|
139
|
+
* other fragmented read of the same node.
|
|
140
|
+
*
|
|
141
|
+
* The device holds **one global transfer cache**, not one per session: an
|
|
142
|
+
* offset-0 request regenerates it and it is freed after the last fragment. So
|
|
143
|
+
* a `getParams()` racing a `getConfig()` on the same node would clobber the
|
|
144
|
+
* other — the second offset-0 regenerates the cache mid-transfer, and the
|
|
145
|
+
* first read's next fragment comes back `Fail` (or, worse, carries bytes from
|
|
146
|
+
* the wrong document). The queue makes that interleaving impossible rather
|
|
147
|
+
* than detecting it after the fact.
|
|
148
|
+
*
|
|
149
|
+
* @param nodeId - Node to read from.
|
|
150
|
+
* @param dataType - Which document to read (params or config).
|
|
151
|
+
* @param endpoint - Endpoint serving that document.
|
|
152
|
+
* @throws {Error} When the device reports a failure, the response is
|
|
153
|
+
* malformed, or a fragment makes no forward progress.
|
|
154
|
+
*/
|
|
155
|
+
readJsonDocument(nodeId, dataType, endpoint) {
|
|
156
|
+
return enqueueNodeRead(nodeId, () => this.pullJsonDocument(nodeId, dataType, endpoint));
|
|
157
|
+
}
|
|
158
|
+
/** The client-pull loop itself; always reached via {@link readJsonDocument}. */
|
|
159
|
+
async pullJsonDocument(nodeId, dataType, endpoint) {
|
|
160
|
+
const fragments = [];
|
|
161
|
+
let offset = 0;
|
|
162
|
+
let totalLength = 0;
|
|
163
|
+
do {
|
|
164
|
+
const response = await this.adapter.sendData(nodeId, endpoint, this.buildGetDataRequest(dataType, offset));
|
|
165
|
+
const buf = this.processGetDataResponse(response, offset);
|
|
166
|
+
totalLength = buf.totalLength;
|
|
167
|
+
if (totalLength === 0) {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
if (buf.payload.length === 0) {
|
|
171
|
+
// Without forward progress the pull loop would never terminate.
|
|
172
|
+
throw new Error(`Device returned an empty fragment at offset ${offset} of ${totalLength} on ${endpoint}`);
|
|
173
|
+
}
|
|
174
|
+
fragments.push(buf.payload);
|
|
175
|
+
offset += buf.payload.length;
|
|
176
|
+
} while (offset < totalLength);
|
|
177
|
+
if (offset > totalLength) {
|
|
178
|
+
throw new Error(`Fragmented read of ${endpoint} overran: got ${offset} bytes, expected ${totalLength}`);
|
|
131
179
|
}
|
|
180
|
+
return this.parseJsonFragments(fragments, endpoint);
|
|
132
181
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
request.msg = esp_local_ctrl.LocalCtrlMsgType.TypeCmdGetPropertyValues;
|
|
136
|
-
const cmd = new esp_local_ctrl.CmdGetPropertyValues();
|
|
137
|
-
cmd.indices.push(index);
|
|
138
|
-
request.cmd_get_prop_vals = cmd;
|
|
139
|
-
return TransformEncoding.uint8ArrayToBase64(request.serialize());
|
|
182
|
+
buildGetDataRequest(dataType, offset) {
|
|
183
|
+
return TransformEncoding.uint8ArrayToBase64(rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.createGetDataRequest(dataType, offset));
|
|
140
184
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Parses one `RespGetData` and checks it answers the requested offset.
|
|
187
|
+
*
|
|
188
|
+
* @param response - Base64 protobuf response from the adapter.
|
|
189
|
+
* @param requestedOffset - Offset asked for, used to detect a desynced pull.
|
|
190
|
+
*/
|
|
191
|
+
processGetDataResponse(response, requestedOffset) {
|
|
192
|
+
const parsed = rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.parseGetDataResponse(TransformEncoding.base64ToUint8Array(response));
|
|
193
|
+
if (!rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.isSuccess(parsed)) {
|
|
194
|
+
throw new Error(`Device rejected the local-control read (status ${rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.getStatus(parsed)})`);
|
|
195
|
+
}
|
|
196
|
+
const fragmentOffset = rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.getOffset(parsed);
|
|
197
|
+
if (fragmentOffset !== requestedOffset) {
|
|
198
|
+
throw new Error(`Device answered offset ${fragmentOffset}, expected ${requestedOffset}`);
|
|
145
199
|
}
|
|
146
|
-
const prop = deserialized.resp_get_prop_vals.props[0];
|
|
147
|
-
if (!prop)
|
|
148
|
-
return undefined;
|
|
149
200
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
201
|
+
payload: rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.getPayload(parsed),
|
|
202
|
+
totalLength: rmaker_local_ctrl.RMakerLocalCtrlProtoHelper.getTotalLen(parsed),
|
|
152
203
|
};
|
|
153
204
|
}
|
|
205
|
+
/** Joins the pulled fragments and parses the result as JSON. */
|
|
206
|
+
parseJsonFragments(fragments, endpoint) {
|
|
207
|
+
const joined = new Uint8Array(fragments.reduce((length, fragment) => length + fragment.length, 0));
|
|
208
|
+
let position = 0;
|
|
209
|
+
for (const fragment of fragments) {
|
|
210
|
+
joined.set(fragment, position);
|
|
211
|
+
position += fragment.length;
|
|
212
|
+
}
|
|
213
|
+
const text = new TextDecoder().decode(joined);
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(text);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw new Error(`Device returned malformed JSON on ${endpoint}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
154
221
|
}
|
|
155
222
|
|
|
156
223
|
exports.ESPLocalControlTransport = ESPLocalControlTransport;
|