@zwave-js/nvmedit 10.6.0 → 10.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/convert.js CHANGED
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.migrateNVM = exports.json700To500 = exports.json500To700 = exports.jsonToNVM500 = exports.jsonToNVM = exports.nvm500ToJSON = exports.nvmToJSON = exports.jsonToNVMObjects_v1_to_v4 = exports.jsonToNVMObjects_v0 = exports.nvmObjectsToJSON = exports.nodeHasInfo = void 0;
6
+ exports.migrateNVM = exports.json700To500 = exports.json500To700 = exports.jsonToNVM500 = exports.jsonToNVM = exports.nvm500ToJSON = exports.nvmToJSON = exports.jsonToNVMObjects_v7_11_0 = exports.jsonToNVMObjects_v7_0_0 = exports.nvmObjectsToJSON = exports.nodeHasInfo = void 0;
7
7
  const safe_1 = require("@zwave-js/core/safe");
8
8
  const safe_2 = require("@zwave-js/shared/safe");
9
+ const typeguards_1 = require("alcalzone-shared/typeguards");
9
10
  const semver_1 = __importDefault(require("semver"));
11
+ const consts_1 = require("./consts");
10
12
  const files_1 = require("./files");
11
13
  const nvm_1 = require("./nvm3/nvm");
12
14
  const utils_1 = require("./nvm3/utils");
@@ -68,28 +70,34 @@ function nvmObjectsToJSON(applicationObjects, protocolObjects) {
68
70
  return ret;
69
71
  throw new safe_1.ZWaveError(`Object${typeof id === "number" ? ` ${id}` : ""} not found!`, safe_1.ZWaveErrorCodes.NVM_ObjectNotFound);
70
72
  };
71
- const getFileOrThrow = (id) => {
73
+ const getFileOrThrow = (id, fileVersion) => {
72
74
  const obj = getObjectOrThrow(id);
73
- return files_1.NVMFile.from(obj);
75
+ return files_1.NVMFile.from(obj, fileVersion);
74
76
  };
75
- const getFile = (id) => {
77
+ const getFile = (id, fileVersion) => {
76
78
  const obj = getObject(id);
77
79
  if (!obj)
78
80
  return undefined;
79
- return files_1.NVMFile.from(obj);
81
+ return files_1.NVMFile.from(obj, fileVersion);
80
82
  };
83
+ // === Protocol NVM files ===
81
84
  // Figure out how to parse the individual files
82
- const protocolVersionFile = getFileOrThrow(files_1.ProtocolVersionFileID);
85
+ const protocolVersionFile = getFileOrThrow(files_1.ProtocolVersionFileID, "7.0.0");
83
86
  const protocolFileFormat = protocolVersionFile.format;
87
+ const protocolVersion = `${protocolVersionFile.major}.${protocolVersionFile.minor}.${protocolVersionFile.patch}`;
88
+ // Bail early if the NVM uses a protocol file format that's newer than we support
89
+ if (protocolFileFormat > consts_1.MAX_PROTOCOL_FILE_FORMAT) {
90
+ throw new safe_1.ZWaveError(`Unsupported protocol file format: ${protocolFileFormat}`, safe_1.ZWaveErrorCodes.NVM_NotSupported, { protocolFileFormat });
91
+ }
84
92
  // Figure out which nodes exist
85
- const nodeIds = getFileOrThrow(files_1.ProtocolNodeListFileID).nodeIds;
93
+ const nodeIds = getFileOrThrow(files_1.ProtocolNodeListFileID, protocolVersion).nodeIds;
86
94
  // Read all flags for all nodes
87
- const appRouteLock = new Set(getFileOrThrow(files_1.ProtocolAppRouteLockNodeMaskFileID).nodeIds);
88
- const routeSlaveSUC = new Set(getFileOrThrow(files_1.ProtocolRouteSlaveSUCNodeMaskFileID).nodeIds);
89
- const sucPendingUpdate = new Set(getFileOrThrow(files_1.ProtocolSUCPendingUpdateNodeMaskFileID).nodeIds);
90
- const isVirtual = new Set(getFileOrThrow(files_1.ProtocolVirtualNodeMaskFileID).nodeIds);
91
- const pendingDiscovery = new Set(getFileOrThrow(files_1.ProtocolPendingDiscoveryNodeMaskFileID).nodeIds);
92
- const routeCacheExists = new Set(getFileOrThrow(files_1.ProtocolRouteCacheExistsNodeMaskFileID).nodeIds);
95
+ const appRouteLock = new Set(getFileOrThrow(files_1.ProtocolAppRouteLockNodeMaskFileID, protocolVersion).nodeIds);
96
+ const routeSlaveSUC = new Set(getFileOrThrow(files_1.ProtocolRouteSlaveSUCNodeMaskFileID, protocolVersion).nodeIds);
97
+ const sucPendingUpdate = new Set(getFileOrThrow(files_1.ProtocolSUCPendingUpdateNodeMaskFileID, protocolVersion).nodeIds);
98
+ const isVirtual = new Set(getFileOrThrow(files_1.ProtocolVirtualNodeMaskFileID, protocolVersion).nodeIds);
99
+ const pendingDiscovery = new Set(getFileOrThrow(files_1.ProtocolPendingDiscoveryNodeMaskFileID, protocolVersion).nodeIds);
100
+ const routeCacheExists = new Set(getFileOrThrow(files_1.ProtocolRouteCacheExistsNodeMaskFileID, protocolVersion).nodeIds);
93
101
  // And create each node entry, including virtual ones
94
102
  for (const id of nodeIds) {
95
103
  const node = getNode(id);
@@ -103,17 +111,14 @@ function nvmObjectsToJSON(applicationObjects, protocolObjects) {
103
111
  try {
104
112
  if (protocolFileFormat === 0) {
105
113
  const fileId = (0, files_1.nodeIdToNodeInfoFileIDV0)(id);
106
- const file = getFileOrThrow(fileId);
114
+ const file = getFileOrThrow(fileId, protocolVersion);
107
115
  nodeInfo = file.nodeInfo;
108
116
  }
109
- else if (protocolFileFormat <= 4) {
117
+ else {
110
118
  const fileId = (0, files_1.nodeIdToNodeInfoFileIDV1)(id);
111
- const file = getFileOrThrow(fileId);
119
+ const file = getFileOrThrow(fileId, protocolVersion);
112
120
  nodeInfo = file.nodeInfos.find((i) => i.nodeId === id);
113
121
  }
114
- else {
115
- throw new safe_1.ZWaveError(`Unsupported protocol file format: ${protocolFileFormat}`, safe_1.ZWaveErrorCodes.NVM_NotSupported);
116
- }
117
122
  }
118
123
  catch (e) {
119
124
  if (e.message.includes("Object not found")) {
@@ -133,17 +138,14 @@ function nvmObjectsToJSON(applicationObjects, protocolObjects) {
133
138
  let routeCache;
134
139
  if (protocolFileFormat === 0) {
135
140
  const fileId = (0, files_1.nodeIdToRouteCacheFileIDV0)(id);
136
- const file = getFile(fileId);
141
+ const file = getFile(fileId, protocolVersion);
137
142
  routeCache = file?.routeCache;
138
143
  }
139
- else if (protocolFileFormat <= 4) {
144
+ else {
140
145
  const fileId = (0, files_1.nodeIdToRouteCacheFileIDV1)(id);
141
- const file = getFile(fileId);
146
+ const file = getFile(fileId, protocolVersion);
142
147
  routeCache = file?.routeCaches.find((i) => i.nodeId === id);
143
148
  }
144
- else {
145
- throw new safe_1.ZWaveError(`Unsupported protocol file format: ${protocolFileFormat}`, safe_1.ZWaveErrorCodes.NVM_NotSupported);
146
- }
147
149
  if (routeCache) {
148
150
  node.lwr = routeCache.lwr;
149
151
  node.nlwr = routeCache.nlwr;
@@ -153,14 +155,29 @@ function nvmObjectsToJSON(applicationObjects, protocolObjects) {
153
155
  delete node.nodeId;
154
156
  }
155
157
  // Now read info about the controller
156
- const controllerInfoFile = getFileOrThrow(files_1.ControllerInfoFileID);
157
- const sucUpdateEntries = getFileOrThrow(files_1.SUCUpdateEntriesFileID).updateEntries;
158
- const rfConfigFile = getFile(files_1.ApplicationRFConfigFileID);
159
- const applicationVersionFile = getFileOrThrow(files_1.ApplicationVersionFileID);
160
- const applicationCCsFile = getFileOrThrow(files_1.ApplicationCCsFileID);
161
- const applicationDataFile = getFile(files_1.ApplicationDataFileID);
162
- const applicationTypeFile = getFileOrThrow(files_1.ApplicationTypeFileID);
163
- const preferredRepeaters = getFile(files_1.ProtocolPreferredRepeatersFileID)?.nodeIds;
158
+ const controllerInfoFile = getFileOrThrow(files_1.ControllerInfoFileID, protocolVersion);
159
+ let sucUpdateEntries;
160
+ if (protocolFileFormat < 5) {
161
+ sucUpdateEntries = getFileOrThrow(files_1.SUCUpdateEntriesFileIDV0, protocolVersion).updateEntries;
162
+ }
163
+ else {
164
+ // V5 has split the entries into multiple files
165
+ sucUpdateEntries = [];
166
+ for (let index = 0; index < consts_1.SUC_MAX_UPDATES; index += files_1.SUC_UPDATES_PER_FILE_V5) {
167
+ const file = getFile((0, files_1.sucUpdateIndexToSUCUpdateEntriesFileIDV5)(index), protocolVersion);
168
+ if (!file)
169
+ break;
170
+ sucUpdateEntries.push(...file.updateEntries);
171
+ }
172
+ }
173
+ // === Application NVM files ===
174
+ const applicationVersionFile = getFileOrThrow(files_1.ApplicationVersionFileID, "7.0.0");
175
+ const applicationVersion = `${applicationVersionFile.major}.${applicationVersionFile.minor}.${applicationVersionFile.patch}`;
176
+ const rfConfigFile = getFile(files_1.ApplicationRFConfigFileID, applicationVersion);
177
+ const applicationCCsFile = getFileOrThrow(files_1.ApplicationCCsFileID, applicationVersion);
178
+ const applicationDataFile = getFile(files_1.ApplicationDataFileID, applicationVersion);
179
+ const applicationTypeFile = getFileOrThrow(files_1.ApplicationTypeFileID, applicationVersion);
180
+ const preferredRepeaters = getFile(files_1.ProtocolPreferredRepeatersFileID, applicationVersion)?.nodeIds;
164
181
  const controllerProps = [
165
182
  "nodeId",
166
183
  "lastNodeId",
@@ -283,18 +300,24 @@ function nvmJSONControllerToFileOptions(ctrlr) {
283
300
  }
284
301
  function serializeCommonApplicationObjects(nvm) {
285
302
  const ret = [];
286
- const applTypeFile = new files_1.ApplicationTypeFile((0, safe_2.pick)(nvm.controller, [
287
- "isListening",
288
- "optionalFunctionality",
289
- "genericDeviceClass",
290
- "specificDeviceClass",
291
- ]));
303
+ const applTypeFile = new files_1.ApplicationTypeFile({
304
+ ...(0, safe_2.pick)(nvm.controller, [
305
+ "isListening",
306
+ "optionalFunctionality",
307
+ "genericDeviceClass",
308
+ "specificDeviceClass",
309
+ ]),
310
+ fileVersion: nvm.controller.applicationVersion,
311
+ });
292
312
  ret.push(applTypeFile.serialize());
293
- const applCCsFile = new files_1.ApplicationCCsFile((0, safe_2.pick)(nvm.controller.commandClasses, [
294
- "includedInsecurely",
295
- "includedSecurelyInsecureCCs",
296
- "includedSecurelySecureCCs",
297
- ]));
313
+ const applCCsFile = new files_1.ApplicationCCsFile({
314
+ ...(0, safe_2.pick)(nvm.controller.commandClasses, [
315
+ "includedInsecurely",
316
+ "includedSecurelyInsecureCCs",
317
+ "includedSecurelySecureCCs",
318
+ ]),
319
+ fileVersion: nvm.controller.applicationVersion,
320
+ });
298
321
  ret.push(applCCsFile.serialize());
299
322
  if (nvm.controller.rfConfig) {
300
323
  const applRFConfigFile = new files_1.ApplicationRFConfigFile({
@@ -305,6 +328,7 @@ function serializeCommonApplicationObjects(nvm) {
305
328
  ]),
306
329
  enablePTI: nvm.controller.rfConfig.enablePTI ?? undefined,
307
330
  maxTXPower: nvm.controller.rfConfig.maxTXPower ?? undefined,
331
+ fileVersion: nvm.controller.applicationVersion,
308
332
  });
309
333
  ret.push(applRFConfigFile.serialize());
310
334
  }
@@ -312,6 +336,7 @@ function serializeCommonApplicationObjects(nvm) {
312
336
  // TODO: ensure this is 512 bytes long
313
337
  const applDataFile = new files_1.ApplicationDataFile({
314
338
  data: Buffer.from(nvm.controller.applicationData, "hex"),
339
+ fileVersion: nvm.controller.applicationVersion,
315
340
  });
316
341
  ret.push(applDataFile.serialize());
317
342
  }
@@ -346,33 +371,58 @@ function serializeCommonProtocolObjects(nvm) {
346
371
  ret.push(new files_1.ControllerInfoFile(nvmJSONControllerToFileOptions(nvm.controller)).serialize());
347
372
  ret.push(new files_1.ProtocolAppRouteLockNodeMaskFile({
348
373
  nodeIds: [...appRouteLock],
374
+ fileVersion: nvm.controller.protocolVersion,
349
375
  }).serialize());
350
376
  ret.push(new files_1.ProtocolRouteSlaveSUCNodeMaskFile({
351
377
  nodeIds: [...routeSlaveSUC],
378
+ fileVersion: nvm.controller.protocolVersion,
352
379
  }).serialize());
353
380
  ret.push(new files_1.ProtocolSUCPendingUpdateNodeMaskFile({
354
381
  nodeIds: [...sucPendingUpdate],
382
+ fileVersion: nvm.controller.protocolVersion,
355
383
  }).serialize());
356
384
  ret.push(new files_1.ProtocolVirtualNodeMaskFile({
357
385
  nodeIds: [...isVirtual],
386
+ fileVersion: nvm.controller.protocolVersion,
358
387
  }).serialize());
359
388
  ret.push(new files_1.ProtocolPendingDiscoveryNodeMaskFile({
360
389
  nodeIds: [...pendingDiscovery],
390
+ fileVersion: nvm.controller.protocolVersion,
361
391
  }).serialize());
362
392
  // TODO: format >= 2: { .key = FILE_ID_LRANGE_NODE_EXIST, .size = FILE_SIZE_LRANGE_NODE_EXIST, .name = "LRANGE_NODE_EXIST"},
363
393
  if (nvm.controller.preferredRepeaters?.length) {
364
394
  ret.push(new files_1.ProtocolPreferredRepeatersFile({
365
395
  nodeIds: nvm.controller.preferredRepeaters,
396
+ fileVersion: nvm.controller.protocolVersion,
366
397
  }).serialize());
367
398
  }
368
- ret.push(new files_1.SUCUpdateEntriesFile({
369
- updateEntries: nvm.controller.sucUpdateEntries,
370
- }).serialize());
399
+ if (nvm.format < 5) {
400
+ ret.push(new files_1.SUCUpdateEntriesFileV0({
401
+ updateEntries: nvm.controller.sucUpdateEntries,
402
+ fileVersion: nvm.controller.protocolVersion,
403
+ }).serialize());
404
+ }
405
+ else {
406
+ // V5 has split the SUC update entries into multiple files
407
+ for (let index = 0; index < consts_1.SUC_MAX_UPDATES; index += files_1.SUC_UPDATES_PER_FILE_V5) {
408
+ const slice = nvm.controller.sucUpdateEntries.slice(index, index + files_1.SUC_UPDATES_PER_FILE_V5);
409
+ if (slice.length === 0)
410
+ break;
411
+ const file = new files_1.SUCUpdateEntriesFileV5({
412
+ updateEntries: slice,
413
+ fileVersion: nvm.controller.protocolVersion,
414
+ });
415
+ file.fileId = (0, files_1.sucUpdateIndexToSUCUpdateEntriesFileIDV5)(index);
416
+ ret.push(file.serialize());
417
+ }
418
+ }
371
419
  return ret;
372
420
  }
373
- function jsonToNVMObjects_v0(json, major, minor, patch) {
421
+ function jsonToNVMObjects_v7_0_0(json, targetSDKVersion) {
374
422
  const target = (0, safe_2.cloneDeep)(json);
423
+ target.controller.protocolVersion = "7.0.0";
375
424
  target.format = 0;
425
+ target.controller.applicationVersion = targetSDKVersion.format();
376
426
  const applicationObjects = new Map();
377
427
  const protocolObjects = new Map();
378
428
  const addApplicationObjects = (...objects) => {
@@ -386,21 +436,23 @@ function jsonToNVMObjects_v0(json, major, minor, patch) {
386
436
  }
387
437
  };
388
438
  // Application files
389
- const [applMajor, applMinor, applPatch] = target.controller.applicationVersion.split(".").map((i) => parseInt(i));
390
439
  const applVersionFile = new files_1.ApplicationVersionFile({
391
- format: target.format,
392
- major: applMajor,
393
- minor: applMinor,
394
- patch: applPatch,
440
+ // The SDK compares 4-byte values where the format is set to 0 to determine whether a migration is needed
441
+ format: 0,
442
+ major: targetSDKVersion.major,
443
+ minor: targetSDKVersion.minor,
444
+ patch: targetSDKVersion.patch,
445
+ fileVersion: target.controller.applicationVersion, // does not matter for this file
395
446
  });
396
447
  addApplicationObjects(applVersionFile.serialize());
397
448
  addApplicationObjects(...serializeCommonApplicationObjects(target));
398
449
  // Protocol files
399
450
  const protocolVersionFile = new files_1.ProtocolVersionFile({
400
451
  format: target.format,
401
- major,
402
- minor,
403
- patch,
452
+ major: 7,
453
+ minor: 0,
454
+ patch: 0,
455
+ fileVersion: target.controller.protocolVersion, // does not matter for this file
404
456
  });
405
457
  addProtocolObjects(protocolVersionFile.serialize());
406
458
  const nodeInfoFiles = new Map();
@@ -416,6 +468,7 @@ function jsonToNVMObjects_v0(json, major, minor, patch) {
416
468
  const nodeInfoFileIndex = (0, files_1.nodeIdToNodeInfoFileIDV0)(nodeId);
417
469
  nodeInfoFiles.set(nodeInfoFileIndex, new files_1.NodeInfoFileV0({
418
470
  nodeInfo: nvmJSONNodeToNodeInfo(nodeId, node),
471
+ fileVersion: target.controller.protocolVersion,
419
472
  }));
420
473
  // Create/update route cache file (if there is a route)
421
474
  if (node.lwr || node.nlwr) {
@@ -427,13 +480,18 @@ function jsonToNVMObjects_v0(json, major, minor, patch) {
427
480
  lwr: node.lwr ?? (0, files_1.getEmptyRoute)(),
428
481
  nlwr: node.nlwr ?? (0, files_1.getEmptyRoute)(),
429
482
  },
483
+ fileVersion: target.controller.protocolVersion,
430
484
  }));
431
485
  }
432
486
  }
433
487
  addProtocolObjects(...serializeCommonProtocolObjects(target));
434
- addProtocolObjects(new files_1.ProtocolNodeListFile({ nodeIds: [...nodeInfoExists] }).serialize());
488
+ addProtocolObjects(new files_1.ProtocolNodeListFile({
489
+ nodeIds: [...nodeInfoExists],
490
+ fileVersion: target.controller.protocolVersion,
491
+ }).serialize());
435
492
  addProtocolObjects(new files_1.ProtocolRouteCacheExistsNodeMaskFile({
436
493
  nodeIds: [...routeCacheExists],
494
+ fileVersion: target.controller.protocolVersion,
437
495
  }).serialize());
438
496
  if (nodeInfoFiles.size > 0) {
439
497
  addProtocolObjects(...[...nodeInfoFiles.values()].map((f) => f.serialize()));
@@ -446,11 +504,47 @@ function jsonToNVMObjects_v0(json, major, minor, patch) {
446
504
  protocolObjects,
447
505
  };
448
506
  }
449
- exports.jsonToNVMObjects_v0 = jsonToNVMObjects_v0;
450
- function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
507
+ exports.jsonToNVMObjects_v7_0_0 = jsonToNVMObjects_v7_0_0;
508
+ function jsonToNVMObjects_v7_11_0(json, targetSDKVersion) {
451
509
  var _a, _b, _c, _d, _e, _f, _g, _h;
452
510
  const target = (0, safe_2.cloneDeep)(json);
453
- target.format = format;
511
+ let targetApplicationVersion;
512
+ let targetProtocolVersion;
513
+ let targetProtocolFormat;
514
+ // We currently support application version migrations up to 7.19.1
515
+ // For all higher ones, set the highest version we support and let the controller handle the migration itself
516
+ if (semver_1.default.lte(targetSDKVersion, "7.19.1")) {
517
+ targetApplicationVersion = semver_1.default.parse(targetSDKVersion);
518
+ }
519
+ else {
520
+ targetApplicationVersion = semver_1.default.parse("7.19.1");
521
+ }
522
+ // The protocol version file only seems to be updated when the format of the protocol file system changes
523
+ // Once again, we simply set the highest version we support here and let the controller handle any potential migration
524
+ if (semver_1.default.gte(targetSDKVersion, "7.19.0")) {
525
+ targetProtocolVersion = semver_1.default.parse("7.19.0");
526
+ targetProtocolFormat = 5;
527
+ }
528
+ else if (semver_1.default.gte(targetSDKVersion, "7.17.0")) {
529
+ targetProtocolVersion = semver_1.default.parse("7.17.0");
530
+ targetProtocolFormat = 4;
531
+ }
532
+ else if (semver_1.default.gte(targetSDKVersion, "7.15.3")) {
533
+ targetProtocolVersion = semver_1.default.parse("7.15.3");
534
+ targetProtocolFormat = 3;
535
+ }
536
+ else if (semver_1.default.gte(targetSDKVersion, "7.12.0")) {
537
+ targetProtocolVersion = semver_1.default.parse("7.12.0");
538
+ targetProtocolFormat = 2;
539
+ }
540
+ else {
541
+ // All versions below 7.11.0 are handled in the _v7_0_0 method
542
+ targetProtocolVersion = semver_1.default.parse("7.11.0");
543
+ targetProtocolFormat = 1;
544
+ }
545
+ target.format = targetProtocolFormat;
546
+ target.controller.applicationVersion = targetApplicationVersion.format();
547
+ target.controller.protocolVersion = targetProtocolVersion.format();
454
548
  const applicationObjects = new Map();
455
549
  const protocolObjects = new Map();
456
550
  const addApplicationObjects = (...objects) => {
@@ -464,12 +558,13 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
464
558
  }
465
559
  };
466
560
  // Application files
467
- const [applMajor, applMinor, applPatch] = target.controller.applicationVersion.split(".").map((i) => parseInt(i));
468
561
  const applVersionFile = new files_1.ApplicationVersionFile({
469
- format: target.format,
470
- major: applMajor,
471
- minor: applMinor,
472
- patch: applPatch,
562
+ // The SDK compares 4-byte values where the format is set to 0 to determine whether a migration is needed
563
+ format: 0,
564
+ major: targetApplicationVersion.major,
565
+ minor: targetApplicationVersion.minor,
566
+ patch: targetApplicationVersion.patch,
567
+ fileVersion: target.controller.applicationVersion, // does not matter for this file
473
568
  });
474
569
  addApplicationObjects(applVersionFile.serialize());
475
570
  // When converting it can be that the rfConfig doesn't exist. Make sure
@@ -481,19 +576,20 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
481
576
  enablePTI: null,
482
577
  maxTXPower: null,
483
578
  });
484
- // For v3+ targets, the enablePTI and maxTxPower must be set in the rfConfig
485
- // or the controller will ignore the file and not accept any changes to the RF config
486
- if (format >= 3) {
579
+ // Make sure the RF config format matches the application version.
580
+ // Otherwise, the controller will ignore the file and not accept any changes to the RF config
581
+ if (semver_1.default.gte(targetSDKVersion, "7.15.3")) {
487
582
  (_b = target.controller.rfConfig).enablePTI ?? (_b.enablePTI = 0);
488
583
  (_c = target.controller.rfConfig).maxTXPower ?? (_c.maxTXPower = 14.0);
489
584
  }
490
585
  addApplicationObjects(...serializeCommonApplicationObjects(target));
491
586
  // Protocol files
492
587
  const protocolVersionFile = new files_1.ProtocolVersionFile({
493
- format: target.format,
494
- major,
495
- minor,
496
- patch,
588
+ format: targetProtocolFormat,
589
+ major: targetProtocolVersion.major,
590
+ minor: targetProtocolVersion.minor,
591
+ patch: targetProtocolVersion.patch,
592
+ fileVersion: target.controller.protocolVersion, // does not matter for this file
497
593
  });
498
594
  addProtocolObjects(protocolVersionFile.serialize());
499
595
  const nodeInfoFiles = new Map();
@@ -510,6 +606,7 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
510
606
  if (!nodeInfoFiles.has(nodeInfoFileIndex)) {
511
607
  nodeInfoFiles.set(nodeInfoFileIndex, new files_1.NodeInfoFileV1({
512
608
  nodeInfos: [],
609
+ fileVersion: target.controller.protocolVersion,
513
610
  }));
514
611
  }
515
612
  const nodeInfoFile = nodeInfoFiles.get(nodeInfoFileIndex);
@@ -521,6 +618,7 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
521
618
  if (!routeCacheFiles.has(routeCacheFileIndex)) {
522
619
  routeCacheFiles.set(routeCacheFileIndex, new files_1.RouteCacheFileV1({
523
620
  routeCaches: [],
621
+ fileVersion: target.controller.protocolVersion,
524
622
  }));
525
623
  }
526
624
  const routeCacheFile = routeCacheFiles.get(routeCacheFileIndex);
@@ -533,7 +631,7 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
533
631
  }
534
632
  // For v3+ targets, the ControllerInfoFile must contain the LongRange properties
535
633
  // or the controller will ignore the file and not have a home ID
536
- if (format >= 3) {
634
+ if (targetProtocolFormat >= 3) {
537
635
  (_d = target.controller).lastNodeIdLR ?? (_d.lastNodeIdLR = 255);
538
636
  (_e = target.controller).maxNodeIdLR ?? (_e.maxNodeIdLR = 0);
539
637
  (_f = target.controller).reservedIdLR ?? (_f.reservedIdLR = 0);
@@ -541,9 +639,13 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
541
639
  (_h = target.controller).dcdcConfig ?? (_h.dcdcConfig = 255);
542
640
  }
543
641
  addProtocolObjects(...serializeCommonProtocolObjects(target));
544
- addProtocolObjects(new files_1.ProtocolNodeListFile({ nodeIds: [...nodeInfoExists] }).serialize());
642
+ addProtocolObjects(new files_1.ProtocolNodeListFile({
643
+ nodeIds: [...nodeInfoExists],
644
+ fileVersion: target.controller.protocolVersion,
645
+ }).serialize());
545
646
  addProtocolObjects(new files_1.ProtocolRouteCacheExistsNodeMaskFile({
546
647
  nodeIds: [...routeCacheExists],
648
+ fileVersion: target.controller.protocolVersion,
547
649
  }).serialize());
548
650
  if (nodeInfoFiles.size > 0) {
549
651
  addProtocolObjects(...[...nodeInfoFiles.values()].map((f) => f.serialize()));
@@ -556,7 +658,7 @@ function jsonToNVMObjects_v1_to_v4(format, json, major, minor, patch) {
556
658
  protocolObjects,
557
659
  };
558
660
  }
559
- exports.jsonToNVMObjects_v1_to_v4 = jsonToNVMObjects_v1_to_v4;
661
+ exports.jsonToNVMObjects_v7_11_0 = jsonToNVMObjects_v7_11_0;
560
662
  /** Reads an NVM buffer and returns its JSON representation */
561
663
  function nvmToJSON(buffer, debugLogs = false) {
562
664
  const nvm = (0, nvm_1.parseNVM)(buffer, debugLogs);
@@ -574,26 +676,17 @@ function nvm500ToJSON(buffer) {
574
676
  }
575
677
  exports.nvm500ToJSON = nvm500ToJSON;
576
678
  /** Takes a JSON represented NVM and converts it to binary */
577
- function jsonToNVM(json, protocolVersion) {
578
- const parsedVersion = semver_1.default.parse(protocolVersion);
679
+ function jsonToNVM(json, targetSDKVersion) {
680
+ const parsedVersion = semver_1.default.parse(targetSDKVersion);
579
681
  if (!parsedVersion) {
580
- throw new safe_1.ZWaveError(`Invalid protocol version: ${protocolVersion}`, safe_1.ZWaveErrorCodes.Argument_Invalid);
682
+ throw new safe_1.ZWaveError(`Invalid SDK version: ${targetSDKVersion}`, safe_1.ZWaveErrorCodes.Argument_Invalid);
581
683
  }
582
684
  let objects;
583
- if (semver_1.default.gte(parsedVersion, "7.17.0")) {
584
- objects = jsonToNVMObjects_v1_to_v4(4, json, parsedVersion.major, parsedVersion.minor, parsedVersion.patch);
585
- }
586
- else if (semver_1.default.gte(parsedVersion, "7.15.3")) {
587
- objects = jsonToNVMObjects_v1_to_v4(3, json, parsedVersion.major, parsedVersion.minor, parsedVersion.patch);
588
- }
589
- else if (semver_1.default.gte(parsedVersion, "7.12.0")) {
590
- objects = jsonToNVMObjects_v1_to_v4(2, json, parsedVersion.major, parsedVersion.minor, parsedVersion.patch);
591
- }
592
- else if (semver_1.default.gte(parsedVersion, "7.11.0")) {
593
- objects = jsonToNVMObjects_v1_to_v4(1, json, parsedVersion.major, parsedVersion.minor, parsedVersion.patch);
685
+ if (semver_1.default.gte(parsedVersion, "7.11.0")) {
686
+ objects = jsonToNVMObjects_v7_11_0(json, parsedVersion);
594
687
  }
595
688
  else if (semver_1.default.gte(parsedVersion, "7.0.0")) {
596
- objects = jsonToNVMObjects_v0(json, parsedVersion.major, parsedVersion.minor, parsedVersion.patch);
689
+ objects = jsonToNVMObjects_v7_0_0(json, parsedVersion);
597
690
  }
598
691
  else {
599
692
  throw new safe_1.ZWaveError("jsonToNVM cannot convert to a pre-7.0 NVM version. Use jsonToNVM500 instead.", safe_1.ZWaveErrorCodes.Argument_Invalid);
@@ -766,11 +859,14 @@ exports.json700To500 = json700To500;
766
859
  function migrateNVM(sourceNVM, targetNVM) {
767
860
  let source;
768
861
  let target;
862
+ let sourceProtocolFileFormat;
863
+ let targetProtocolFileFormat;
769
864
  try {
770
865
  source = {
771
866
  type: 700,
772
867
  json: nvmToJSON(sourceNVM),
773
868
  };
869
+ sourceProtocolFileFormat = source.json.format;
774
870
  }
775
871
  catch (e) {
776
872
  if ((0, safe_1.isZWaveError)(e) && e.code === safe_1.ZWaveErrorCodes.NVM_InvalidFormat) {
@@ -780,6 +876,14 @@ function migrateNVM(sourceNVM, targetNVM) {
780
876
  json: nvm500ToJSON(sourceNVM),
781
877
  };
782
878
  }
879
+ else if ((0, safe_1.isZWaveError)(e) &&
880
+ e.code === safe_1.ZWaveErrorCodes.NVM_NotSupported &&
881
+ (0, typeguards_1.isObject)(e.context) &&
882
+ typeof e.context.protocolFileFormat === "number") {
883
+ // This is a 700 series NVM, but the protocol version is not (yet) supported
884
+ source = { type: "unknown" };
885
+ sourceProtocolFileFormat = e.context.protocolFileFormat;
886
+ }
783
887
  else {
784
888
  source = { type: "unknown" };
785
889
  }
@@ -789,6 +893,7 @@ function migrateNVM(sourceNVM, targetNVM) {
789
893
  type: 700,
790
894
  json: nvmToJSON(targetNVM),
791
895
  };
896
+ targetProtocolFileFormat = target.json.format;
792
897
  }
793
898
  catch (e) {
794
899
  if ((0, safe_1.isZWaveError)(e) && e.code === safe_1.ZWaveErrorCodes.NVM_InvalidFormat) {
@@ -798,19 +903,48 @@ function migrateNVM(sourceNVM, targetNVM) {
798
903
  json: nvm500ToJSON(targetNVM),
799
904
  };
800
905
  }
906
+ else if ((0, safe_1.isZWaveError)(e) &&
907
+ e.code === safe_1.ZWaveErrorCodes.NVM_NotSupported &&
908
+ source.type === 700 &&
909
+ (0, typeguards_1.isObject)(e.context) &&
910
+ typeof e.context.protocolFileFormat === "number") {
911
+ // This is a 700 series NVM, but the protocol version is not (yet) supported
912
+ target = { type: "unknown" };
913
+ targetProtocolFileFormat = e.context.protocolFileFormat;
914
+ }
801
915
  else {
802
916
  target = { type: "unknown" };
803
917
  }
804
918
  }
805
919
  // Short circuit if...
806
- if (source.type === 700 && target.type === 700) {
920
+ if (target.type === "unknown" &&
921
+ targetProtocolFileFormat &&
922
+ targetProtocolFileFormat > consts_1.MAX_PROTOCOL_FILE_FORMAT &&
923
+ sourceProtocolFileFormat &&
924
+ sourceProtocolFileFormat <= targetProtocolFileFormat) {
925
+ // ...both the source and the target are 700 series, but at least the target uses an unsupported protocol version.
926
+ // We can be sure hwoever that the target can upgrade any 700 series NVM to its protocol version, as long as the
927
+ // source protocol version is not higher than the target's
928
+ return sourceNVM;
929
+ }
930
+ else if (source.type === 700 && target.type === 700) {
807
931
  //... the source and target protocol versions are compatible without conversion
808
932
  const sourceProtocolVersion = source.json.controller.protocolVersion;
809
933
  const targetProtocolVersion = target.json.controller.protocolVersion;
934
+ // ... and the application version on the both is compatible with the respective protocol version
935
+ const sourceApplicationVersion = source.json.controller.applicationVersion;
936
+ const targetApplicationVersion = target.json.controller.applicationVersion;
810
937
  // The 700 series firmware can automatically upgrade backups from a previous protocol version
811
938
  // Not sure when that ability was added. To be on the safe side, allow it for 7.16+ which definitely supports it.
812
939
  if (semver_1.default.gte(targetProtocolVersion, "7.16.0") &&
813
- semver_1.default.gte(targetProtocolVersion, sourceProtocolVersion)) {
940
+ semver_1.default.gte(targetProtocolVersion, sourceProtocolVersion) &&
941
+ // the application version is updated on every update, protocol version only when the format changes
942
+ // so this is a good indicator if the NVMs are in a compatible state
943
+ semver_1.default.gte(targetApplicationVersion, targetProtocolVersion) &&
944
+ semver_1.default.gte(sourceApplicationVersion, sourceProtocolVersion) &&
945
+ // avoid preserving broken 255.x versions which appear on some controllers
946
+ semver_1.default.lt(sourceApplicationVersion, "255.0.0") &&
947
+ semver_1.default.lt(targetApplicationVersion, "255.0.0")) {
814
948
  return sourceNVM;
815
949
  }
816
950
  }
@@ -829,6 +963,13 @@ function migrateNVM(sourceNVM, targetNVM) {
829
963
  // TypeScript doesn't understand multi-variable narrowings (yet)
830
964
  source = source;
831
965
  target = target;
966
+ // Some 700 series NVMs have a strange 255.x application version - fix that first
967
+ if (target.type === 700 &&
968
+ semver_1.default.gte(target.json.controller.applicationVersion, "255.0.0")) {
969
+ // replace both with the protocol version
970
+ target.json.controller.applicationVersion =
971
+ target.json.controller.protocolVersion;
972
+ }
832
973
  // In any case, preserve the application version of the target stick
833
974
  source.json.controller.applicationVersion =
834
975
  target.json.controller.applicationVersion;
@@ -850,7 +991,8 @@ function migrateNVM(sourceNVM, targetNVM) {
850
991
  };
851
992
  // The target is a different series, try to preserve the RF config of the target stick
852
993
  json.controller.rfConfig = target.json.controller.rfConfig;
853
- return jsonToNVM(json, target.json.controller.protocolVersion);
994
+ // 700 series distinguishes the NVM format by the application version
995
+ return jsonToNVM(json, target.json.controller.applicationVersion);
854
996
  }
855
997
  else if (source.type === 700 && target.type === 500) {
856
998
  // We need to downgrade the source to 500 series
@@ -868,7 +1010,8 @@ function migrateNVM(sourceNVM, targetNVM) {
868
1010
  ...source.json,
869
1011
  meta: target.json.meta,
870
1012
  };
871
- return jsonToNVM(json, target.json.controller.protocolVersion);
1013
+ // 700 series distinguishes the NVM format by the application version
1014
+ return jsonToNVM(json, target.json.controller.applicationVersion);
872
1015
  }
873
1016
  }
874
1017
  exports.migrateNVM = migrateNVM;