@espressif/rainmaker-neo-base-sdk 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +40 -4
  3. package/dist/cjs/ESPDevice.js +6 -0
  4. package/dist/cjs/ESPDevice.js.map +1 -1
  5. package/dist/cjs/ESPRMNeoNode.js +33 -14
  6. package/dist/cjs/ESPRMNeoNode.js.map +1 -1
  7. package/dist/cjs/index.js +3 -0
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/methods/ESPDevice/Provision.js +7 -0
  10. package/dist/cjs/methods/ESPDevice/Provision.js.map +1 -1
  11. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js +26 -0
  12. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  13. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js +46 -0
  14. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  15. package/dist/cjs/methods/ESPRMNeoNode/Sync.js +4 -1
  16. package/dist/cjs/methods/ESPRMNeoNode/Sync.js.map +1 -1
  17. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js +8 -9
  18. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  19. package/dist/cjs/proto/esp_rmaker_chal_resp.js +5 -0
  20. package/dist/cjs/proto/esp_rmaker_chal_resp.js.map +1 -1
  21. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  22. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  23. package/dist/cjs/services/NodeMQTTOrchestrator.js +2 -5
  24. package/dist/cjs/services/NodeMQTTOrchestrator.js.map +1 -1
  25. package/dist/cjs/utils/constants.js +10 -0
  26. package/dist/cjs/utils/constants.js.map +1 -1
  27. package/dist/cjs/utils/nodeUtils.js +12 -1
  28. package/dist/cjs/utils/nodeUtils.js.map +1 -1
  29. package/dist/cjs/utils/waitForNodeOnline.js +7 -6
  30. package/dist/cjs/utils/waitForNodeOnline.js.map +1 -1
  31. package/dist/esm/ESPDevice.js +6 -0
  32. package/dist/esm/ESPDevice.js.map +1 -1
  33. package/dist/esm/ESPRMNeoNode.js +33 -14
  34. package/dist/esm/ESPRMNeoNode.js.map +1 -1
  35. package/dist/esm/index.js +3 -1
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/esm/methods/ESPDevice/Provision.js +7 -0
  38. package/dist/esm/methods/ESPDevice/Provision.js.map +1 -1
  39. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js +24 -0
  40. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  41. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js +44 -0
  42. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  43. package/dist/esm/methods/ESPRMNeoNode/Sync.js +4 -1
  44. package/dist/esm/methods/ESPRMNeoNode/Sync.js.map +1 -1
  45. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js +9 -10
  46. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  47. package/dist/esm/proto/esp_rmaker_chal_resp.js +5 -0
  48. package/dist/esm/proto/esp_rmaker_chal_resp.js.map +1 -1
  49. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  50. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  51. package/dist/esm/services/NodeMQTTOrchestrator.js +2 -5
  52. package/dist/esm/services/NodeMQTTOrchestrator.js.map +1 -1
  53. package/dist/esm/utils/constants.js +10 -1
  54. package/dist/esm/utils/constants.js.map +1 -1
  55. package/dist/esm/utils/nodeUtils.js +12 -1
  56. package/dist/esm/utils/nodeUtils.js.map +1 -1
  57. package/dist/esm/utils/waitForNodeOnline.js +7 -6
  58. package/dist/esm/utils/waitForNodeOnline.js.map +1 -1
  59. package/dist/types/ESPDevice.d.ts +10 -0
  60. package/dist/types/ESPRMNeoNode.d.ts +17 -5
  61. package/dist/types/methods/ESPDevice/ResetWifiStatus.d.ts +18 -0
  62. package/dist/types/methods/ESPDevice/RetryNetworkCredentials.d.ts +22 -0
  63. package/dist/types/methods/ESPDevice/index.d.ts +2 -0
  64. package/dist/types/methods/ESPRMNeoUser/MQTT/AssumeRole.d.ts +25 -15
  65. package/dist/types/services/ESPRMNeoMqtt/ESPRMNeoMqtt.d.ts +10 -0
  66. package/dist/types/services/NodeMQTTOrchestrator.d.ts +2 -2
  67. package/dist/types/services/interfaces/MQTTTransport.d.ts +10 -0
  68. package/dist/types/types/provision.d.ts +10 -0
  69. package/dist/types/utils/constants.d.ts +10 -1
  70. package/dist/types/utils/waitForNodeOnline.d.ts +9 -3
  71. package/package.json +1 -1
package/LICENSE CHANGED
@@ -187,7 +187,7 @@
187
187
  same "printed page" as the copyright notice for easier
188
188
  identification within third-party archives.
189
189
 
190
- Copyright 2026 Espressif Systems \(Shanghai\) CO LTD
190
+ Copyright 2026 Espressif Systems (Shanghai) CO LTD
191
191
 
192
192
  Licensed under the Apache License, Version 2.0 (the "License");
193
193
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,3 +1,40 @@
1
+ # ESP RainMaker Neo - IoT Cloud
2
+
3
+ **Tools**
4
+
5
+ [![Try With Launchpad](https://img.shields.io/badge/Try_With-ESP_Launchpad-0066CC?logo=espressif)](https://espressif.github.io/esp-launchpad/?flashConfigURL=https://espressif.github.io/esp-rainmaker-neo-firmware/launchpad.toml)
6
+  
7
+ [![Download iOS App](https://img.shields.io/badge/Download-iOS_App-0066CC?logo=appstore)](https://apps.apple.com/us/app/esp-rainmaker-home/id1563728960)
8
+  
9
+ [![Download Android App](https://img.shields.io/badge/Download-Android_App-0066CC?logo=googleplay)](https://play.google.com/store/apps/details?id=com.espressif.novahome)
10
+
11
+ **Documentation**
12
+
13
+ [![ESP RainMaker Neo Website](https://img.shields.io/badge/ESP_RainMaker_Neo-Website-6F42C1?logo=espressif)](https://neo.rainmaker.espressif.com)
14
+  
15
+ [![Documentation](https://img.shields.io/badge/ESP_RainMaker_Neo-Docs-6F42C1?logo=readthedocs)](https://docs.neo.rainmaker.espressif.com/)
16
+
17
+ ---
18
+
19
+ ## Introduction
20
+
21
+ ESP RainMaker Neo is a serverless, open-source IoT cloud for ESP devices that you deploy into your own AWS account. It scales with your fleet and is pay-as-you-go. Devices connect over MQTT through AWS IoT. Phone apps, the admin dashboard and voice assistants reach the same backend over REST APIs and MQTT.
22
+
23
+ <p align="center">
24
+ <img src="assets/images/architecture.svg" alt="ESP RainMaker Neo Architecture" width="800">
25
+ </p>
26
+
27
+ ### Repositories
28
+
29
+ | Repository | Holds |
30
+ | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
31
+ | [esp-rainmaker-neo](https://github.com/espressif/esp-rainmaker-neo) | Cloud backend, admin dashboard |
32
+ | [esp-rainmaker-neo-firmware](https://github.com/espressif/esp-rainmaker-neo-firmware) | Device firmware SDK |
33
+ | [esp-rainmaker-home](https://github.com/espressif/esp-rainmaker-home)<br>[esp-rainmaker-neo-app-sdk-ts](https://github.com/espressif/esp-rainmaker-neo-app-sdk-ts) | ESP RainMaker Home phone app (iOS and Android)<br>(this repository) ESP RainMaker Neo App SDK (TypeScript) |
34
+
35
+
36
+ ---
37
+
1
38
  # ESP RainMaker Neo TypeScript SDK
2
39
 
3
40
  **RainMaker Neo** (short: **RMNeo**) is Espressif’s AIoT stack. `@espressif/rainmaker-neo-base-sdk` is the TypeScript SDK that powers JavaScript-based apps integrating with RainMaker Neo — authentication, groups/nodes, device provisioning, real-time MQTT control, and group sharing.
@@ -63,10 +100,7 @@ import { ESPRMNeoBase } from "@espressif/rainmaker-neo-base-sdk";
63
100
  ESPRMNeoBase.init({
64
101
  baseUrl: "https://your-api-gateway.amazonaws.com/prod",
65
102
  userApiBase: "https://your-user-api-gateway.amazonaws.com/prod",
66
- identityId: "your-identity-pool-id",
67
103
  awsRegion: "us-east-1",
68
- userPoolId: "your-user-pool-id",
69
- clientId: "your-cognito-client-id",
70
104
  iotEndpoint: "xxxxxxxx-ats.iot.us-east-1.amazonaws.com",
71
105
  // Optional adapters (provided by your app):
72
106
  // mqttAdapter, customStorageAdapter, provisionAdapter, ...
@@ -184,7 +218,9 @@ await device.disconnect();
184
218
 
185
219
  ## API Documentation
186
220
 
187
- Generate local docs from JSDoc:
221
+ Browse the hosted API reference at [https://espressif.github.io/esp-rainmaker-neo-app-sdk-ts/](https://espressif.github.io/esp-rainmaker-neo-app-sdk-ts/).
222
+
223
+ To generate local docs from JSDoc:
188
224
 
189
225
  ```bash
190
226
  npm run genDocs
@@ -24,6 +24,12 @@ class ESPDevice {
24
24
  name;
25
25
  transport;
26
26
  security;
27
+ /**
28
+ * What a completed association produced, kept so `retryNetworkCredentials`
29
+ * can resume the flow instead of repeating it. Set once verification
30
+ * succeeds; `undefined` until then.
31
+ */
32
+ provisionResumeState;
27
33
  constructor(deviceConfig) {
28
34
  this.name = deviceConfig.name;
29
35
  this.transport = deviceConfig.transport;
@@ -1 +1 @@
1
- {"version":3,"file":"ESPDevice.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPDevice.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -40,14 +40,19 @@ class ESPRMNeoNode {
40
40
  subgroupIds = [];
41
41
  devices = [];
42
42
  services = [];
43
+ /**
44
+ * Last-known connectivity. Seeded from cached `connectivity_status` when
45
+ * present (cloud config does not provide it); otherwise starts offline until
46
+ * an MQTT shadow `online` update arrives.
47
+ */
43
48
  connectivityStatus = {
44
49
  isConnected: false,
45
50
  lastConnectionTimestamp: 0,
46
51
  };
47
52
  /**
48
53
  * Transports currently usable for this node, keyed by mode.
49
- * - `mqtt` (cloud) is added when the node reports online and removed when
50
- * it goes offline (via shadow updates or seeded `connectivity_status`).
54
+ * - `mqtt` (cloud) is added when the node is connected and removed when it
55
+ * goes offline (from cached `connectivity_status` or shadow updates).
51
56
  * - `local` is added/removed at runtime by a `localDiscovery` subscriber as the
52
57
  * node appears/disappears on the LAN (via {@link addTransport}/{@link removeTransport}).
53
58
  * Apps may also add custom string-keyed entries (pair with
@@ -80,16 +85,11 @@ class ESPRMNeoNode {
80
85
  this.subgroupIds = common.asStringArray(subgroupIdOrIds);
81
86
  this.transportOrder = ESPRMNeoBase.ESPRMNeoBase.getTransportOrder();
82
87
  this.availableTransports = {};
83
- this.applyNodeConfig(config);
84
88
  if (config.connectivity_status) {
85
89
  this.connectivityStatus = config.connectivity_status;
86
90
  }
87
- if (this.connectivityStatus.isConnected) {
88
- this.addTransport(transport.ESPTransportMode.mqtt, {
89
- type: transport.ESPTransportMode.mqtt,
90
- metadata: {},
91
- });
92
- }
91
+ this.applyNodeConfig(config);
92
+ this.syncMqttTransportAvailability();
93
93
  this.subscribeToMqttUpdates();
94
94
  }
95
95
  /**
@@ -169,11 +169,14 @@ class ESPRMNeoNode {
169
169
  this.devices = deviceRecords.map((d) => new ESPRMNeoDevice.ESPRMNeoDevice(nodeTransform.transformNodeDevice(d), nodeRef));
170
170
  this.services = serviceRecords.map((s) => new ESPRMNeoService.ESPRMNeoService(nodeTransform.transformNodeService(s), nodeRef));
171
171
  // Keep wire records for cache writes (never persist live class instances).
172
+ // Preserve last-known connectivity when the incoming config omits it
173
+ // (cloud node config does not include connectivity_status).
172
174
  this.wireConfig = {
173
175
  ...config,
174
176
  info,
175
177
  devices: deviceRecords,
176
178
  services: serviceRecords,
179
+ connectivity_status: config.connectivity_status ?? this.connectivityStatus,
177
180
  };
178
181
  this.config = {
179
182
  ...config,
@@ -243,19 +246,27 @@ class ESPRMNeoNode {
243
246
  this.wireConfig.params = this.config.params;
244
247
  }
245
248
  this.applyConnectivityStatus(shadow, connectivityStatus);
246
- ESPRMNeoStorage.ESPRMNeoStorage.setItem(constants.StorageKeys.NODE_CONFIG_PREFIX + this.nodeId, JSON.stringify({ ...this.wireConfig, node_id: this.nodeId }));
249
+ this.persistWireConfig();
247
250
  this.refreshConfigIfNcfgChanged(shadow);
248
251
  }
249
252
  /**
250
- * Updates {@link connectivityStatus} and MQTT transport availability when the
251
- * shadow reports `online`. Partial updates without `online` are ignored so
252
- * an existing status is not cleared.
253
+ * Updates {@link connectivityStatus} when the shadow reports `online`, then
254
+ * syncs MQTT transport availability. Partial updates without `online` are
255
+ * ignored so an existing status is not cleared.
253
256
  */
254
257
  applyConnectivityStatus(shadow, connectivityStatus) {
255
258
  if (shadow.state?.reported?.online === undefined)
256
259
  return;
257
260
  this.connectivityStatus = connectivityStatus;
258
- if (connectivityStatus.isConnected) {
261
+ this.wireConfig.connectivity_status = connectivityStatus;
262
+ this.syncMqttTransportAvailability();
263
+ }
264
+ /**
265
+ * Registers the MQTT transport only while {@link connectivityStatus} reports
266
+ * the node as connected; removes it when offline.
267
+ */
268
+ syncMqttTransportAvailability() {
269
+ if (this.connectivityStatus.isConnected) {
259
270
  this.addTransport(transport.ESPTransportMode.mqtt, {
260
271
  type: transport.ESPTransportMode.mqtt,
261
272
  metadata: {},
@@ -265,6 +276,14 @@ class ESPRMNeoNode {
265
276
  this.removeTransport(transport.ESPTransportMode.mqtt);
266
277
  }
267
278
  }
279
+ /** Persists {@link wireConfig} (including last-known connectivity) to storage. */
280
+ persistWireConfig() {
281
+ ESPRMNeoStorage.ESPRMNeoStorage.setItem(constants.StorageKeys.NODE_CONFIG_PREFIX + this.nodeId, JSON.stringify({
282
+ ...this.wireConfig,
283
+ node_id: this.nodeId,
284
+ connectivity_status: this.connectivityStatus,
285
+ }));
286
+ }
268
287
  /** When cloud `ncfg_ver` changes, refetch config and persist the new marker. */
269
288
  async refreshConfigIfNcfgChanged(shadow) {
270
289
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"ESPRMNeoNode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPRMNeoNode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/cjs/index.js CHANGED
@@ -104,6 +104,8 @@ require('./methods/ESPDevice/SetProofOfPossession.js');
104
104
  require('./methods/ESPDevice/SendData.js');
105
105
  require('./methods/ESPDevice/InitializeSession.js');
106
106
  require('./methods/ESPDevice/SetNetworkCredentials.js');
107
+ require('./methods/ESPDevice/ResetWifiStatus.js');
108
+ require('./methods/ESPDevice/RetryNetworkCredentials.js');
107
109
  require('./methods/ESPDevice/InitiateUserNodeMapping.js');
108
110
  require('./methods/ESPDevice/VerifyUserNodeMapping.js');
109
111
  require('./methods/ESPDevice/StartAssistedClaiming.js');
@@ -224,6 +226,7 @@ exports.APIEndpoints = constants.APIEndpoints;
224
226
  exports.APIOperations = constants.APIOperations;
225
227
  exports.APIPathV1 = constants.APIPathV1;
226
228
  exports.AWSCredentialsErrorMessages = constants.AWSCredentialsErrorMessages;
229
+ exports.AssumeRoleErrorMessages = constants.AssumeRoleErrorMessages;
227
230
  exports.AuthErrorCodes = constants.AuthErrorCodes;
228
231
  exports.AuthSuccessMessages = constants.AuthSuccessMessages;
229
232
  exports.AutomationStatusValues = constants.AutomationStatusValues;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -67,6 +67,13 @@ async function runChallengeResponseFlow(device, ssid, passphrase, groupId, onPro
67
67
  challenge_response: signedChallenge,
68
68
  node_id: nodeId,
69
69
  });
70
+ // Association is verified and cannot be repeated; remember what step 6 needs
71
+ // so `retryNetworkCredentials` can resume from the Wi-Fi step.
72
+ device.provisionResumeState = {
73
+ nodeId: nodeId,
74
+ groupId,
75
+ options: options,
76
+ };
70
77
  // 5. SET NETWORK CREDENTIALS
71
78
  reportProgress(onProgress, provision.ESPProvResponseStatus.onProgress, constants.ESPProvProgressMessages.SETTING_NETWORK_CREDENTIALS);
72
79
  await device.setNetworkCredentials(ssid, passphrase);
@@ -1 +1 @@
1
- {"version":3,"file":"Provision.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"Provision.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ var ESPDevice = require('../../ESPDevice.js');
4
+ var logger$1 = require('../../utils/logger.js');
5
+
6
+ /*
7
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
8
+ *
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+ const logger = new logger$1.Logger("ResetWifiStatus");
12
+ ESPDevice.ESPDevice.prototype.resetWifiStatus = async function () {
13
+ if (!this.ESPProvisionAdapter.resetWifiStatus) {
14
+ logger.warn(`Adapter has no resetWifiStatus; treating as not acknowledged for "${this.name}"`);
15
+ return false;
16
+ }
17
+ try {
18
+ const acknowledged = await this.ESPProvisionAdapter.resetWifiStatus(this.name);
19
+ return acknowledged;
20
+ }
21
+ catch (error) {
22
+ logger.error(`<- device "${this.name}": reset failed`, error);
23
+ throw error;
24
+ }
25
+ };
26
+ //# sourceMappingURL=ResetWifiStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetWifiStatus.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ var ESPDevice = require('../../ESPDevice.js');
4
+ var provision = require('../../types/provision.js');
5
+ var constants = require('../../utils/constants.js');
6
+ var ESPProvError = require('../../utils/error/ESPProvError.js');
7
+ var waitForNodeOnline = require('../../utils/waitForNodeOnline.js');
8
+ var logger$1 = require('../../utils/logger.js');
9
+
10
+ /*
11
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
12
+ *
13
+ * SPDX-License-Identifier: Apache-2.0
14
+ */
15
+ const logger = new logger$1.Logger("RetryNetworkCredentials");
16
+ ESPDevice.ESPDevice.prototype.retryNetworkCredentials = async function (ssid, passphrase, onProgress) {
17
+ const resumeState = this.provisionResumeState;
18
+ if (!resumeState) {
19
+ logger.error(`Retry refused for "${this.name}": no association recorded, nothing to resume`);
20
+ throw new ESPProvError.ESPProvError("FAILED_PROV");
21
+ }
22
+ onProgress({
23
+ status: provision.ESPProvResponseStatus.onProgress,
24
+ description: constants.ESPProvProgressMessages.SETTING_NETWORK_CREDENTIALS,
25
+ });
26
+ await this.setNetworkCredentials(ssid, passphrase);
27
+ const options = resumeState.options;
28
+ if (options?.waitForOnline && options.user) {
29
+ onProgress({
30
+ status: provision.ESPProvResponseStatus.onProgress,
31
+ description: constants.ESPProvProgressMessages.WAITING_FOR_ONLINE,
32
+ });
33
+ await waitForNodeOnline.waitForNodeOnline({
34
+ nodeId: resumeState.nodeId,
35
+ groupId: resumeState.groupId,
36
+ user: options.user,
37
+ timeoutMs: options.onlineTimeoutMs ?? waitForNodeOnline.DEFAULT_NODE_ONLINE_TIMEOUT_MS,
38
+ });
39
+ }
40
+ onProgress({
41
+ status: provision.ESPProvResponseStatus.succeed,
42
+ description: resumeState.nodeId ?? constants.ESPProvProgressMessages.DEVICE_PROVISIONED,
43
+ });
44
+ return resumeState.nodeId;
45
+ };
46
+ //# sourceMappingURL=RetryNetworkCredentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetryNetworkCredentials.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -16,7 +16,10 @@ ESPRMNeoNode.ESPRMNeoNode.prototype.sync = async function () {
16
16
  // Apply first so a schema issue surfaces to the caller instead of
17
17
  // poisoning the storage cache for the next cold start.
18
18
  this.applyNodeConfig(config);
19
- await nodeUtils.writeLocalNodeConfig(this.nodeId, config);
19
+ await nodeUtils.writeLocalNodeConfig(this.nodeId, {
20
+ ...config,
21
+ connectivity_status: this.connectivityStatus,
22
+ });
20
23
  return this;
21
24
  }
22
25
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"Sync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"Sync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -11,27 +11,26 @@ var ESPSigV4APIManager = require('../../../services/ESPSigV4APIManager.js');
11
11
  */
12
12
  /**
13
13
  * Implementation of the `assumeRole` method for the `ESPRMNeoUser` class.
14
- *
15
- * Exchanges identity (temporary) credentials for assume-role credentials with IoT/MQTT
16
- * permissions. The backend signs the user in with the provided credentials and returns
17
- * a new credential set for MQTT; this is not refreshing or extending the same credentials.
18
14
  */
19
15
  ESPRMNeoUser.ESPRMNeoUser.prototype.assumeRole = async function (accessKey, secretKey, sessionToken, options) {
20
- // Request body: identity (temporary) credentials so backend can assume the IoT role
21
16
  const requestBody = {
22
17
  access_key: accessKey,
23
18
  secret_key: secretKey,
24
19
  session_token: sessionToken,
25
20
  };
26
- // Merge optional fields (e.g., include: ["s3", "kvs"])
27
- if (options?.include && options.include.length > 0) {
28
- requestBody.include = options.include;
21
+ const servicesMode = !!(options?.services && options.services.length > 0);
22
+ if (servicesMode) {
23
+ if (!options.nodeId) {
24
+ throw new Error(constants.AssumeRoleErrorMessages.NODE_ID_REQUIRED);
25
+ }
26
+ requestBody.services = options.services;
27
+ requestBody.node_id = options.nodeId;
29
28
  }
30
29
  const api = ESPSigV4APIManager.ESPSigV4APIManager.getInstance();
31
30
  const response = await api.post(constants.APIPathV1.ASSUME_ROLE, requestBody);
32
31
  // Validate response contains required fields
33
32
  if (!response.access_key || !response.secret_key || !response.session_token) {
34
- throw new Error("Invalid assume role response: missing required credential fields");
33
+ throw new Error(constants.AssumeRoleErrorMessages.INVALID_RESPONSE);
35
34
  }
36
35
  return response;
37
36
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AssumeRole.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"AssumeRole.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -21,6 +21,11 @@ function _interopNamespaceDefault(e) {
21
21
 
22
22
  var pb_1__namespace = /*#__PURE__*/_interopNamespaceDefault(pb_1);
23
23
 
24
+ /*
25
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
26
+ *
27
+ * SPDX-License-Identifier: Apache-2.0
28
+ */
24
29
  /**
25
30
  * Generated by the protoc-gen-ts. DO NOT EDIT!
26
31
  * compiler version: 5.29.3
@@ -1 +1 @@
1
- {"version":3,"file":"esp_rmaker_chal_resp.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"esp_rmaker_chal_resp.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -141,8 +141,8 @@ class NodeMQTTOrchestrator {
141
141
  /**
142
142
  * Registers a node with the orchestrator.
143
143
  * Must be called before subscribeToNode, getParams, getShadow, etc.
144
- * On shadow rename: clears the old binding; ignores shorter/incomplete names.
145
- * Shrink membership via {@link unregisterNode} first.
144
+ * On shadow rename: clears the old binding, then installs the new name
145
+ * (including shrinks to a shorter membership shadow).
146
146
  *
147
147
  * @param nodeId - The device/node identifier.
148
148
  * @param shadowName - The named shadow (e.g. `params-groupId-subgroupId`).
@@ -151,9 +151,6 @@ class NodeMQTTOrchestrator {
151
151
  const instance = NodeMQTTOrchestrator.getInstance();
152
152
  const existing = instance.nodeMap.get(nodeId);
153
153
  if (existing && existing.shadowName !== shadowName) {
154
- if (shadowName.split("-").length < existing.shadowName.split("-").length) {
155
- return;
156
- }
157
154
  NodeMQTTOrchestrator.unregisterNode(nodeId);
158
155
  }
159
156
  instance.nodeMap.set(nodeId, { shadowName });
@@ -1 +1 @@
1
- {"version":3,"file":"NodeMQTTOrchestrator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"NodeMQTTOrchestrator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -393,6 +393,15 @@ const TriggerErrorMessages = {
393
393
  /** Logged when retrieving triggers for a node fails. */
394
394
  GET_FAILED: "getTriggers failed",
395
395
  };
396
+ /**
397
+ * Error messages for assumeRole (POST /v1/assumed-roles).
398
+ */
399
+ const AssumeRoleErrorMessages = {
400
+ /** Thrown when `services` is provided without a `nodeId`. */
401
+ NODE_ID_REQUIRED: "assumeRole: nodeId is required when services is provided",
402
+ /** Thrown when the assume-role response is missing required credential fields. */
403
+ INVALID_RESPONSE: "Invalid assume role response: missing required credential fields",
404
+ };
396
405
  /**
397
406
  * Error / log messages for temporary AWS credentials (POST /v1/user/credentials).
398
407
  */
@@ -847,6 +856,7 @@ exports.APIEndpoints = APIEndpoints;
847
856
  exports.APIOperations = APIOperations;
848
857
  exports.APIPathV1 = APIPathV1;
849
858
  exports.AWSCredentialsErrorMessages = AWSCredentialsErrorMessages;
859
+ exports.AssumeRoleErrorMessages = AssumeRoleErrorMessages;
850
860
  exports.AuthErrorCodes = AuthErrorCodes;
851
861
  exports.AuthSuccessMessages = AuthSuccessMessages;
852
862
  exports.AutomationStatusValues = AutomationStatusValues;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"constants.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -28,7 +28,18 @@ async function readLocalNodeConfig(nodeId) {
28
28
  /** Best-effort persist of node config to local storage. */
29
29
  async function writeLocalNodeConfig(nodeId, config) {
30
30
  try {
31
- await ESPRMNeoStorage.ESPRMNeoStorage.setNodeConfig(nodeId, config);
31
+ let toStore = config;
32
+ // Cloud config omits connectivity — keep the last cached status when writing.
33
+ if (!config.connectivity_status) {
34
+ const prev = await ESPRMNeoStorage.ESPRMNeoStorage.getNodeConfig(nodeId);
35
+ if (prev?.connectivity_status) {
36
+ toStore = {
37
+ ...config,
38
+ connectivity_status: prev.connectivity_status,
39
+ };
40
+ }
41
+ }
42
+ await ESPRMNeoStorage.ESPRMNeoStorage.setNodeConfig(nodeId, toStore);
32
43
  }
33
44
  catch (error) {
34
45
  logger.warn(`Cache write failed for ${nodeId}`, error);
@@ -1 +1 @@
1
- {"version":3,"file":"nodeUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"nodeUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -65,11 +65,12 @@ function isNodeOnlineFromShadowPayload(payload) {
65
65
  * @throws {ESPProvError} If `timeoutMs` elapses without the node reporting online.
66
66
  * @throws {Error} If MQTT connection or subscription fails.
67
67
  */
68
- async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
69
- const shadowName = shadowUtils.constructShadowName(groupId);
68
+ async function waitForNodeOnline({ nodeId, groupId, subgroupIds, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
69
+ const shadowName = shadowUtils.constructShadowName(groupId, subgroupIds ?? []);
70
70
  logger.info("Waiting for node to come online", {
71
71
  nodeId,
72
72
  groupId,
73
+ subgroupIds: subgroupIds ?? [],
73
74
  shadowName,
74
75
  timeoutMs,
75
76
  pollIntervalMs,
@@ -149,10 +150,10 @@ async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NO
149
150
  });
150
151
  await user.connectMQTT();
151
152
  }
152
- // 2. Register (if needed) and subscribe before the shadow may exist
153
- if (!alreadyRegistered) {
154
- NodeMQTTOrchestrator.NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
155
- }
153
+ // 2. Bind to the membership shadow and subscribe before the shadow may exist.
154
+ // Always registerNode so a leftover longer/stale binding for the same nodeId
155
+ // is rebound to constructShadowName(groupId, subgroupIds).
156
+ NodeMQTTOrchestrator.NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
156
157
  await NodeMQTTOrchestrator.NodeMQTTOrchestrator.subscribeToNode(nodeId, onUpdate);
157
158
  logger.debug("Subscribed to node shadow for online wait", {
158
159
  nodeId,
@@ -1 +1 @@
1
- {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -22,6 +22,12 @@ class ESPDevice {
22
22
  name;
23
23
  transport;
24
24
  security;
25
+ /**
26
+ * What a completed association produced, kept so `retryNetworkCredentials`
27
+ * can resume the flow instead of repeating it. Set once verification
28
+ * succeeds; `undefined` until then.
29
+ */
30
+ provisionResumeState;
25
31
  constructor(deviceConfig) {
26
32
  this.name = deviceConfig.name;
27
33
  this.transport = deviceConfig.transport;
@@ -1 +1 @@
1
- {"version":3,"file":"ESPDevice.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPDevice.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}