@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.
Files changed (117) 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/ESPRMNeoBase.js +1 -1
  6. package/dist/cjs/ESPRMNeoNode.js +33 -14
  7. package/dist/cjs/ESPRMNeoNode.js.map +1 -1
  8. package/dist/cjs/index.js +14 -0
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/methods/ESPDevice/Provision.js +7 -0
  11. package/dist/cjs/methods/ESPDevice/Provision.js.map +1 -1
  12. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js +26 -0
  13. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  14. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js +46 -0
  15. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  16. package/dist/cjs/methods/ESPRMNeoNode/Sync.js +4 -1
  17. package/dist/cjs/methods/ESPRMNeoNode/Sync.js.map +1 -1
  18. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js +8 -9
  19. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  20. package/dist/cjs/proto/esp_rmaker_chal_resp.js +5 -0
  21. package/dist/cjs/proto/esp_rmaker_chal_resp.js.map +1 -1
  22. package/dist/cjs/proto/rmaker_local_ctrl.js +273 -0
  23. package/dist/cjs/proto/rmaker_local_ctrl.js.map +1 -0
  24. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  25. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  26. package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
  27. package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
  28. package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js +183 -116
  29. package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
  30. package/dist/cjs/services/ESPTransport/LocalControlSession.js +62 -0
  31. package/dist/cjs/services/ESPTransport/LocalControlSession.js.map +1 -0
  32. package/dist/cjs/services/NodeMQTTOrchestrator.js +2 -5
  33. package/dist/cjs/services/NodeMQTTOrchestrator.js.map +1 -1
  34. package/dist/cjs/types/transport.js +24 -0
  35. package/dist/cjs/types/transport.js.map +1 -1
  36. package/dist/cjs/utils/constants.js +80 -4
  37. package/dist/cjs/utils/constants.js.map +1 -1
  38. package/dist/cjs/utils/eventSubscriptionUtils.js +48 -4
  39. package/dist/cjs/utils/eventSubscriptionUtils.js.map +1 -1
  40. package/dist/cjs/utils/nodeUtils.js +12 -1
  41. package/dist/cjs/utils/nodeUtils.js.map +1 -1
  42. package/dist/cjs/utils/waitForNodeOnline.js +7 -6
  43. package/dist/cjs/utils/waitForNodeOnline.js.map +1 -1
  44. package/dist/esm/ESPDevice.js +6 -0
  45. package/dist/esm/ESPDevice.js.map +1 -1
  46. package/dist/esm/ESPRMNeoBase.js +1 -1
  47. package/dist/esm/ESPRMNeoNode.js +33 -14
  48. package/dist/esm/ESPRMNeoNode.js.map +1 -1
  49. package/dist/esm/index.js +4 -2
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/methods/ESPDevice/Provision.js +7 -0
  52. package/dist/esm/methods/ESPDevice/Provision.js.map +1 -1
  53. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js +24 -0
  54. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  55. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js +44 -0
  56. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  57. package/dist/esm/methods/ESPRMNeoNode/Sync.js +4 -1
  58. package/dist/esm/methods/ESPRMNeoNode/Sync.js.map +1 -1
  59. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js +9 -10
  60. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  61. package/dist/esm/proto/esp_rmaker_chal_resp.js +5 -0
  62. package/dist/esm/proto/esp_rmaker_chal_resp.js.map +1 -1
  63. package/dist/esm/proto/rmaker_local_ctrl.js +271 -0
  64. package/dist/esm/proto/rmaker_local_ctrl.js.map +1 -0
  65. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  66. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  67. package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
  68. package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
  69. package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js +184 -117
  70. package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
  71. package/dist/esm/services/ESPTransport/LocalControlSession.js +59 -0
  72. package/dist/esm/services/ESPTransport/LocalControlSession.js.map +1 -0
  73. package/dist/esm/services/NodeMQTTOrchestrator.js +2 -5
  74. package/dist/esm/services/NodeMQTTOrchestrator.js.map +1 -1
  75. package/dist/esm/types/transport.js +24 -1
  76. package/dist/esm/types/transport.js.map +1 -1
  77. package/dist/esm/utils/constants.js +74 -5
  78. package/dist/esm/utils/constants.js.map +1 -1
  79. package/dist/esm/utils/eventSubscriptionUtils.js +49 -5
  80. package/dist/esm/utils/eventSubscriptionUtils.js.map +1 -1
  81. package/dist/esm/utils/nodeUtils.js +12 -1
  82. package/dist/esm/utils/nodeUtils.js.map +1 -1
  83. package/dist/esm/utils/waitForNodeOnline.js +7 -6
  84. package/dist/esm/utils/waitForNodeOnline.js.map +1 -1
  85. package/dist/types/ESPDevice.d.ts +10 -0
  86. package/dist/types/ESPRMNeoBase.d.ts +1 -1
  87. package/dist/types/ESPRMNeoNode.d.ts +17 -5
  88. package/dist/types/methods/ESPDevice/ResetWifiStatus.d.ts +18 -0
  89. package/dist/types/methods/ESPDevice/RetryNetworkCredentials.d.ts +22 -0
  90. package/dist/types/methods/ESPDevice/index.d.ts +2 -0
  91. package/dist/types/methods/ESPRMNeoUser/MQTT/AssumeRole.d.ts +25 -15
  92. package/dist/types/proto/rmaker_local_ctrl.d.ts +137 -0
  93. package/dist/types/services/ESPRMNeoMqtt/ESPRMNeoMqtt.d.ts +10 -0
  94. package/dist/types/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.d.ts +4 -3
  95. package/dist/types/services/ESPTransport/ESPLocalControlTransport.d.ts +70 -23
  96. package/dist/types/services/ESPTransport/LocalControlSession.d.ts +24 -0
  97. package/dist/types/services/NodeMQTTOrchestrator.d.ts +2 -2
  98. package/dist/types/services/interfaces/MQTTTransport.d.ts +10 -0
  99. package/dist/types/types/discovery.d.ts +1 -1
  100. package/dist/types/types/localControl.d.ts +26 -1
  101. package/dist/types/types/provision.d.ts +10 -0
  102. package/dist/types/types/transport.d.ts +24 -2
  103. package/dist/types/utils/baseUtils.d.ts +1 -1
  104. package/dist/types/utils/constants.d.ts +74 -5
  105. package/dist/types/utils/eventSubscriptionUtils.d.ts +19 -3
  106. package/dist/types/utils/waitForNodeOnline.d.ts +9 -3
  107. package/package.json +1 -1
  108. package/dist/cjs/proto/constants.js +0 -24
  109. package/dist/cjs/proto/constants.js.map +0 -1
  110. package/dist/cjs/proto/esp_local_ctrl.js +0 -787
  111. package/dist/cjs/proto/esp_local_ctrl.js.map +0 -1
  112. package/dist/esm/proto/constants.js +0 -24
  113. package/dist/esm/proto/constants.js.map +0 -1
  114. package/dist/esm/proto/esp_local_ctrl.js +0 -758
  115. package/dist/esm/proto/esp_local_ctrl.js.map +0 -1
  116. package/dist/types/proto/constants.d.ts +0 -15
  117. package/dist/types/proto/esp_local_ctrl.d.ts +0 -283
@@ -1,26 +1,70 @@
1
1
  import { ESPDiscoveryManager } from '../services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js';
2
2
  import { subscribeNodeUpdates } from '../services/NodeUpdatesBus.js';
3
- import { ESPTransportMode } from '../types/transport.js';
3
+ import { ESPLocalControlProtocol, ESPTransportMode } from '../types/transport.js';
4
+ import { RMakerLocalCtrlCapability, RMakerLocalCtrlTxtKey } from './constants.js';
4
5
 
5
6
  /*
6
7
  * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
7
8
  *
8
9
  * SPDX-License-Identifier: Apache-2.0
9
10
  */
10
- /** Maps a default local-discovery hit into the client-facing payload shape. */
11
+ /**
12
+ * Splits the mDNS `cap` TXT record (`"local_ctrl,ch_resp"`) into tokens.
13
+ * Returns `undefined` when the record is absent, which older firmware omits.
14
+ */
15
+ function parseCapabilities(txt) {
16
+ const raw = txt?.[RMakerLocalCtrlTxtKey.CAP];
17
+ if (typeof raw !== "string")
18
+ return undefined;
19
+ const capabilities = raw
20
+ .split(",")
21
+ .map((capability) => capability.trim())
22
+ .filter(Boolean);
23
+ return capabilities.length ? capabilities : undefined;
24
+ }
25
+ /**
26
+ * Maps a default local-discovery hit into the client-facing payload shape.
27
+ *
28
+ * The `_esp_rmaker_ctrl._tcp` instance is also advertised by nodes that
29
+ * only serve challenge-response (on-network user-node association). Those are
30
+ * not reachable for param control, so a hit whose `cap` TXT record excludes
31
+ * `local_ctrl` maps to `undefined` and is dropped rather than registered as a
32
+ * local transport. A hit with no `cap` record is treated as control-capable.
33
+ *
34
+ * @param info - Raw adapter result (`nodeId`, `baseUrl`, and `txt` when the
35
+ * platform resolved TXT records).
36
+ * @returns The payload to deliver, or `undefined` to skip this hit.
37
+ */
11
38
  function toDiscoveredNodeData(info) {
39
+ const capabilities = parseCapabilities(info.txt);
40
+ if (capabilities &&
41
+ !capabilities.includes(RMakerLocalCtrlCapability.LOCAL_CTRL)) {
42
+ return undefined;
43
+ }
12
44
  return {
13
45
  nodeId: info.nodeId,
14
46
  transportDetails: {
15
47
  type: ESPTransportMode.local,
16
- metadata: { baseUrl: info.baseUrl },
48
+ metadata: {
49
+ baseUrl: info.baseUrl,
50
+ protocol: ESPLocalControlProtocol.rmakerLocalCtrl,
51
+ ...(capabilities && { capabilities }),
52
+ },
17
53
  },
18
54
  };
19
55
  }
20
- /** Starts LAN discovery; each hit is mapped then passed to `onDiscovered`. */
56
+ /**
57
+ * Starts LAN discovery; each hit is mapped then passed to `onDiscovered`.
58
+ * Hits that are not control-capable (see {@link toDiscoveredNodeData}) are
59
+ * skipped.
60
+ */
21
61
  function startLocalDiscovery(onDiscovered) {
22
62
  const manager = new ESPDiscoveryManager();
23
- manager.startDiscovery((info) => onDiscovered(toDiscoveredNodeData(info)));
63
+ manager.startDiscovery((info) => {
64
+ const data = toDiscoveredNodeData(info);
65
+ if (data)
66
+ onDiscovered(data);
67
+ });
24
68
  return { stop: () => manager.stopDiscovery() };
25
69
  }
26
70
  /** Forwards process-wide node param updates to `onUpdate`. */
@@ -1 +1 @@
1
- {"version":3,"file":"eventSubscriptionUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"eventSubscriptionUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -26,7 +26,18 @@ async function readLocalNodeConfig(nodeId) {
26
26
  /** Best-effort persist of node config to local storage. */
27
27
  async function writeLocalNodeConfig(nodeId, config) {
28
28
  try {
29
- await ESPRMNeoStorage.setNodeConfig(nodeId, config);
29
+ let toStore = config;
30
+ // Cloud config omits connectivity — keep the last cached status when writing.
31
+ if (!config.connectivity_status) {
32
+ const prev = await ESPRMNeoStorage.getNodeConfig(nodeId);
33
+ if (prev?.connectivity_status) {
34
+ toStore = {
35
+ ...config,
36
+ connectivity_status: prev.connectivity_status,
37
+ };
38
+ }
39
+ }
40
+ await ESPRMNeoStorage.setNodeConfig(nodeId, toStore);
30
41
  }
31
42
  catch (error) {
32
43
  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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -63,11 +63,12 @@ function isNodeOnlineFromShadowPayload(payload) {
63
63
  * @throws {ESPProvError} If `timeoutMs` elapses without the node reporting online.
64
64
  * @throws {Error} If MQTT connection or subscription fails.
65
65
  */
66
- async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
67
- const shadowName = constructShadowName(groupId);
66
+ async function waitForNodeOnline({ nodeId, groupId, subgroupIds, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
67
+ const shadowName = constructShadowName(groupId, subgroupIds ?? []);
68
68
  logger.info("Waiting for node to come online", {
69
69
  nodeId,
70
70
  groupId,
71
+ subgroupIds: subgroupIds ?? [],
71
72
  shadowName,
72
73
  timeoutMs,
73
74
  pollIntervalMs,
@@ -147,10 +148,10 @@ async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NO
147
148
  });
148
149
  await user.connectMQTT();
149
150
  }
150
- // 2. Register (if needed) and subscribe before the shadow may exist
151
- if (!alreadyRegistered) {
152
- NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
153
- }
151
+ // 2. Bind to the membership shadow and subscribe before the shadow may exist.
152
+ // Always registerNode so a leftover longer/stale binding for the same nodeId
153
+ // is rebound to constructShadowName(groupId, subgroupIds).
154
+ NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
154
155
  await NodeMQTTOrchestrator.subscribeToNode(nodeId, onUpdate);
155
156
  logger.debug("Subscribed to node shadow for online wait", {
156
157
  nodeId,
@@ -1 +1 @@
1
- {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -9,5 +9,15 @@ export declare class ESPDevice implements ESPDeviceInterface {
9
9
  name: string;
10
10
  transport: string;
11
11
  security: number;
12
+ /**
13
+ * What a completed association produced, kept so `retryNetworkCredentials`
14
+ * can resume the flow instead of repeating it. Set once verification
15
+ * succeeds; `undefined` until then.
16
+ */
17
+ provisionResumeState?: {
18
+ nodeId: string;
19
+ groupId: string;
20
+ options?: Record<string, any>;
21
+ };
12
22
  constructor(deviceConfig: ESPDeviceInterface);
13
23
  }
@@ -115,7 +115,7 @@ export declare class ESPRMNeoBase {
115
115
  static getProvisionAdapter(): ESPProvisionAdapterInterface | undefined;
116
116
  /**
117
117
  * Sets the local control adapter used to talk to nodes over the LAN via
118
- * the `esp_local_ctrl` protocol.
118
+ * the `rmaker_local_ctrl` protocol.
119
119
  *
120
120
  * @param adapter - Adapter implementing {@link ESPLocalControlAdapterInterface}.
121
121
  * @throws {Error} If the SDK is not initialized or the adapter is invalid.
@@ -22,11 +22,16 @@ export declare class ESPRMNeoNode {
22
22
  subgroupIds: string[];
23
23
  devices: ESPRMNeoDevice[];
24
24
  services: ESPRMNeoService[];
25
+ /**
26
+ * Last-known connectivity. Seeded from cached `connectivity_status` when
27
+ * present (cloud config does not provide it); otherwise starts offline until
28
+ * an MQTT shadow `online` update arrives.
29
+ */
25
30
  connectivityStatus: ESPRMNeoConnectivityStatusInterface;
26
31
  /**
27
32
  * Transports currently usable for this node, keyed by mode.
28
- * - `mqtt` (cloud) is added when the node reports online and removed when
29
- * it goes offline (via shadow updates or seeded `connectivity_status`).
33
+ * - `mqtt` (cloud) is added when the node is connected and removed when it
34
+ * goes offline (from cached `connectivity_status` or shadow updates).
30
35
  * - `local` is added/removed at runtime by a `localDiscovery` subscriber as the
31
36
  * node appears/disappears on the LAN (via {@link addTransport}/{@link removeTransport}).
32
37
  * Apps may also add custom string-keyed entries (pair with
@@ -132,11 +137,18 @@ export declare class ESPRMNeoNode {
132
137
  */
133
138
  private processNodeUpdate;
134
139
  /**
135
- * Updates {@link connectivityStatus} and MQTT transport availability when the
136
- * shadow reports `online`. Partial updates without `online` are ignored so
137
- * an existing status is not cleared.
140
+ * Updates {@link connectivityStatus} when the shadow reports `online`, then
141
+ * syncs MQTT transport availability. Partial updates without `online` are
142
+ * ignored so an existing status is not cleared.
138
143
  */
139
144
  private applyConnectivityStatus;
145
+ /**
146
+ * Registers the MQTT transport only while {@link connectivityStatus} reports
147
+ * the node as connected; removes it when offline.
148
+ */
149
+ private syncMqttTransportAvailability;
150
+ /** Persists {@link wireConfig} (including last-known connectivity) to storage. */
151
+ private persistWireConfig;
140
152
  /** When cloud `ncfg_ver` changes, refetch config and persist the new marker. */
141
153
  private refreshConfigIfNcfgChanged;
142
154
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Augments the ESPDevice class with the `resetWifiStatus` method.
3
+ */
4
+ declare module "../../ESPDevice" {
5
+ interface ESPDevice {
6
+ /**
7
+ * Asks the device to clear its Wi-Fi state over the open provisioning session.
8
+ *
9
+ * Sent on the network-control endpoint, so it needs an established session and
10
+ * leaves the user-node association intact — only Wi-Fi credentials are cleared.
11
+ * That is what makes `retryNetworkCredentials` possible after a wrong password,
12
+ * instead of making the user factory reset the board.
13
+ * @returns A promise resolving to `true` when the device acknowledged the reset.
14
+ */
15
+ resetWifiStatus(): Promise<boolean>;
16
+ }
17
+ }
18
+ export {};
@@ -0,0 +1,22 @@
1
+ import { ESPProvResponse } from "../../types/provision";
2
+ /**
3
+ * Augments the ESPDevice class with the `retryNetworkCredentials` method.
4
+ */
5
+ declare module "../../ESPDevice" {
6
+ interface ESPDevice {
7
+ /**
8
+ * Re-sends Wi-Fi credentials after `resetWifiStatus`, resuming the flow the
9
+ * first attempt started rather than repeating its association.
10
+ *
11
+ * Sending credentials alone is not enough: the online wait and the success
12
+ * report follow them and need the node id the association produced, which
13
+ * only this SDK holds.
14
+ * @param ssid - The SSID, unchanged from the first attempt.
15
+ * @param passphrase - The corrected Wi-Fi password.
16
+ * @param onProgress - Progress callback; emits the same messages as `provision`.
17
+ * @returns A promise resolving to the node id.
18
+ * @throws {ESPProvError} When no association has completed on this device yet.
19
+ */
20
+ retryNetworkCredentials(ssid: string, passphrase: string, onProgress: (message: ESPProvResponse) => void): Promise<string>;
21
+ }
22
+ }
@@ -8,6 +8,8 @@ import "./SetProofOfPossession";
8
8
  import "./SendData";
9
9
  import "./InitializeSession";
10
10
  import "./SetNetworkCredentials";
11
+ import "./ResetWifiStatus";
12
+ import "./RetryNetworkCredentials";
11
13
  import "./InitiateUserNodeMapping";
12
14
  import "./VerifyUserNodeMapping";
13
15
  import "./StartAssistedClaiming";
@@ -5,29 +5,39 @@ import { AWSCredentials } from "../../../types/input";
5
5
  declare module "../../../ESPRMNeoUser" {
6
6
  interface ESPRMNeoUser {
7
7
  /**
8
- * Exchanges identity (temporary) credentials for role-based IoT/MQTT credentials.
8
+ * Exchanges identity (temporary) credentials for role-based credentials.
9
9
  *
10
- * This is not session extension. The backend uses the provided temporary credentials
11
- * (from getTemporaryAWSCredentials, which are identity-pool–style credentials) to assume
12
- * an IAM role that has IoT/MQTT permissions. It returns a different set of credentials
13
- * (assume-role credentials) used for MQTT connection and device shadows.
10
+ * Two modes chosen by whether `options.services` is set:
11
+ * 1. Default (no `options.services`): POST /v1/assumed-roles. Returns
12
+ * IoT/MQTT credentials scoped to the user's group/subgroup membership.
13
+ * Used for MQTT WebSocket auth.
14
+ * 2. Per-node services (`options.services` with `nodeId`):
15
+ * Same route, with `services` and `node_id` in the body. Returns
16
+ * credentials scoped to that single node for the listed services
17
+ * (currently `s3` and/or `kvs`). The response contains only the
18
+ * requested service permissions — no IoT/MQTT statements.
14
19
  *
15
- * Flow: temporary credentials (auth) POST /v1/assumed-roles → assume-role credentials (IoT).
16
- *
17
- * @param accessKey - AWS access key from getTemporaryAWSCredentials (identity credentials).
20
+ * @param accessKey - AWS access key from getTemporaryAWSCredentials.
18
21
  * @param secretKey - AWS secret key from getTemporaryAWSCredentials.
19
22
  * @param sessionToken - AWS session token from getTemporaryAWSCredentials.
20
- * @param options - Optional parameters merged into the request body.
21
- * @param options.include - Services to include in the session policy (e.g., ["s3"], ["kvs"], ["s3", "kvs"]).
22
- * When omitted, only IoT/MQTT permissions are granted.
23
- * @returns A promise that resolves to AWSCredentials (role-based; used for MQTT).
24
- * @throws Error if the API call fails or if the response is invalid.
23
+ * @param options - Optional. To request per-node service credentials, pass
24
+ * `services` and `nodeId`.
25
+ * @returns A promise that resolves to AWSCredentials.
26
+ * @throws Error if `services` is set without `nodeId`, or if the API call fails.
25
27
  */
26
28
  assumeRole(accessKey: string, secretKey: string, sessionToken: string, options?: AssumeRoleOptions): Promise<AWSCredentials>;
27
29
  }
28
30
  }
31
+ /** Per-node service credentials supported by the backend. */
32
+ export type AssumeRoleService = "s3" | "kvs";
29
33
  /** Options for the assumeRole method. */
30
34
  export interface AssumeRoleOptions {
31
- /** Services to include in the session policy (e.g., ["s3"], ["kvs"]). */
32
- include?: string[];
35
+ /**
36
+ * Services to scope credentials to (e.g., `["s3"]`, `["kvs"]`, `["s3", "kvs"]`).
37
+ * When set, `nodeId` is required and the returned credentials contain only the
38
+ * listed service permissions for that single node.
39
+ */
40
+ services?: AssumeRoleService[];
41
+ /** Required when `services` is set. The node these credentials apply to. */
42
+ nodeId?: string;
33
43
  }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Codec for the `rmaker_local_ctrl` endpoint protocol, mirroring the firmware's
3
+ * `local_ctrl.proto` schema.
4
+ *
5
+ * Hand-rolled like {@link ClaimingProtoHelper} rather than generated: the
6
+ * schema is small, and generated `google-protobuf` modules cannot be pulled
7
+ * into a downstream Metro/RN bundle (see the note in `utils/export.ts`).
8
+ *
9
+ * Field numbers are the wire contract with deployed firmware — treat them as
10
+ * frozen:
11
+ * - `RMakerLocalCtrlPayload`: 1 = msg (varint), 10 = cmdGetData, 11 = respGetData
12
+ * - `CmdGetData`: 1 = DataType (varint), 2 = Offset (varint),
13
+ * 3 = Timestamp (varint), 4 = HasTimestamp (varint)
14
+ * - `RespGetData`: 1 = Status (varint), 2 = Buf
15
+ * - `PayloadBuf`: 1 = Offset (varint), 2 = Payload (bytes), 3 = TotalLen (varint)
16
+ *
17
+ * `Timestamp` / `HasTimestamp` are reserved for a future signed-response
18
+ * extension and are ignored by current firmware, so the encoder omits them.
19
+ *
20
+ * Only `get_params` / `get_config` use this schema; `set_params` carries raw
21
+ * JSON on the wire.
22
+ */
23
+ /**
24
+ * Status returned by the device for a data read.
25
+ */
26
+ export declare enum RMakerLocalCtrlStatus {
27
+ Success = 0,
28
+ Fail = 1,
29
+ InvalidParam = 2,
30
+ NoMemory = 3
31
+ }
32
+ /**
33
+ * Selects which document a read targets.
34
+ */
35
+ export declare enum RMakerLocalCtrlDataType {
36
+ /** The node's params JSON, as served by `get_params`. */
37
+ TypeParams = 0,
38
+ /** The node's config JSON, as served by `get_config`. */
39
+ TypeConfig = 1
40
+ }
41
+ /**
42
+ * Message type discriminator carried on `RMakerLocalCtrlPayload.msg`.
43
+ */
44
+ export declare enum RMakerLocalCtrlMsgType {
45
+ TypeCmdGetData = 0,
46
+ TypeRespGetData = 1
47
+ }
48
+ /**
49
+ * One fragment of a larger document.
50
+ */
51
+ export interface PayloadBuf {
52
+ /** Byte offset of this fragment within the document. */
53
+ offset: number;
54
+ /** Fragment bytes (up to 200 per response). */
55
+ payload: Uint8Array;
56
+ /** Full document length in bytes. */
57
+ totalLen: number;
58
+ }
59
+ /**
60
+ * Device response to a data read.
61
+ */
62
+ export interface RespGetData {
63
+ /** Read status. */
64
+ status: RMakerLocalCtrlStatus;
65
+ /** Fragment carried by this response. */
66
+ buf?: PayloadBuf;
67
+ }
68
+ /**
69
+ * Envelope exchanged on the `get_params` / `get_config` endpoints.
70
+ */
71
+ export interface RMakerLocalCtrlPayload {
72
+ /** Message type. */
73
+ msg: RMakerLocalCtrlMsgType;
74
+ /** Response payload (device → app). */
75
+ respGetData?: RespGetData;
76
+ }
77
+ /**
78
+ * Encoder/decoder for `rmaker_local_ctrl` frames.
79
+ */
80
+ export declare class RMakerLocalCtrlProtoHelper {
81
+ /**
82
+ * Builds a `CmdGetData` request for one fragment.
83
+ *
84
+ * An `offset` of 0 makes the device (re)generate and cache the document;
85
+ * subsequent offsets are served from that cache.
86
+ *
87
+ * @param dataType - Document to read.
88
+ * @param offset - Byte offset to read from.
89
+ * @returns The serialized request.
90
+ */
91
+ static createGetDataRequest(dataType: RMakerLocalCtrlDataType, offset: number): Uint8Array;
92
+ /**
93
+ * Parses a `RespGetData` frame from the device.
94
+ *
95
+ * @param data - Raw response bytes.
96
+ * @returns The parsed payload. Absent fields keep their proto3 defaults.
97
+ */
98
+ static parseGetDataResponse(data: Uint8Array): RMakerLocalCtrlPayload;
99
+ /**
100
+ * Whether the device reported a successful read.
101
+ *
102
+ * @param response - Parsed response.
103
+ */
104
+ static isSuccess(response: RMakerLocalCtrlPayload): boolean;
105
+ /**
106
+ * Returns the device's status as its enum name, for diagnostics.
107
+ *
108
+ * @param response - Parsed response.
109
+ * @returns The status name, or `"Unknown"` when absent.
110
+ */
111
+ static getStatus(response: RMakerLocalCtrlPayload): string;
112
+ /**
113
+ * Offset the device answered with, which must match the requested offset.
114
+ *
115
+ * @param response - Parsed response.
116
+ */
117
+ static getOffset(response: RMakerLocalCtrlPayload): number;
118
+ /**
119
+ * Full document length reported by the device.
120
+ *
121
+ * @param response - Parsed response.
122
+ */
123
+ static getTotalLen(response: RMakerLocalCtrlPayload): number;
124
+ /**
125
+ * Fragment bytes carried by the response.
126
+ *
127
+ * @param response - Parsed response.
128
+ */
129
+ static getPayload(response: RMakerLocalCtrlPayload): Uint8Array;
130
+ private static concat;
131
+ private static encodeVarint;
132
+ private static readVarint;
133
+ /** Advances past a field this codec does not read. */
134
+ private static skipField;
135
+ private static parseRespGetData;
136
+ private static parsePayloadBuf;
137
+ }
@@ -58,6 +58,16 @@ export declare class ESPRMNeoMqtt implements MQTTTransport {
58
58
  * Returns whether the underlying MQTT client is connected.
59
59
  */
60
60
  isConnected(): Promise<boolean>;
61
+ /**
62
+ * Registers a transport connection-status listener when the underlying
63
+ * adapter supports it; otherwise returns a no-op unsubscribe.
64
+ *
65
+ * @param callback - Invoked with `{ connected }` on status changes
66
+ * @returns Unsubscribe function
67
+ */
68
+ onConnectionStatusChange(callback: (status: {
69
+ connected: boolean;
70
+ }) => void): () => void;
61
71
  /**
62
72
  * Publish a message to a topic.
63
73
  *
@@ -4,9 +4,10 @@ import { DiscoveryParamsInterface, ESPDiscoveryCallback } from "../../../types/d
4
4
  * adapter (see {@link ESPRMNeoBase.setLocalDiscoveryAdapter}).
5
5
  *
6
6
  * - With no discovery config, the default local protocol is used
7
- * (mDNS service `_esp_local_ctrl._tcp.` in the `local` domain).
8
- * - A custom {@link DiscoveryParamsInterface} can be supplied for other
9
- * discovery protocols.
7
+ * (mDNS service `_esp_rmaker_ctrl._tcp.` in the `local` domain — the
8
+ * service advertised by RainMaker Neo firmware).
9
+ * - A custom {@link DiscoveryParamsInterface} can be supplied to browse any
10
+ * other service type.
10
11
  */
11
12
  declare class ESPDiscoveryManager {
12
13
  /** Discovery parameters (service type / domain) passed to the adapter. */
@@ -2,37 +2,84 @@ import type { ESPRMNeoNode } from "../../ESPRMNeoNode";
2
2
  import { ESPAPIResponse } from "../../types/output";
3
3
  import { ESPTransportConfig, ESPTransportInterface } from "../../types/transport";
4
4
  /**
5
- * Built-in `local` transport. Communicates with the node over the LAN through
6
- * the app-supplied {@link ESPRMNeoBase.ESPLocalControlAdapter} using the
7
- * `esp_local_ctrl` protobuf protocol. Connection metadata (`baseUrl`,
8
- * `securityType`, `pop`, and `username` for sec2) is supplied via the transport
9
- * config by {@link delegatedTransportHandler}.
5
+ * The built-in `local` transport, speaking the `rmaker_local_ctrl` endpoint
6
+ * protocol over the app-supplied {@link ESPRMNeoBase.ESPLocalControlAdapter}:
7
+ *
8
+ * - `set_params` carries the same raw JSON body as a cloud set-params call and
9
+ * answers `{"status":"success"}` / `{"status":"fail","description":…}`.
10
+ * - `get_params` / `get_config` exchange protobuf `CmdGetData`/`RespGetData` and
11
+ * are fragmented — the client pulls fixed-size chunks by offset until
12
+ * `TotalLen` is covered.
13
+ *
14
+ * Connection metadata (`baseUrl`, `securityType`, `pop`, and `username` for
15
+ * sec2) is supplied via the transport config by {@link
16
+ * delegatedTransportHandler}; security 0 is not offered by this protocol.
10
17
  */
11
18
  declare class ESPLocalControlTransport implements ESPTransportInterface {
12
- private payload;
13
19
  metadata: Record<string, any>;
14
- propertyInfo: Record<string, any>;
15
20
  constructor(transportConfig: ESPTransportConfig);
16
21
  private get adapter();
22
+ private ensureConnected;
17
23
  /**
18
- * Connects to the node, retrying on failure. RMNeo's adapter resolves on a
19
- * successful connection and rejects otherwise, so a resolved call is treated
20
- * as connected.
24
+ * Applies params over `set_params`.
25
+ *
26
+ * @param payload - `{ node_id, payload }`, where `payload` is the
27
+ * `{ <deviceOrServiceName>: { <paramName>: value } }` map to write.
28
+ * @throws {Error} When the device reports a non-success status.
21
29
  */
22
- private connectWithRetry;
23
- private ensureConnected;
24
30
  setParam(payload: Record<string, any>, _nodeRef?: ESPRMNeoNode): Promise<ESPAPIResponse>;
31
+ /**
32
+ * Reads the node's full params document over `get_params`.
33
+ *
34
+ * @param payload - `{ node_id }` identifying the node to read.
35
+ * @returns The params JSON, keyed by device/service name.
36
+ */
25
37
  getParams(payload: Record<string, any>, _nodeRef?: ESPRMNeoNode): Promise<Record<string, any>>;
26
- private setProperty;
27
- private buildSetPropertyRequest;
28
- private processSetPropertyResponse;
29
- /** Fetches the property count, then reads each property value into propertyInfo. */
30
- private getPropertyInfo;
31
- private fetchPropertyCount;
32
- private buildGetPropertyCountRequest;
33
- private processGetPropertyCountResponse;
34
- private fetchPropertyValue;
35
- private buildGetPropertyValueRequest;
36
- private processGetPropertyValueResponse;
38
+ /**
39
+ * Reads the node's config document over `get_config`. Not part of
40
+ * {@link ESPTransportInterface} — the node config normally comes from the
41
+ * cloud; this serves LAN-only flows.
42
+ *
43
+ * @param nodeId - Node to read from.
44
+ * @returns The node config JSON.
45
+ */
46
+ getConfig(nodeId: string): Promise<Record<string, any>>;
47
+ /**
48
+ * Validates a `set_params` raw-JSON response.
49
+ *
50
+ * @throws {Error} When the body is unparseable or reports a failure.
51
+ */
52
+ private assertSetParamsAccepted;
53
+ /**
54
+ * Pulls a fragmented document and parses it as JSON, serialized against every
55
+ * other fragmented read of the same node.
56
+ *
57
+ * The device holds **one global transfer cache**, not one per session: an
58
+ * offset-0 request regenerates it and it is freed after the last fragment. So
59
+ * a `getParams()` racing a `getConfig()` on the same node would clobber the
60
+ * other — the second offset-0 regenerates the cache mid-transfer, and the
61
+ * first read's next fragment comes back `Fail` (or, worse, carries bytes from
62
+ * the wrong document). The queue makes that interleaving impossible rather
63
+ * than detecting it after the fact.
64
+ *
65
+ * @param nodeId - Node to read from.
66
+ * @param dataType - Which document to read (params or config).
67
+ * @param endpoint - Endpoint serving that document.
68
+ * @throws {Error} When the device reports a failure, the response is
69
+ * malformed, or a fragment makes no forward progress.
70
+ */
71
+ private readJsonDocument;
72
+ /** The client-pull loop itself; always reached via {@link readJsonDocument}. */
73
+ private pullJsonDocument;
74
+ private buildGetDataRequest;
75
+ /**
76
+ * Parses one `RespGetData` and checks it answers the requested offset.
77
+ *
78
+ * @param response - Base64 protobuf response from the adapter.
79
+ * @param requestedOffset - Offset asked for, used to detect a desynced pull.
80
+ */
81
+ private processGetDataResponse;
82
+ /** Joins the pulled fragments and parses the result as JSON. */
83
+ private parseJsonFragments;
37
84
  }
38
85
  export { ESPLocalControlTransport };
@@ -0,0 +1,24 @@
1
+ import type { ESPLocalControlAdapterInterface, ESPLocalControlSessionOptions } from "../../types/localControl";
2
+ /**
3
+ * Returns the app-supplied local control adapter.
4
+ *
5
+ * @throws {Error} When no adapter was registered on {@link ESPRMNeoBase}.
6
+ */
7
+ export declare function getLocalControlAdapter(): ESPLocalControlAdapterInterface;
8
+ /**
9
+ * Ensures a usable local-control session for `nodeId`, connecting (with
10
+ * retries) when the adapter reports none.
11
+ *
12
+ * The adapter resolves on a successful handshake and rejects otherwise, so a
13
+ * resolved `connect` is treated as connected.
14
+ *
15
+ * @param adapter - Adapter to drive.
16
+ * @param nodeId - Node to connect to.
17
+ * @param metadata - Local transport metadata (`baseUrl`, `securityType`, `pop`,
18
+ * `username`).
19
+ * @param options - Session endpoints for the protocol in use; omitted for the
20
+ * adapter's built-in default paths.
21
+ * @param maxRetries - Connection attempts before failing.
22
+ * @throws {Error} When every connection attempt fails.
23
+ */
24
+ export declare function ensureLocalControlSession(adapter: ESPLocalControlAdapterInterface, nodeId: string, metadata: Record<string, any>, options?: ESPLocalControlSessionOptions, maxRetries?: number): Promise<void>;
@@ -76,8 +76,8 @@ export declare class NodeMQTTOrchestrator {
76
76
  /**
77
77
  * Registers a node with the orchestrator.
78
78
  * Must be called before subscribeToNode, getParams, getShadow, etc.
79
- * On shadow rename: clears the old binding; ignores shorter/incomplete names.
80
- * Shrink membership via {@link unregisterNode} first.
79
+ * On shadow rename: clears the old binding, then installs the new name
80
+ * (including shrinks to a shorter membership shadow).
81
81
  *
82
82
  * @param nodeId - The device/node identifier.
83
83
  * @param shadowName - The named shadow (e.g. `params-groupId-subgroupId`).