zwave-js 15.24.3 → 15.25.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 (53) hide show
  1. package/build/cjs/lib/_version.d.ts +1 -1
  2. package/build/cjs/lib/_version.js +1 -1
  3. package/build/cjs/lib/_version.js.map +1 -1
  4. package/build/cjs/lib/driver/Driver.d.ts +9 -1
  5. package/build/cjs/lib/driver/Driver.js +216 -130
  6. package/build/cjs/lib/driver/Driver.js.map +3 -3
  7. package/build/cjs/lib/driver/PartialCCSessionManager.d.ts +69 -0
  8. package/build/cjs/lib/driver/PartialCCSessionManager.js +161 -0
  9. package/build/cjs/lib/driver/PartialCCSessionManager.js.map +7 -0
  10. package/build/cjs/lib/driver/ZWaveOptions.d.ts +5 -0
  11. package/build/cjs/lib/driver/ZWaveOptions.js.map +2 -2
  12. package/build/cjs/lib/node/Node.d.ts +0 -1
  13. package/build/cjs/lib/node/Node.js +45 -12
  14. package/build/cjs/lib/node/Node.js.map +2 -2
  15. package/build/cjs/lib/node/_Types.d.ts +5 -3
  16. package/build/cjs/lib/node/_Types.js +1 -0
  17. package/build/cjs/lib/node/_Types.js.map +2 -2
  18. package/build/cjs/lib/node/mixins/90_InterviewProgress.d.ts +94 -0
  19. package/build/cjs/lib/node/mixins/90_InterviewProgress.js +280 -0
  20. package/build/cjs/lib/node/mixins/90_InterviewProgress.js.map +7 -0
  21. package/build/cjs/lib/node/mixins/index.d.ts +2 -2
  22. package/build/cjs/lib/node/mixins/index.js +2 -2
  23. package/build/cjs/lib/node/mixins/index.js.map +2 -2
  24. package/build/esm/lib/_version.d.ts +1 -1
  25. package/build/esm/lib/_version.js +1 -1
  26. package/build/esm/lib/driver/Driver.d.ts +9 -1
  27. package/build/esm/lib/driver/Driver.d.ts.map +1 -1
  28. package/build/esm/lib/driver/Driver.js +260 -149
  29. package/build/esm/lib/driver/Driver.js.map +1 -1
  30. package/build/esm/lib/driver/PartialCCSessionManager.d.ts +69 -0
  31. package/build/esm/lib/driver/PartialCCSessionManager.d.ts.map +1 -0
  32. package/build/esm/lib/driver/PartialCCSessionManager.js +169 -0
  33. package/build/esm/lib/driver/PartialCCSessionManager.js.map +1 -0
  34. package/build/esm/lib/driver/ZWaveOptions.d.ts +5 -0
  35. package/build/esm/lib/driver/ZWaveOptions.d.ts.map +1 -1
  36. package/build/esm/lib/driver/ZWaveOptions.js.map +1 -1
  37. package/build/esm/lib/node/Node.d.ts +0 -1
  38. package/build/esm/lib/node/Node.d.ts.map +1 -1
  39. package/build/esm/lib/node/Node.js +102 -21
  40. package/build/esm/lib/node/Node.js.map +1 -1
  41. package/build/esm/lib/node/_Types.d.ts +5 -3
  42. package/build/esm/lib/node/_Types.d.ts.map +1 -1
  43. package/build/esm/lib/node/_Types.js +2 -1
  44. package/build/esm/lib/node/_Types.js.map +1 -1
  45. package/build/esm/lib/node/mixins/90_InterviewProgress.d.ts +94 -0
  46. package/build/esm/lib/node/mixins/90_InterviewProgress.d.ts.map +1 -0
  47. package/build/esm/lib/node/mixins/90_InterviewProgress.js +283 -0
  48. package/build/esm/lib/node/mixins/90_InterviewProgress.js.map +1 -0
  49. package/build/esm/lib/node/mixins/index.d.ts +2 -2
  50. package/build/esm/lib/node/mixins/index.d.ts.map +1 -1
  51. package/build/esm/lib/node/mixins/index.js +2 -2
  52. package/build/esm/lib/node/mixins/index.js.map +1 -1
  53. package/package.json +9 -9
@@ -1,3 +1,3 @@
1
- export declare const PACKAGE_VERSION = "15.24.3";
1
+ export declare const PACKAGE_VERSION = "15.25.0";
2
2
  export declare const PACKAGE_NAME = "zwave-js";
3
3
  //# sourceMappingURL=_version.d.ts.map
@@ -22,7 +22,7 @@ __export(version_exports, {
22
22
  PACKAGE_VERSION: () => PACKAGE_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const PACKAGE_VERSION = "15.24.3";
25
+ const PACKAGE_VERSION = "15.25.0";
26
26
  const PACKAGE_NAME = "zwave-js";
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/_version.ts"],
4
- "sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.24.3\";\nexport const PACKAGE_NAME = \"zwave-js\";\n"],
4
+ "sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.25.0\";\nexport const PACKAGE_NAME = \"zwave-js\";\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;;AACO,MAAM,kBAAkB;AACxB,MAAM,eAAe;",
6
6
  "names": []
7
7
  }
@@ -416,8 +416,16 @@ export declare class Driver extends TypedEventTarget<DriverEventCallbacks> imple
416
416
  private isMissingNodeACK;
417
417
  private shouldRequestWakeupOnDemand;
418
418
  private partialCCSessions;
419
- private getPartialCCSession;
419
+ /**
420
+ * Determines the request a new partial CC session is the response to, if any.
421
+ * This is used to re-request the response when segments are missing.
422
+ */
423
+ private findPartialCCSessionRequest;
424
+ /** Re-requests the response a partial CC session belongs to because segments are missing */
425
+ private rerequestPartialCCSession;
420
426
  private assemblePartialCCs;
427
+ /** Refreshes the timeouts of awaited messages which a partial message may belong to */
428
+ private refreshAwaitedMessageTimers;
421
429
  private handleTransportServiceCommand;
422
430
  private handleUnsolicitedMessage;
423
431
  private handleSerialAPIStartedUnexpectedly;
@@ -62,6 +62,7 @@ var import_DriverMode = require("./DriverMode.js");
62
62
  var import_EndDeviceCLI = require("./EndDeviceCLI.js");
63
63
  var import_MessageGenerators = require("./MessageGenerators.js");
64
64
  var import_NetworkCache = require("./NetworkCache.js");
65
+ var import_PartialCCSessionManager = require("./PartialCCSessionManager.js");
65
66
  var import_Queue = require("./Queue.js");
66
67
  var import_SerialAPICommandMachine = require("./SerialAPICommandMachine.js");
67
68
  var import_StateMachineShared = require("./StateMachineShared.js");
@@ -117,7 +118,8 @@ const defaultOptions = {
117
118
  sendDataJammed: 5,
118
119
  nodeInterview: 5,
119
120
  smartStartInclusion: 5,
120
- firmwareUpdateOTW: 3
121
+ firmwareUpdateOTW: 3,
122
+ partialReports: 2
121
123
  },
122
124
  disableOptimisticValueUpdate: false,
123
125
  features: {
@@ -210,6 +212,9 @@ function checkOptions(options) {
210
212
  if (options.attempts.firmwareUpdateOTW < 1 || options.attempts.firmwareUpdateOTW > 5) {
211
213
  throw new import_core.ZWaveError(`The OTW firmware update attempts must be between 1 and 5!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions);
212
214
  }
215
+ if (options.attempts.partialReports < 0 || options.attempts.partialReports > 3) {
216
+ throw new import_core.ZWaveError(`The partial reports attempts must be between 0 and 3!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions);
217
+ }
213
218
  if (options.inclusionUserCallbacks) {
214
219
  if (!(0, import_typeguards.isObject)(options.inclusionUserCallbacks)) {
215
220
  throw new import_core.ZWaveError(`The inclusionUserCallbacks must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions);
@@ -2218,6 +2223,7 @@ class Driver extends import_shared.TypedEventTarget {
2218
2223
  ]) {
2219
2224
  timeout?.clear();
2220
2225
  }
2226
+ this.partialCCSessions.clear();
2221
2227
  }
2222
2228
  async handleSerialData(serial) {
2223
2229
  try {
@@ -2346,7 +2352,6 @@ class Driver extends import_shared.TypedEventTarget {
2346
2352
  if (!this._controller && (0, import_serialapi.containsCC)(msg))
2347
2353
  return;
2348
2354
  if (msg) {
2349
- let wasMessageLogged = false;
2350
2355
  if ((0, import_serialapi.isCommandRequest)(msg) && (0, import_serialapi.containsCC)(msg)) {
2351
2356
  if (msg.command instanceof import_cc.SecurityCCCommandEncapsulationNonceGet) {
2352
2357
  const node = this.tryGetNode(msg);
@@ -2359,29 +2364,35 @@ class Driver extends import_shared.TypedEventTarget {
2359
2364
  secondaryTags: ["partial"],
2360
2365
  direction: "inbound"
2361
2366
  });
2362
- wasMessageLogged = true;
2363
- void this.handleTransportServiceCommand(msg.command).catch(() => {
2367
+ const reassembled = await this.handleTransportServiceCommand(msg.command);
2368
+ if (!reassembled) {
2369
+ this.refreshAwaitedMessageTimers(msg);
2370
+ return;
2371
+ }
2372
+ msg.command = reassembled;
2373
+ }
2374
+ if (this.isSecurityLevelTooLow(msg.command)) {
2375
+ this.driverLog.logMessage(msg, {
2376
+ direction: "inbound",
2377
+ secondaryTags: ["discarded"]
2364
2378
  });
2379
+ return;
2365
2380
  }
2366
- if (!await this.assemblePartialCCs(msg)) {
2367
- for (const entry of this.awaitedMessages) {
2368
- if (entry.refreshPredicate?.(msg)) {
2369
- entry.timeout?.refresh();
2370
- }
2371
- }
2381
+ const completeMsg = await this.assemblePartialCCs(msg);
2382
+ if (!completeMsg) {
2383
+ this.refreshAwaitedMessageTimers(msg);
2372
2384
  return;
2373
2385
  }
2374
- if (this.isSecurityLevelTooLow(msg.command) || this.shouldDiscardCC(msg.command)) {
2375
- if (!wasMessageLogged) {
2376
- this.driverLog.logMessage(msg, {
2377
- direction: "inbound",
2378
- secondaryTags: ["discarded"]
2379
- });
2380
- }
2386
+ msg = completeMsg;
2387
+ if (this.shouldDiscardCC(completeMsg.command)) {
2388
+ this.driverLog.logMessage(msg, {
2389
+ direction: "inbound",
2390
+ secondaryTags: ["discarded"]
2391
+ });
2381
2392
  return;
2382
2393
  }
2383
2394
  try {
2384
- this.persistCCValues(msg.command);
2395
+ this.persistCCValues(completeMsg.command);
2385
2396
  } catch (e) {
2386
2397
  if ((0, import_core.isZWaveError)(e) && e.code === import_core.ZWaveErrorCodes.PacketFormat_InvalidPayload) {
2387
2398
  this.driverLog.print(`dropping CC with invalid values${typeof e.context === "string" ? ` (Reason: ${e.context})` : ""}`, "warn");
@@ -2390,19 +2401,13 @@ class Driver extends import_shared.TypedEventTarget {
2390
2401
  throw e;
2391
2402
  }
2392
2403
  }
2393
- if ((0, import_cc.isTransportServiceEncapsulation)(msg.command)) {
2394
- msg.command = msg.command.encapsulated;
2395
- wasMessageLogged = false;
2396
- }
2397
2404
  }
2398
- if (!wasMessageLogged) {
2399
- try {
2400
- this.driverLog.logMessage(msg, {
2401
- direction: "inbound"
2402
- });
2403
- } catch (e) {
2404
- this.driverLog.print(`Logging a message failed: ${(0, import_shared.getErrorMessage)(e)}`);
2405
- }
2405
+ try {
2406
+ this.driverLog.logMessage(msg, {
2407
+ direction: "inbound"
2408
+ });
2409
+ } catch (e) {
2410
+ this.driverLog.print(`Logging a message failed: ${(0, import_shared.getErrorMessage)(e)}`);
2406
2411
  }
2407
2412
  void this.handleUnsolicitedMessage(msg);
2408
2413
  }
@@ -2766,71 +2771,111 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2766
2771
  shouldRequestWakeupOnDemand(node) {
2767
2772
  return !!node.supportsWakeUpOnDemand && node.status === import_Types2.NodeStatus.Asleep && this.hasPendingTransactions((t) => t.requestWakeUpOnDemand && t.message.getNodeId() === node.id);
2768
2773
  }
2769
- partialCCSessions = /* @__PURE__ */ new Map();
2770
- getPartialCCSession(command, createIfMissing) {
2771
- const sessionId = command.getPartialCCSessionId();
2772
- if (sessionId) {
2773
- const partialSessionKey = JSON.stringify({
2774
- nodeId: command.nodeId,
2775
- ccId: command.ccId,
2776
- ccCommand: command.ccCommand,
2777
- ...sessionId
2774
+ partialCCSessions = new import_PartialCCSessionManager.PartialCCSessionManager({
2775
+ getSegmentTimeout: /* @__PURE__ */ __name(() => this._options.timeouts.report, "getSegmentTimeout"),
2776
+ getMaxReRequestAttempts: /* @__PURE__ */ __name(() => this._options.attempts.partialReports, "getMaxReRequestAttempts"),
2777
+ findRequestCC: /* @__PURE__ */ __name((command) => this.findPartialCCSessionRequest(command), "findRequestCC"),
2778
+ rerequestSession: /* @__PURE__ */ __name((session) => this.rerequestPartialCCSession(session), "rerequestSession"),
2779
+ onSessionLost: /* @__PURE__ */ __name((session) => {
2780
+ this.controllerLog.logNode(session.nodeId, {
2781
+ message: `Dropping incomplete partial CC session`,
2782
+ level: "warn",
2783
+ direction: "inbound"
2778
2784
  });
2779
- if (createIfMissing && !this.partialCCSessions.has(partialSessionKey)) {
2780
- this.partialCCSessions.set(partialSessionKey, []);
2781
- }
2782
- return {
2783
- partialSessionKey,
2784
- session: this.partialCCSessions.get(partialSessionKey)
2785
- };
2785
+ }, "onSessionLost")
2786
+ });
2787
+ /**
2788
+ * Determines the request a new partial CC session is the response to, if any.
2789
+ * This is used to re-request the response when segments are missing.
2790
+ */
2791
+ findPartialCCSessionRequest(command) {
2792
+ const currentMessage = this.queue.currentTransaction?.getCurrentMessage();
2793
+ if (!currentMessage || !(0, import_serialapi.containsCC)(currentMessage) || currentMessage.getNodeId() !== command.nodeId || !currentMessage.expectsNodeUpdate(this)) {
2794
+ return;
2786
2795
  }
2796
+ const request = (0, import_cc.getInnermostCommandClass)(currentMessage.command);
2797
+ if (!request.isExpectedCCResponse(this, (0, import_cc.getInnermostCommandClass)(command))) {
2798
+ return;
2799
+ }
2800
+ return request;
2801
+ }
2802
+ /** Re-requests the response a partial CC session belongs to because segments are missing */
2803
+ rerequestPartialCCSession(session) {
2804
+ this.refreshAwaitedMessageTimers(session.lastSegmentMsg);
2805
+ this.controllerLog.logNode(session.nodeId, {
2806
+ message: `Some expected reports were not received, requesting the response again...`,
2807
+ level: "warn",
2808
+ direction: "outbound"
2809
+ });
2810
+ void this.sendCommand(session.requestCC, {
2811
+ priority: import_core.MessagePriority.Immediate,
2812
+ maxSendAttempts: 1,
2813
+ ignoreNodeUpdate: true
2814
+ }).catch(import_shared.noop);
2787
2815
  }
2788
2816
  /**
2789
- * Assembles partial CCs of in a message body. Returns `true` when the message is complete and can be handled further.
2790
- * If the message expects another partial one, this returns `false`.
2817
+ * Assembles partial CCs in a message body. Returns the message containing the complete
2818
+ * command to continue handling. This is not necessarily the passed message, since responses
2819
+ * may be split into multiple messages.
2820
+ * If the message contains a partial CC and more segments are expected, this returns `undefined`.
2791
2821
  */
2792
2822
  async assemblePartialCCs(msg) {
2793
2823
  let command = msg.command;
2794
2824
  while (true) {
2795
- const { partialSessionKey, session } = this.getPartialCCSession(command, true) ?? {};
2796
- if (session) {
2797
- if (command.expectMoreMessages(session)) {
2798
- session.push(command);
2799
- if (!(0, import_cc.isTransportServiceEncapsulation)(msg.command)) {
2800
- this.driverLog.logMessage(msg, {
2801
- secondaryTags: ["partial"],
2825
+ const update = this.partialCCSessions.handleCommand(command, msg);
2826
+ if (update?.type === "segment") {
2827
+ if (update.duplicate) {
2828
+ this.controllerLog.logNode(command.nodeId, {
2829
+ message: `Ignoring duplicate partial CC segment:`,
2830
+ level: "debug",
2831
+ direction: "inbound"
2832
+ });
2833
+ }
2834
+ this.driverLog.logMessage(msg, {
2835
+ secondaryTags: ["partial"],
2836
+ direction: "inbound"
2837
+ });
2838
+ return void 0;
2839
+ } else if (update?.type === "complete") {
2840
+ if (update.partials.length > 0) {
2841
+ this.driverLog.logMessage(msg, {
2842
+ secondaryTags: ["partial"],
2843
+ direction: "inbound"
2844
+ });
2845
+ }
2846
+ try {
2847
+ await update.command.mergePartialCCs(update.partials, {
2848
+ ...this.getCCParsingContext(),
2849
+ sourceNodeId: update.command.nodeId,
2850
+ frameType: update.finalMsg.frameType
2851
+ });
2852
+ assertValidCCs(update.finalMsg);
2853
+ if (update.partials.length > 0) {
2854
+ this.controllerLog.logNode(update.command.nodeId, {
2855
+ message: `Assembled partial CC from ${update.partials.length + 1} segments`,
2856
+ level: "debug",
2802
2857
  direction: "inbound"
2803
2858
  });
2804
2859
  }
2805
- return false;
2806
- } else {
2807
- this.partialCCSessions.delete(partialSessionKey);
2808
- try {
2809
- await command.mergePartialCCs(session, {
2810
- ...this.getCCParsingContext(),
2811
- sourceNodeId: msg.command.nodeId,
2812
- frameType: msg.frameType
2813
- });
2814
- assertValidCCs(msg);
2815
- } catch (e) {
2816
- if ((0, import_core.isZWaveError)(e)) {
2817
- switch (e.code) {
2818
- case import_core.ZWaveErrorCodes.Deserialization_NotImplemented:
2819
- case import_core.ZWaveErrorCodes.CC_NotImplemented:
2820
- this.driverLog.print(`Dropping message because it could not be deserialized: ${e.message}`, "warn");
2821
- return false;
2822
- case import_core.ZWaveErrorCodes.PacketFormat_InvalidPayload:
2823
- this.driverLog.print(`Could not assemble partial CCs because the payload is invalid. Dropping them.`, "warn");
2824
- return false;
2825
- case import_core.ZWaveErrorCodes.Driver_NotReady:
2826
- this.driverLog.print(`Could not assemble partial CCs because the driver is not ready yet. Dropping them`, "warn");
2827
- return false;
2828
- }
2860
+ } catch (e) {
2861
+ if ((0, import_core.isZWaveError)(e)) {
2862
+ switch (e.code) {
2863
+ case import_core.ZWaveErrorCodes.Deserialization_NotImplemented:
2864
+ case import_core.ZWaveErrorCodes.CC_NotImplemented:
2865
+ this.driverLog.print(`Dropping message because it could not be deserialized: ${e.message}`, "warn");
2866
+ return void 0;
2867
+ case import_core.ZWaveErrorCodes.PacketFormat_InvalidPayload:
2868
+ this.driverLog.print(`Could not assemble partial CCs because the payload is invalid. Dropping them.`, "warn");
2869
+ return void 0;
2870
+ case import_core.ZWaveErrorCodes.Driver_NotReady:
2871
+ this.driverLog.print(`Could not assemble partial CCs because the driver is not ready yet. Dropping them`, "warn");
2872
+ return void 0;
2829
2873
  }
2830
- throw e;
2831
2874
  }
2875
+ throw e;
2832
2876
  }
2833
- } else {
2877
+ msg = update.finalMsg;
2878
+ command = update.command;
2834
2879
  }
2835
2880
  if ((0, import_cc.isEncapsulatingCommandClass)(command)) {
2836
2881
  command = command.encapsulated;
@@ -2838,19 +2883,40 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2838
2883
  break;
2839
2884
  }
2840
2885
  }
2841
- return true;
2886
+ return msg;
2887
+ }
2888
+ /** Refreshes the timeouts of awaited messages which a partial message may belong to */
2889
+ refreshAwaitedMessageTimers(msg) {
2890
+ for (const entry of this.awaitedMessages) {
2891
+ if (entry.refreshPredicate?.(msg)) {
2892
+ entry.timeout?.refresh();
2893
+ }
2894
+ }
2842
2895
  }
2843
- /** Is called when a Transport Service command is received */
2896
+ /**
2897
+ * Is called when a Transport Service command is received.
2898
+ * When the command completes a datagram, the reassembled command is returned.
2899
+ */
2844
2900
  async handleTransportServiceCommand(command) {
2845
2901
  const nodeSessions = this.ensureNodeSessions(command.nodeId);
2846
2902
  const missingSegmentTimeout = import_cc.TransportServiceTimeouts.requestMissingSegmentR2;
2847
- const advanceTransportServiceSession = /* @__PURE__ */ __name(async (session, input) => {
2848
- const machine = session.machine;
2903
+ const sendSegmentComplete = /* @__PURE__ */ __name(() => {
2904
+ const cc = new import_cc.TransportServiceCCSegmentComplete({
2905
+ nodeId: command.nodeId,
2906
+ sessionId: command.sessionId
2907
+ });
2908
+ void this.sendCommand(cc, {
2909
+ maxSendAttempts: 1,
2910
+ priority: import_core.MessagePriority.Immediate
2911
+ }).catch(import_shared.noop);
2912
+ }, "sendSegmentComplete");
2913
+ const advanceTransportServiceSession = /* @__PURE__ */ __name((session2, input) => {
2914
+ const machine = session2.machine;
2849
2915
  const transition = machine.next(input);
2850
2916
  if (transition) {
2851
2917
  machine.transition(transition.newState);
2852
2918
  if (machine.state.value === "receive") {
2853
- startMissingSegmentTimeout(session);
2919
+ startMissingSegmentTimeout(session2);
2854
2920
  } else if (machine.state.value === "requestMissing") {
2855
2921
  this.controllerLog.logNode(command.nodeId, {
2856
2922
  message: `Transport Service RX session #${command.sessionId}: Segment with offset ${machine.state.offset} missing - requesting it...`,
@@ -2862,11 +2928,10 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2862
2928
  sessionId: command.sessionId,
2863
2929
  datagramOffset: machine.state.offset
2864
2930
  });
2865
- await this.sendCommand(cc, {
2931
+ this.sendCommand(cc, {
2866
2932
  maxSendAttempts: 1,
2867
2933
  priority: import_core.MessagePriority.Immediate
2868
- }).catch(import_shared.noop);
2869
- startMissingSegmentTimeout(session);
2934
+ }).catch(import_shared.noop).finally(() => startMissingSegmentTimeout(session2));
2870
2935
  } else if (machine.state.value === "failure") {
2871
2936
  this.controllerLog.logNode(command.nodeId, {
2872
2937
  message: `Transport Service RX session #${command.sessionId} failed`,
@@ -2874,42 +2939,39 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2874
2939
  direction: "none"
2875
2940
  });
2876
2941
  nodeSessions.transportService.delete(command.sessionId);
2877
- if (session.timeout) {
2878
- clearTimeout(session.timeout);
2942
+ if (session2.timeout) {
2943
+ clearTimeout(session2.timeout);
2879
2944
  }
2945
+ } else if (machine.state.value === "success") {
2946
+ this.controllerLog.logNode(command.nodeId, {
2947
+ message: `Transport Service RX session #${command.sessionId} complete`,
2948
+ level: "debug",
2949
+ direction: "inbound"
2950
+ });
2951
+ if (session2.timeout) {
2952
+ clearTimeout(session2.timeout);
2953
+ }
2954
+ sendSegmentComplete();
2955
+ return "completed";
2880
2956
  }
2881
- }
2882
- if (machine.state.value === "success") {
2883
- this.controllerLog.logNode(command.nodeId, {
2884
- message: `Transport Service RX session #${command.sessionId} complete`,
2885
- level: "debug",
2886
- direction: "inbound"
2887
- });
2888
- if (session.timeout) {
2889
- clearTimeout(session.timeout);
2890
- }
2891
- const cc = new import_cc.TransportServiceCCSegmentComplete({
2892
- nodeId: command.nodeId,
2893
- sessionId: command.sessionId
2894
- });
2895
- await this.sendCommand(cc, {
2896
- maxSendAttempts: 1,
2897
- priority: import_core.MessagePriority.Immediate
2898
- }).catch(import_shared.noop);
2957
+ } else if (machine.state.value === "success") {
2958
+ sendSegmentComplete();
2899
2959
  }
2900
2960
  }, "advanceTransportServiceSession");
2901
- function startMissingSegmentTimeout(session) {
2902
- if (session.timeout) {
2903
- clearTimeout(session.timeout);
2961
+ function startMissingSegmentTimeout(session2) {
2962
+ if (session2.timeout) {
2963
+ clearTimeout(session2.timeout);
2904
2964
  }
2905
- session.timeout = setTimeout(() => {
2906
- session.timeout = void 0;
2907
- void advanceTransportServiceSession(session, {
2965
+ session2.timeout = setTimeout(() => {
2966
+ session2.timeout = void 0;
2967
+ advanceTransportServiceSession(session2, {
2908
2968
  value: "timeout"
2909
2969
  });
2910
2970
  }, missingSegmentTimeout);
2911
2971
  }
2912
2972
  __name(startMissingSegmentTimeout, "startMissingSegmentTimeout");
2973
+ let session;
2974
+ let datagramOffset;
2913
2975
  if (command instanceof import_cc.TransportServiceCCFirstSegment) {
2914
2976
  nodeSessions.transportService.clear();
2915
2977
  this.controllerLog.logNode(command.nodeId, {
@@ -2918,21 +2980,15 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2918
2980
  direction: "inbound"
2919
2981
  });
2920
2982
  const machine = (0, import_TransportServiceMachine.createTransportServiceRXMachine)(command.datagramSize, command.partialDatagram.length);
2921
- const session = {
2922
- fragmentSize: command.partialDatagram.length,
2923
- machine
2983
+ session = {
2984
+ machine,
2985
+ datagram: new import_shared.Bytes(command.datagramSize)
2924
2986
  };
2925
2987
  nodeSessions.transportService.set(command.sessionId, session);
2926
- startMissingSegmentTimeout(session);
2988
+ datagramOffset = 0;
2927
2989
  } else {
2928
- const transportSession = nodeSessions.transportService.get(command.sessionId);
2929
- if (transportSession) {
2930
- await advanceTransportServiceSession(transportSession, {
2931
- value: "segment",
2932
- offset: command.datagramOffset,
2933
- length: command.partialDatagram.length
2934
- });
2935
- } else {
2990
+ session = nodeSessions.transportService.get(command.sessionId);
2991
+ if (!session) {
2936
2992
  const cc = new import_cc.TransportServiceCCSegmentWait({
2937
2993
  nodeId: command.nodeId,
2938
2994
  pendingSegments: 0
@@ -2941,7 +2997,36 @@ ${(0, import_shared.buffer2hex)(data)}`, "warn");
2941
2997
  maxSendAttempts: 1,
2942
2998
  priority: import_core.MessagePriority.Immediate
2943
2999
  }).catch(import_shared.noop);
3000
+ return;
2944
3001
  }
3002
+ datagramOffset = command.datagramOffset;
3003
+ }
3004
+ if (datagramOffset + command.partialDatagram.length > session.datagram.length) {
3005
+ this.controllerLog.logNode(command.nodeId, {
3006
+ message: `Transport Service RX session #${command.sessionId}: Ignoring segment because it is incompatible with the datagram length`,
3007
+ level: "warn",
3008
+ direction: "inbound"
3009
+ });
3010
+ return;
3011
+ }
3012
+ session.datagram.set(command.partialDatagram, datagramOffset);
3013
+ const result = advanceTransportServiceSession(session, {
3014
+ value: "segment",
3015
+ offset: datagramOffset,
3016
+ length: command.partialDatagram.length
3017
+ });
3018
+ if (result !== "completed")
3019
+ return;
3020
+ try {
3021
+ return await import_cc.CommandClass.parse(session.datagram, {
3022
+ ...this.getCCParsingContext(),
3023
+ sourceNodeId: command.nodeId,
3024
+ // Transport Service is only used for singlecast commands
3025
+ frameType: command.frameType ?? "singlecast"
3026
+ });
3027
+ } catch (e) {
3028
+ this.driverLog.print(`Dropping Transport Service datagram because the contained command could not be deserialized: ${(0, import_shared.getErrorMessage)(e)}`, "warn");
3029
+ return;
2945
3030
  }
2946
3031
  }
2947
3032
  /**
@@ -3036,8 +3121,6 @@ ${handlers.length} left`);
3036
3121
  this.controllerLog.logNode(cc.nodeId, `is unknown - discarding received command...`, "warn");
3037
3122
  return true;
3038
3123
  }
3039
- if (cc instanceof import_cc.TransportServiceCC)
3040
- return false;
3041
3124
  if (cc instanceof import_cc.CRC16CCCommandEncapsulation) {
3042
3125
  return this.isSecurityLevelTooLow(cc.encapsulated);
3043
3126
  }
@@ -3093,7 +3176,7 @@ ${handlers.length} left`);
3093
3176
  }
3094
3177
  }
3095
3178
  if (requiresSecurity && !isSecure) {
3096
- this.controllerLog.logNode(cc.nodeId, `command was received at a lower security level than expected - discarding it...`, "warn");
3179
+ this.controllerLog.logNode(cc.nodeId, `command was received at a lower security level than expected - discarding it:`, "warn");
3097
3180
  return true;
3098
3181
  }
3099
3182
  return false;
@@ -4121,6 +4204,9 @@ ${handlers.length} left`);
4121
4204
  if (!!options.reportTimeoutMs) {
4122
4205
  msg.nodeUpdateTimeout = options.reportTimeoutMs;
4123
4206
  }
4207
+ if (options.ignoreNodeUpdate) {
4208
+ msg.ignoreNodeUpdate = true;
4209
+ }
4124
4210
  return msg;
4125
4211
  }
4126
4212
  /**