UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

580 lines (579 loc) 23.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var NetworkCache_exports = {}; __export(NetworkCache_exports, { cacheKeyUtils: () => cacheKeyUtils, cacheKeys: () => cacheKeys, deserializeNetworkCacheValue: () => deserializeNetworkCacheValue, migrateLegacyNetworkCache: () => migrateLegacyNetworkCache, serializeNetworkCacheValue: () => serializeNetworkCacheValue }); module.exports = __toCommonJS(NetworkCache_exports); var import_core = require("@zwave-js/core"); var import_shared = require("@zwave-js/shared"); var import_typeguards = require("alcalzone-shared/typeguards"); var import_pathe = __toESM(require("pathe"), 1); var import_Inclusion = require("../controller/Inclusion.js"); var import_DeviceClass = require("../node/DeviceClass.js"); var import_Types = require("../node/_Types.js"); const cacheKeys = { controller: { provisioningList: "controller.provisioningList", associations: /* @__PURE__ */ __name((groupId) => `controller.associations.${groupId}`, "associations"), securityKeys: /* @__PURE__ */ __name((secClass) => `controller.securityKeys.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeys"), securityKeysLongRange: /* @__PURE__ */ __name((secClass) => `controller.securityKeyLongRange.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeysLongRange"), privateKey: "controller.privateKey" }, // TODO: somehow these functions should be combined with the pattern matching below node: /* @__PURE__ */ __name((nodeId) => { const nodeBaseKey = `node.${nodeId}.`; return { _baseKey: nodeBaseKey, _securityClassBaseKey: `${nodeBaseKey}securityClasses`, _priorityReturnRouteBaseKey: `${nodeBaseKey}priorityReturnRoute`, interviewStage: `${nodeBaseKey}interviewStage`, bootstrapped: `${nodeBaseKey}bootstrapped`, deviceClass: `${nodeBaseKey}deviceClass`, isListening: `${nodeBaseKey}isListening`, isFrequentListening: `${nodeBaseKey}isFrequentListening`, isRouting: `${nodeBaseKey}isRouting`, supportedDataRates: `${nodeBaseKey}supportedDataRates`, protocolVersion: `${nodeBaseKey}protocolVersion`, nodeType: `${nodeBaseKey}nodeType`, supportsSecurity: `${nodeBaseKey}supportsSecurity`, supportsBeaming: `${nodeBaseKey}supportsBeaming`, securityClass: /* @__PURE__ */ __name((secClass) => `${nodeBaseKey}securityClasses.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityClass"), dsk: `${nodeBaseKey}dsk`, failedS2Bootstrapping: `${nodeBaseKey}failedS2Bootstrapping`, endpoint: /* @__PURE__ */ __name((index) => { const endpointBaseKey = `${nodeBaseKey}endpoint.${index}.`; const ccBaseKey = `${endpointBaseKey}commandClass.`; return { _baseKey: endpointBaseKey, _ccBaseKey: ccBaseKey, commandClass: /* @__PURE__ */ __name((ccId) => { const ccAsHex = (0, import_shared.num2hex)(ccId); return `${ccBaseKey}${ccAsHex}`; }, "commandClass") }; }, "endpoint"), hasSUCReturnRoute: `${nodeBaseKey}hasSUCReturnRoute`, priorityReturnRoute: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}priorityReturnRoute.${destinationNodeId}`, "priorityReturnRoute"), prioritySUCReturnRoute: `${nodeBaseKey}priorityReturnRoute.SUC`, customReturnRoutes: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}customReturnRoutes.${destinationNodeId}`, "customReturnRoutes"), customSUCReturnRoutes: `${nodeBaseKey}customReturnRoutes.SUC`, defaultTransitionDuration: `${nodeBaseKey}defaultTransitionDuration`, defaultVolume: `${nodeBaseKey}defaultVolume`, lastSeen: `${nodeBaseKey}lastSeen`, deviceConfigHash: `${nodeBaseKey}deviceConfigHash` }; }, "node") }; const cacheKeyUtils = { nodeIdFromKey: /* @__PURE__ */ __name((key) => { const match = /^node\.(?<nodeId>\d+)\./.exec(key); if (match) { return parseInt(match.groups.nodeId, 10); } }, "nodeIdFromKey"), nodePropertyFromKey: /* @__PURE__ */ __name((key) => { const match = /^node\.\d+\.(?<property>[^.]+)$/.exec(key); return match?.groups?.property; }, "nodePropertyFromKey"), isEndpointKey: /* @__PURE__ */ __name((key) => { return /endpoints\.(?<index>\d+)$/.test(key); }, "isEndpointKey"), endpointIndexFromKey: /* @__PURE__ */ __name((key) => { const match = /endpoints\.(?<index>\d+)$/.exec(key); if (match) { return parseInt(match.groups.index, 10); } }, "endpointIndexFromKey"), destinationFromPriorityReturnRouteKey: /* @__PURE__ */ __name((key) => { const match = /\.priorityReturnRoute\.(?<nodeId>\d+)$/.exec(key); if (match) { return parseInt(match.groups.nodeId, 10); } }, "destinationFromPriorityReturnRouteKey") }; function tryParseInterviewStage(value) { if ((typeof value === "string" || typeof value === "number") && value in import_Types.InterviewStage) { return typeof value === "number" ? value : import_Types.InterviewStage[value]; } } __name(tryParseInterviewStage, "tryParseInterviewStage"); function tryParseDeviceClass(value) { if ((0, import_typeguards.isObject)(value)) { const { basic, generic, specific } = value; if (typeof basic === "number" && typeof generic === "number" && typeof specific === "number") { return new import_DeviceClass.DeviceClass(basic, generic, specific); } } } __name(tryParseDeviceClass, "tryParseDeviceClass"); function tryParseSecurityClasses(value) { if ((0, import_typeguards.isObject)(value)) { const ret = /* @__PURE__ */ new Map(); for (const [key, val] of Object.entries(value)) { if (key in import_core.SecurityClass && typeof import_core.SecurityClass[key] === "number" && typeof val === "boolean") { ret.set(import_core.SecurityClass[key], val); } } return ret; } } __name(tryParseSecurityClasses, "tryParseSecurityClasses"); function tryParseNodeType(value) { if (typeof value === "string" && value in import_core.NodeType) { return import_core.NodeType[value]; } } __name(tryParseNodeType, "tryParseNodeType"); function tryParseProvisioningList(value) { const ret = []; if (!(0, import_typeguards.isArray)(value)) return; for (const entry of value) { if ((0, import_typeguards.isObject)(entry) && typeof entry.dsk === "string" && (0, import_typeguards.isArray)(entry.securityClasses) && entry.securityClasses.every((s) => isSerializedSecurityClass(s)) && (entry.requestedSecurityClasses == void 0 || (0, import_typeguards.isArray)(entry.requestedSecurityClasses) && entry.requestedSecurityClasses.every((s) => isSerializedSecurityClass(s))) && (entry.protocol == void 0 || isSerializedProtocol(entry.protocol)) && (entry.supportedProtocols == void 0 || (0, import_typeguards.isArray)(entry.supportedProtocols) && entry.supportedProtocols.every((s) => isSerializedProtocol(s))) && (entry.status == void 0 || isSerializedProvisioningEntryStatus(entry.status))) { if ("nodeId" in entry && typeof entry.nodeId !== "number") { return; } const parsed = { ...entry }; parsed.securityClasses = entry.securityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0); if (entry.requestedSecurityClasses) { parsed.requestedSecurityClasses = entry.requestedSecurityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0); } if (entry.status != void 0) { parsed.status = import_Inclusion.ProvisioningEntryStatus[entry.status]; } if (entry.protocol != void 0) { parsed.protocol = tryParseSerializedProtocol(entry.protocol); } if (entry.supportedProtocols) { parsed.supportedProtocols = entry.supportedProtocols.map((s) => tryParseSerializedProtocol(s)).filter((s) => s !== void 0); } ret.push(parsed); } else { return; } } return ret; } __name(tryParseProvisioningList, "tryParseProvisioningList"); function isSerializedSecurityClass(value) { if (typeof value === "number" && value in import_core.SecurityClass) return true; if (typeof value === "string") { if (value.startsWith("unknown (0x") && value.endsWith(")")) { value = value.slice(11, -1); } if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") { return true; } } return false; } __name(isSerializedSecurityClass, "isSerializedSecurityClass"); function tryParseSerializedSecurityClass(value) { if (typeof value === "number" && value in import_core.SecurityClass) return value; if (typeof value === "string") { if (value.startsWith("unknown (0x") && value.endsWith(")")) { value = value.slice(11, -1); } if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") { return import_core.SecurityClass[value]; } } } __name(tryParseSerializedSecurityClass, "tryParseSerializedSecurityClass"); function isSerializedProvisioningEntryStatus(s) { return typeof s === "string" && s in import_Inclusion.ProvisioningEntryStatus && typeof import_Inclusion.ProvisioningEntryStatus[s] === "number"; } __name(isSerializedProvisioningEntryStatus, "isSerializedProvisioningEntryStatus"); function isSerializedProtocol(s) { if (typeof s === "number" && s in import_core.Protocols) return true; return typeof s === "string" && s in import_core.Protocols && typeof import_core.Protocols[s] === "number"; } __name(isSerializedProtocol, "isSerializedProtocol"); function tryParseSerializedProtocol(value) { if (typeof value === "number" && value in import_core.Protocols) return value; if (typeof value === "string") { if (value in import_core.Protocols && typeof import_core.Protocols[value] === "number") { return import_core.Protocols[value]; } } } __name(tryParseSerializedProtocol, "tryParseSerializedProtocol"); function tryParseDate(value) { if (typeof value === "number") { const ret = new Date(value); if (!isNaN(ret.getTime())) return ret; } } __name(tryParseDate, "tryParseDate"); function tryParseAssociationAddress(value) { if ((0, import_typeguards.isObject)(value)) { const { nodeId, endpoint } = value; if (typeof nodeId !== "number") return; if (endpoint !== void 0 && typeof endpoint !== "number") return; return { nodeId, endpoint }; } } __name(tryParseAssociationAddress, "tryParseAssociationAddress"); function tryParseBuffer(value) { if (typeof value === "string") { try { return import_shared.Bytes.from(value, "hex"); } catch { } } } __name(tryParseBuffer, "tryParseBuffer"); function tryParseBufferBase64(value) { if (typeof value === "string") { try { return import_shared.Bytes.from(value, "base64"); } catch { } } } __name(tryParseBufferBase64, "tryParseBufferBase64"); function deserializeNetworkCacheValue(key, value) { function ensureType(value2, type) { if (typeof value2 === type) return value2; throw new import_core.ZWaveError(`Incorrect type ${typeof value2} for property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache); } __name(ensureType, "ensureType"); function fail() { throw new import_core.ZWaveError(`Failed to deserialize property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache); } __name(fail, "fail"); switch (cacheKeyUtils.nodePropertyFromKey(key)) { case "interviewStage": { value = tryParseInterviewStage(value); if (value) return value; fail(); } case "deviceClass": { value = tryParseDeviceClass(value); if (value) return value; fail(); } case "isListening": case "isRouting": case "hasSUCReturnRoute": return ensureType(value, "boolean"); case "isFrequentListening": { switch (value) { case "1000ms": case true: return "1000ms"; case "250ms": return "250ms"; case false: return false; } fail(); } case "dsk": { if (typeof value === "string") { return (0, import_core.dskFromString)(value); } fail(); } case "failedS2Bootstrapping": { return ensureType(value, "boolean"); } case "supportsSecurity": return ensureType(value, "boolean"); case "supportsBeaming": try { return ensureType(value, "boolean"); } catch { return ensureType(value, "string"); } case "protocolVersion": return ensureType(value, "number"); case "nodeType": { value = tryParseNodeType(value); if (value) return value; fail(); } case "supportedDataRates": { if ((0, import_typeguards.isArray)(value) && value.every((r) => typeof r === "number")) { return value; } fail(); } case "lastSeen": { value = tryParseDate(value); if (value) return value; fail(); } case "deviceConfigHash": { if (typeof value !== "string") fail(); const versionMatch = value.match(/^\$v\d+\$/)?.[0]; if (versionMatch) { value = tryParseBufferBase64(value.slice(versionMatch.length)); if (value) { value = import_shared.Bytes.concat([ import_shared.Bytes.from(versionMatch, "utf8"), value ]); } } else { value = tryParseBuffer(value); } if (value) return value; fail(); } } if (key.startsWith("controller.associations.")) { value = tryParseAssociationAddress(value); if (value) return value; fail(); } else if (key.startsWith("controller.securityKeys.")) { value = tryParseBuffer(value); if (value) return value; fail(); } switch (key) { case cacheKeys.controller.provisioningList: { value = tryParseProvisioningList(value); if (value) return value; fail(); } case cacheKeys.controller.privateKey: { value = tryParseBuffer(value); if (value) return value; fail(); } } return value; } __name(deserializeNetworkCacheValue, "deserializeNetworkCacheValue"); function serializeNetworkCacheValue(key, value) { switch (cacheKeyUtils.nodePropertyFromKey(key)) { case "interviewStage": { return import_Types.InterviewStage[value]; } case "deviceClass": { const deviceClass = value; return { basic: deviceClass.basic, generic: deviceClass.generic.key, specific: deviceClass.specific.key }; } case "nodeType": { return import_core.NodeType[value]; } case "securityClasses": { const ret = {}; for (const secClass of import_core.securityClassOrder) { if (secClass in value) { ret[import_core.SecurityClass[secClass]] = value[secClass]; } } return ret; } case "dsk": { return (0, import_core.dskToString)(value); } case "lastSeen": { return value.getTime(); } case "deviceConfigHash": { const valueAsString = import_shared.Bytes.view(value).toString("utf8"); const versionMatch = valueAsString.match(/^\$v\d+\$/)?.[0]; if (versionMatch) { return versionMatch + import_shared.Bytes.view(value).subarray(versionMatch.length).toString("base64"); } else { return import_shared.Bytes.view(value).toString("hex"); } } } if (key.startsWith("controller.securityKeys.")) { return import_shared.Bytes.view(value).toString("hex"); } switch (key) { case cacheKeys.controller.provisioningList: { const ret = []; for (const entry of value) { const serialized = { ...entry }; serialized.securityClasses = entry.securityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c)); if (entry.requestedSecurityClasses) { serialized.requestedSecurityClasses = entry.requestedSecurityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c)); } if (entry.status != void 0) { serialized.status = (0, import_shared.getEnumMemberName)(import_Inclusion.ProvisioningEntryStatus, entry.status); } if (entry.protocol != void 0) { serialized.protocol = (0, import_shared.getEnumMemberName)(import_core.Protocols, entry.protocol); } if (entry.supportedProtocols != void 0) { serialized.supportedProtocols = entry.supportedProtocols.map((p) => (0, import_shared.getEnumMemberName)(import_core.Protocols, p)); } ret.push(serialized); } return ret; } case cacheKeys.controller.privateKey: { return import_shared.Bytes.view(value).toString("hex"); } } return value; } __name(serializeNetworkCacheValue, "serializeNetworkCacheValue"); const legacyPaths = { // These seem to duplicate the ones in cacheKeys, but this allows us to change // something in the future without breaking migration controller: { provisioningList: "controller.provisioningList" }, node: { // These are relative to the node object interviewStage: `interviewStage`, deviceClass: `deviceClass`, isListening: `isListening`, isFrequentListening: `isFrequentListening`, isRouting: `isRouting`, supportedDataRates: `supportedDataRates`, protocolVersion: `protocolVersion`, nodeType: `nodeType`, supportsSecurity: `supportsSecurity`, supportsBeaming: `supportsBeaming`, securityClasses: `securityClasses`, dsk: `dsk` }, commandClass: { // These are relative to the commandClasses object name: `name`, endpoint: /* @__PURE__ */ __name((index) => `endpoints.${index}`, "endpoint") } }; async function migrateLegacyNetworkCache(homeId, networkCache, valueDB, fs, cacheDir) { const cacheFile = import_pathe.default.join(cacheDir, `${homeId.toString(16)}.json`); try { const stat = await fs.stat(cacheFile); if (!stat.isFile()) return; } catch { return; } const legacyContents = await fs.readFile(cacheFile); const legacy = JSON.parse(import_shared.Bytes.view(legacyContents).toString("utf8")); const jsonl = networkCache; function tryMigrate(targetKey, source, sourcePath, converter) { let val = (0, import_shared.pickDeep)(source, sourcePath); if (val != void 0 && converter) val = converter(val); if (val != void 0) jsonl.set(targetKey, val); } __name(tryMigrate, "tryMigrate"); tryMigrate(cacheKeys.controller.provisioningList, legacy, legacyPaths.controller.provisioningList, tryParseProvisioningList); if ((0, import_typeguards.isObject)(legacy.nodes)) { for (const node of Object.values(legacy.nodes)) { if (!(0, import_typeguards.isObject)(node) || typeof node.id !== "number") continue; const nodeCacheKeys = cacheKeys.node(node.id); tryMigrate(nodeCacheKeys.interviewStage, node, legacyPaths.node.interviewStage, tryParseInterviewStage); tryMigrate(nodeCacheKeys.deviceClass, node, legacyPaths.node.deviceClass, (v) => tryParseDeviceClass(v)); tryMigrate(nodeCacheKeys.isListening, node, legacyPaths.node.isListening); tryMigrate(nodeCacheKeys.isFrequentListening, node, legacyPaths.node.isFrequentListening); tryMigrate(nodeCacheKeys.isRouting, node, legacyPaths.node.isRouting); tryMigrate(nodeCacheKeys.supportedDataRates, node, legacyPaths.node.supportedDataRates); tryMigrate(nodeCacheKeys.protocolVersion, node, legacyPaths.node.protocolVersion); tryMigrate(nodeCacheKeys.nodeType, node, legacyPaths.node.nodeType, tryParseNodeType); tryMigrate(nodeCacheKeys.supportsSecurity, node, legacyPaths.node.supportsSecurity); tryMigrate(nodeCacheKeys.supportsBeaming, node, legacyPaths.node.supportsBeaming); const securityClasses = tryParseSecurityClasses((0, import_shared.pickDeep)(node, legacyPaths.node.securityClasses)); if (securityClasses) { for (const [secClass, val] of securityClasses) { jsonl.set(nodeCacheKeys.securityClass(secClass), val); } } tryMigrate(nodeCacheKeys.dsk, node, legacyPaths.node.dsk, import_core.dskFromString); if ((0, import_typeguards.isObject)(node.commandClasses)) { for (const [ccIdHex, cc] of Object.entries(node.commandClasses)) { const ccId = parseInt(ccIdHex, 16); if ((0, import_typeguards.isObject)(cc.endpoints)) { for (const endpointId of Object.keys(cc.endpoints)) { const endpointIndex = parseInt(endpointId, 10); const cacheKey = nodeCacheKeys.endpoint(endpointIndex).commandClass(ccId); tryMigrate(cacheKey, cc, legacyPaths.commandClass.endpoint(endpointIndex)); } } } } const dbKey = JSON.stringify({ nodeId: node.id, commandClass: -1, endpoint: 0, property: "hasSUCReturnRoute" }); if (valueDB.has(dbKey)) { const hasSUCReturnRoute = valueDB.get(dbKey); valueDB.delete(dbKey); jsonl.set(nodeCacheKeys.hasSUCReturnRoute, hasSUCReturnRoute); } } } } __name(migrateLegacyNetworkCache, "migrateLegacyNetworkCache"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { cacheKeyUtils, cacheKeys, deserializeNetworkCacheValue, migrateLegacyNetworkCache, serializeNetworkCacheValue }); //# sourceMappingURL=NetworkCache.js.map