UNPKG

zigbee2mqtt

Version:

Zigbee to MQTT bridge using Zigbee-herdsman

806 lines 76.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const node_fs_1 = __importDefault(require("node:fs")); const bind_decorator_1 = __importDefault(require("bind-decorator")); const json_stable_stringify_without_jsonify_1 = __importDefault(require("json-stable-stringify-without-jsonify")); const jszip_1 = __importDefault(require("jszip")); const object_assign_deep_1 = __importDefault(require("object-assign-deep")); const winston_transport_1 = __importDefault(require("winston-transport")); const zigbee_herdsman_1 = require("zigbee-herdsman"); const device_1 = require("zigbee-herdsman/dist/controller/model/device"); const zhc = __importStar(require("zigbee-herdsman-converters")); const device_2 = __importDefault(require("../model/device")); const data_1 = __importDefault(require("../util/data")); const logger_1 = __importDefault(require("../util/logger")); const settings = __importStar(require("../util/settings")); const utils_1 = __importStar(require("../util/utils")); const extension_1 = __importDefault(require("./extension")); class Bridge extends extension_1.default { #requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`); // set on `start` #osInfo; zigbee2mqttVersion; zigbeeHerdsmanVersion; zigbeeHerdsmanConvertersVersion; coordinatorVersion; restartRequired = false; lastJoinedDeviceIeeeAddr; lastBridgeLoggingPayload; logTransport; requestLookup = { "device/options": this.deviceOptions, "device/configure_reporting": this.deviceConfigureReporting, "device/remove": this.deviceRemove, "device/interview": this.deviceInterview, "device/generate_external_definition": this.deviceGenerateExternalDefinition, "device/rename": this.deviceRename, "group/add": this.groupAdd, "group/options": this.groupOptions, "group/remove": this.groupRemove, "group/rename": this.groupRename, permit_join: this.permitJoin, restart: this.restart, backup: this.backup, "touchlink/factory_reset": this.touchlinkFactoryReset, "touchlink/identify": this.touchlinkIdentify, "install_code/add": this.installCodeAdd, "touchlink/scan": this.touchlinkScan, health_check: this.healthCheck, coordinator_check: this.coordinatorCheck, options: this.bridgeOptions, }; async start() { const debugToMQTTFrontend = settings.get().advanced.log_debug_to_mqtt_frontend; const bridgeLogging = (message, level, namespace) => { const payload = (0, json_stable_stringify_without_jsonify_1.default)({ message, level, namespace }); if (payload !== this.lastBridgeLoggingPayload) { this.lastBridgeLoggingPayload = payload; void this.mqtt.publish("bridge/logging", payload, { skipLog: true }); } }; if (debugToMQTTFrontend) { class DebugEventTransport extends winston_transport_1.default { log(info, next) { bridgeLogging(info.message, info.level, info.namespace); next(); } } this.logTransport = new DebugEventTransport(); } else { class EventTransport extends winston_transport_1.default { log(info, next) { if (info.level !== "debug") { bridgeLogging(info.message, info.level, info.namespace); } next(); } } this.logTransport = new EventTransport(); } logger_1.default.addTransport(this.logTransport); const os = await import("node:os"); const process = await import("node:process"); const logicalCpuCores = os.cpus(); this.#osInfo = { version: `${os.version()} - ${os.release()} - ${os.arch()}`, node_version: process.version, cpus: `${[...new Set(logicalCpuCores.map((cpu) => cpu.model))].join(" | ")} (x${logicalCpuCores.length})`, memory_mb: Math.round(os.totalmem() / 1024 / 1024), }; this.zigbee2mqttVersion = await utils_1.default.getZigbee2MQTTVersion(); this.zigbeeHerdsmanVersion = await utils_1.default.getDependencyVersion("zigbee-herdsman"); this.zigbeeHerdsmanConvertersVersion = await utils_1.default.getDependencyVersion("zigbee-herdsman-converters"); this.coordinatorVersion = await this.zigbee.getCoordinatorVersion(); this.eventBus.onEntityRenamed(this, async () => { await this.publishInfo(); }); this.eventBus.onGroupMembersChanged(this, async () => { await this.publishGroups(); }); this.eventBus.onDevicesChanged(this, async () => { await this.publishDevices(); await this.publishInfo(); await this.publishDefinitions(); }); this.eventBus.onPermitJoinChanged(this, async () => { if (!this.zigbee.isStopping()) { await this.publishInfo(); } }); this.eventBus.onScenesChanged(this, async () => { await this.publishDevices(); await this.publishGroups(); }); // Zigbee events this.eventBus.onDeviceJoined(this, async (data) => { this.lastJoinedDeviceIeeeAddr = data.device.ieeeAddr; await this.publishDevices(); const payload = { type: "device_joined", data: { friendly_name: data.device.name, ieee_address: data.device.ieeeAddr }, }; await this.mqtt.publish("bridge/event", (0, json_stable_stringify_without_jsonify_1.default)(payload)); }); this.eventBus.onDeviceLeave(this, async (data) => { await this.publishDevices(); await this.publishDefinitions(); const payload = { type: "device_leave", data: { ieee_address: data.ieeeAddr, friendly_name: data.name } }; await this.mqtt.publish("bridge/event", (0, json_stable_stringify_without_jsonify_1.default)(payload)); }); this.eventBus.onDeviceNetworkAddressChanged(this, async () => { await this.publishDevices(); }); this.eventBus.onDeviceInterview(this, async (data) => { await this.publishDevices(); let payload; if (data.status === "successful") { payload = { type: "device_interview", data: { friendly_name: data.device.name, status: data.status, ieee_address: data.device.ieeeAddr, supported: data.device.isSupported, definition: this.getDefinitionPayload(data.device), }, }; } else { payload = { type: "device_interview", data: { friendly_name: data.device.name, status: data.status, ieee_address: data.device.ieeeAddr }, }; } await this.mqtt.publish("bridge/event", (0, json_stable_stringify_without_jsonify_1.default)(payload)); }); this.eventBus.onDeviceAnnounce(this, async (data) => { await this.publishDevices(); const payload = { type: "device_announce", data: { friendly_name: data.device.name, ieee_address: data.device.ieeeAddr }, }; await this.mqtt.publish("bridge/event", (0, json_stable_stringify_without_jsonify_1.default)(payload)); }); await this.publishInfo(); await this.publishDevices(); await this.publishGroups(); await this.publishDefinitions(); this.eventBus.onMQTTMessage(this, this.onMQTTMessage); } async stop() { await super.stop(); logger_1.default.removeTransport(this.logTransport); } async onMQTTMessage(data) { const match = data.topic.match(this.#requestRegex); if (!match) { return; } const key = match[1].toLowerCase(); if (key in this.requestLookup) { const message = utils_1.default.parseJSON(data.message, data.message); try { const response = await this.requestLookup[key](message); await this.mqtt.publish(`bridge/response/${match[1]}`, (0, json_stable_stringify_without_jsonify_1.default)(response)); } catch (error) { logger_1.default.error(`Request '${data.topic}' failed with error: '${error.message}'`); // biome-ignore lint/style/noNonNullAssertion: always using Error logger_1.default.debug(error.stack); const response = utils_1.default.getResponse(message, {}, error.message); await this.mqtt.publish(`bridge/response/${match[1]}`, (0, json_stable_stringify_without_jsonify_1.default)(response)); } } } /** * Requests */ async deviceOptions(message) { return await this.changeEntityOptions("device", message); } async groupOptions(message) { return await this.changeEntityOptions("group", message); } async bridgeOptions(message) { if (typeof message !== "object" || typeof message.options !== "object") { throw new Error("Invalid payload"); } const newSettings = message.options; this.restartRequired = settings.apply(newSettings); // Apply some settings on-the-fly. if (newSettings.homeassistant) { await this.enableDisableExtension(settings.get().homeassistant.enabled, "HomeAssistant"); } if (newSettings.advanced?.log_level != null) { logger_1.default.setLevel(settings.get().advanced.log_level); } if (newSettings.advanced?.log_namespaced_levels != null) { logger_1.default.setNamespacedLevels(settings.get().advanced.log_namespaced_levels); } if (newSettings.advanced?.log_debug_namespace_ignore != null) { logger_1.default.setDebugNamespaceIgnore(settings.get().advanced.log_debug_namespace_ignore); } logger_1.default.info("Successfully changed options"); await this.publishInfo(); return utils_1.default.getResponse(message, { restart_required: this.restartRequired }); } async deviceRemove(message) { return await this.removeEntity("device", message); } async groupRemove(message) { return await this.removeEntity("group", message); } // biome-ignore lint/suspicious/useAwait: API async healthCheck(message) { return utils_1.default.getResponse(message, { healthy: true }); } async coordinatorCheck(message) { const result = await this.zigbee.coordinatorCheck(); const missingRouters = result.missingRouters.map((d) => { return { ieee_address: d.ieeeAddr, friendly_name: d.name }; }); return utils_1.default.getResponse(message, { missing_routers: missingRouters }); } async groupAdd(message) { if (typeof message === "object" && message.friendly_name === undefined) { throw new Error("Invalid payload"); } const friendlyName = typeof message === "object" ? message.friendly_name : message; const ID = typeof message === "object" && message.id !== undefined ? message.id : null; const group = settings.addGroup(friendlyName, ID); this.zigbee.createGroup(group.ID); await this.publishGroups(); return utils_1.default.getResponse(message, { friendly_name: group.friendly_name, id: group.ID }); } async deviceRename(message) { return await this.renameEntity("device", message); } async groupRename(message) { return await this.renameEntity("group", message); } // biome-ignore lint/suspicious/useAwait: API async restart(message) { // Wait 500 ms before restarting so response can be send. setTimeout(this.restartCallback, 500); logger_1.default.info("Restarting Zigbee2MQTT"); return utils_1.default.getResponse(message, {}); } async backup(message) { await this.zigbee.backup(); const dataPath = data_1.default.getPath(); const files = utils_1.default .getAllFiles(dataPath) .map((f) => [f, f.substring(dataPath.length + 1)]) .filter((f) => !f[1].startsWith("log")); const zip = new jszip_1.default(); for (const f of files) { zip.file(f[1], node_fs_1.default.readFileSync(f[0])); } const base64Zip = await zip.generateAsync({ type: "base64" }); return utils_1.default.getResponse(message, { zip: base64Zip }); } async installCodeAdd(message) { if (typeof message === "object" && message.value === undefined) { throw new Error("Invalid payload"); } const value = typeof message === "object" ? message.value : message; await this.zigbee.addInstallCode(value); logger_1.default.info("Successfully added new install code"); return utils_1.default.getResponse(message, { value }); } async permitJoin(message) { let time; let device; if (typeof message === "object") { if (message.time === undefined) { throw new Error("Invalid payload"); } time = Number.parseInt(message.time, 10); if (message.device) { const resolved = this.zigbee.resolveEntity(message.device); if (resolved instanceof device_2.default) { device = resolved; } else { throw new Error(`Device '${message.device}' does not exist`); } } } else { time = Number.parseInt(message, 10); } await this.zigbee.permitJoin(time, device); const response = { time }; if (device) { response.device = device.name; } return utils_1.default.getResponse(message, response); } async touchlinkIdentify(message) { if (typeof message !== "object" || message.ieee_address === undefined || message.channel === undefined) { throw new Error("Invalid payload"); } logger_1.default.info(`Start Touchlink identify of '${message.ieee_address}' on channel ${message.channel}`); await this.zigbee.touchlinkIdentify(message.ieee_address, message.channel); return utils_1.default.getResponse(message, { ieee_address: message.ieee_address, channel: message.channel }); } async touchlinkFactoryReset(message) { let result = false; let payload = {}; if (typeof message === "object" && message.ieee_address !== undefined && message.channel !== undefined) { logger_1.default.info(`Start Touchlink factory reset of '${message.ieee_address}' on channel ${message.channel}`); result = await this.zigbee.touchlinkFactoryReset(message.ieee_address, message.channel); payload = { ieee_address: message.ieee_address, channel: message.channel, }; } else { logger_1.default.info("Start Touchlink factory reset of first found device"); result = await this.zigbee.touchlinkFactoryResetFirst(); } if (result) { logger_1.default.info("Successfully factory reset device through Touchlink"); return utils_1.default.getResponse(message, payload); } logger_1.default.error("Failed to factory reset device through Touchlink"); throw new Error("Failed to factory reset device through Touchlink"); } async touchlinkScan(message) { logger_1.default.info("Start Touchlink scan"); const result = await this.zigbee.touchlinkScan(); const found = result.map((r) => { return { ieee_address: r.ieeeAddr, channel: r.channel }; }); logger_1.default.info("Finished Touchlink scan"); return utils_1.default.getResponse(message, { found }); } /** * Utils */ async changeEntityOptions(entityType, message) { if (typeof message !== "object" || message.id === undefined || message.options === undefined) { throw new Error("Invalid payload"); } const cleanup = (o) => { delete o.friendlyName; delete o.friendly_name; delete o.ID; delete o.type; delete o.devices; return o; }; const ID = message.id; const entity = this.getEntity(entityType, ID); const oldOptions = (0, object_assign_deep_1.default)({}, cleanup(entity.options)); if (message.options.icon) { const base64Match = utils_1.default.matchBase64File(message.options.icon); if (base64Match) { const fileSettings = utils_1.default.saveBase64DeviceIcon(base64Match); message.options.icon = fileSettings; logger_1.default.debug(`Saved base64 image as file to '${fileSettings}'`); } } const restartRequired = settings.changeEntityOptions(ID, message.options); if (restartRequired) this.restartRequired = true; const newOptions = cleanup(entity.options); await this.publishInfo(); logger_1.default.info(`Changed config for ${entityType} ${ID}`); this.eventBus.emitEntityOptionsChanged({ from: oldOptions, to: newOptions, entity }); return utils_1.default.getResponse(message, { from: oldOptions, to: newOptions, id: ID, restart_required: this.restartRequired }); } async deviceConfigureReporting(message) { if (typeof message !== "object" || message.id === undefined || message.endpoint === undefined || message.cluster === undefined || message.maximum_report_interval === undefined || message.minimum_report_interval === undefined || message.reportable_change === undefined || message.attribute === undefined) { throw new Error("Invalid payload"); } const device = this.getEntity("device", message.id); const endpoint = device.endpoint(message.endpoint); if (!endpoint) { throw new Error(`Device '${device.ID}' does not have endpoint '${message.endpoint}'`); } const coordinatorEndpoint = this.zigbee.firstCoordinatorEndpoint(); await endpoint.bind(message.cluster, coordinatorEndpoint); await endpoint.configureReporting(message.cluster, [ { attribute: message.attribute, minimumReportInterval: message.minimum_report_interval, maximumReportInterval: message.maximum_report_interval, reportableChange: message.reportable_change, }, ], message.options); await this.publishDevices(); logger_1.default.info(`Configured reporting for '${message.id}', '${message.cluster}.${message.attribute}'`); return utils_1.default.getResponse(message, { id: message.id, endpoint: message.endpoint, cluster: message.cluster, maximum_report_interval: message.maximum_report_interval, minimum_report_interval: message.minimum_report_interval, reportable_change: message.reportable_change, attribute: message.attribute, }); } async deviceInterview(message) { if (typeof message !== "object" || message.id === undefined) { throw new Error("Invalid payload"); } const device = this.getEntity("device", message.id); logger_1.default.info(`Interviewing '${device.name}'`); try { await device.zh.interview(true); logger_1.default.info(`Successfully interviewed '${device.name}'`); } catch (error) { throw new Error(`interview of '${device.name}' (${device.ieeeAddr}) failed: ${error}`, { cause: error }); } // A re-interview can for example result in a different modelId, therefore reconsider the definition. await device.resolveDefinition(true); this.eventBus.emitDevicesChanged(); this.eventBus.emitExposesChanged({ device }); return utils_1.default.getResponse(message, { id: message.id }); } async deviceGenerateExternalDefinition(message) { if (typeof message !== "object" || message.id === undefined) { throw new Error("Invalid payload"); } const device = this.getEntity("device", message.id); const source = await zhc.generateExternalDefinitionSource(device.zh); return utils_1.default.getResponse(message, { id: message.id, source }); } async renameEntity(entityType, message) { const deviceAndHasLast = entityType === "device" && typeof message === "object" && message.last === true; if (typeof message !== "object" || (message.from === undefined && !deviceAndHasLast) || message.to === undefined) { throw new Error("Invalid payload"); } if (deviceAndHasLast && !this.lastJoinedDeviceIeeeAddr) { throw new Error("No device has joined since start"); } const from = deviceAndHasLast ? this.lastJoinedDeviceIeeeAddr : message.from; (0, utils_1.assertString)(message.to, "to"); const to = message.to.trim(); const homeAssisantRename = message.homeassistant_rename !== undefined ? message.homeassistant_rename : false; const entity = this.getEntity(entityType, from); const oldFriendlyName = entity.options.friendly_name; settings.changeFriendlyName(from, to); // Clear retained messages await this.mqtt.publish(oldFriendlyName, "", { clientOptions: { retain: true } }); this.eventBus.emitEntityRenamed({ entity: entity, homeAssisantRename, from: oldFriendlyName, to }); if (entity instanceof device_2.default) { await this.publishDevices(); } else { await this.publishGroups(); await this.publishInfo(); } // Republish entity state await this.publishEntityState(entity, {}); return utils_1.default.getResponse(message, { from: oldFriendlyName, to, homeassistant_rename: homeAssisantRename }); } async removeEntity(entityType, message) { const ID = typeof message === "object" ? message.id : message.trim(); const entity = this.getEntity(entityType, ID); // note: entity.name is dynamically retrieved, will change once device is removed (friendly => ieee) const friendlyName = entity.name; let block = false; let force = false; let blockForceLog = ""; if (entityType === "device" && typeof message === "object") { block = !!message.block; force = !!message.force; blockForceLog = ` (block: ${block}, force: ${force})`; } else if (entityType === "group" && typeof message === "object") { force = !!message.force; blockForceLog = ` (force: ${force})`; } try { logger_1.default.info(`Removing ${entityType} '${friendlyName}'${blockForceLog}`); if (entity instanceof device_2.default) { if (block) { settings.blockDevice(entity.ieeeAddr); } if (force) { entity.zh.removeFromDatabase(); } else { await entity.zh.removeFromNetwork(); } this.eventBus.emitEntityRemoved({ id: entity.ID, name: friendlyName, type: "device" }); settings.removeDevice(entity.ID); } else { if (force) { entity.zh.removeFromDatabase(); } else { await entity.zh.removeFromNetwork(); } this.eventBus.emitEntityRemoved({ id: entity.ID, name: friendlyName, type: "group" }); settings.removeGroup(entity.ID); } // Remove from state this.state.remove(entity.ID); // Clear any retained messages await this.mqtt.publish(friendlyName, "", { clientOptions: { retain: true } }); logger_1.default.info(`Successfully removed ${entityType} '${friendlyName}'${blockForceLog}`); if (entity instanceof device_2.default) { await this.publishGroups(); await this.publishDevices(); // Refresh Cluster definition await this.publishDefinitions(); const responseData = { id: ID, block, force }; return utils_1.default.getResponse(message, responseData); } await this.publishGroups(); const responseData = { id: ID, force }; return utils_1.default.getResponse(message, // @ts-expect-error typing infer does not work here responseData); } catch (error) { throw new Error(`Failed to remove ${entityType} '${friendlyName}'${blockForceLog} (${error})`); } } getEntity(type, id) { const entity = this.zigbee.resolveEntity(id); if (!entity || entity.constructor.name.toLowerCase() !== type) { throw new Error(`${utils_1.default.capitalize(type)} '${id}' does not exist`); } return entity; } async publishInfo() { const config = (0, object_assign_deep_1.default)({}, settings.get()); // @ts-expect-error hidden from publish delete config.advanced.network_key; delete config.mqtt.password; delete config.frontend.auth_token; const networkParams = await this.zigbee.getNetworkParameters(); const payload = { os: this.#osInfo, mqtt: this.mqtt.info, version: this.zigbee2mqttVersion.version, commit: this.zigbee2mqttVersion.commitHash, zigbee_herdsman_converters: this.zigbeeHerdsmanConvertersVersion, zigbee_herdsman: this.zigbeeHerdsmanVersion, coordinator: { ieee_address: this.zigbee.firstCoordinatorEndpoint().deviceIeeeAddress, ...this.coordinatorVersion, }, network: { pan_id: networkParams.panID, extended_pan_id: networkParams.extendedPanID, channel: networkParams.channel, }, log_level: logger_1.default.getLevel(), permit_join: this.zigbee.getPermitJoin(), permit_join_end: this.zigbee.getPermitJoinEnd(), restart_required: this.restartRequired, config, config_schema: settings.schemaJson, }; await this.mqtt.publish("bridge/info", (0, json_stable_stringify_without_jsonify_1.default)(payload), { clientOptions: { retain: true }, skipLog: true }); } async publishDevices() { const devices = []; for (const device of this.zigbee.devicesIterator()) { const endpoints = {}; for (const endpoint of device.zh.endpoints) { const data = { name: device.endpointName(endpoint), scenes: utils_1.default.getScenes(endpoint), bindings: [], configured_reportings: [], clusters: { input: endpoint.getInputClusters().map((c) => c.name), output: endpoint.getOutputClusters().map((c) => c.name), }, }; for (const bind of endpoint.binds) { const target = utils_1.default.isZHEndpoint(bind.target) ? { type: "endpoint", ieee_address: bind.target.deviceIeeeAddress, endpoint: bind.target.ID } : { type: "group", id: bind.target.groupID }; data.bindings.push({ cluster: bind.cluster.name, target }); } for (const configuredReporting of endpoint.configuredReportings) { data.configured_reportings.push({ cluster: configuredReporting.cluster.name, attribute: configuredReporting.attribute.name || configuredReporting.attribute.ID, minimum_report_interval: configuredReporting.minimumReportInterval, maximum_report_interval: configuredReporting.maximumReportInterval, reportable_change: configuredReporting.reportableChange, }); } endpoints[endpoint.ID] = data; } devices.push({ ieee_address: device.ieeeAddr, type: device.zh.type, network_address: device.zh.networkAddress, supported: device.isSupported, friendly_name: device.name, disabled: !!device.options.disabled, description: device.options.description, definition: this.getDefinitionPayload(device), power_source: device.zh.powerSource, software_build_id: device.zh.softwareBuildID, date_code: device.zh.dateCode, model_id: device.zh.modelID, /** @deprecated interviewing and interview_completed are superceded by interview_state */ interviewing: device.zh.interviewState === device_1.InterviewState.InProgress, interview_completed: device.zh.interviewState === device_1.InterviewState.Successful, interview_state: device.zh.interviewState, manufacturer: device.zh.manufacturerName, endpoints, }); } await this.mqtt.publish("bridge/devices", (0, json_stable_stringify_without_jsonify_1.default)(devices), { clientOptions: { retain: true }, skipLog: true }); } async publishGroups() { const groups = []; for (const group of this.zigbee.groupsIterator()) { const members = []; for (const member of group.zh.members) { members.push({ ieee_address: member.deviceIeeeAddress, endpoint: member.ID }); } groups.push({ id: group.ID, friendly_name: group.ID === utils_1.DEFAULT_BIND_GROUP_ID ? "default_bind_group" : group.name, description: group.options.description, scenes: utils_1.default.getScenes(group.zh), members, }); } await this.mqtt.publish("bridge/groups", (0, json_stable_stringify_without_jsonify_1.default)(groups), { clientOptions: { retain: true }, skipLog: true }); } async publishDefinitions() { const data = { clusters: zigbee_herdsman_1.Zcl.Clusters, custom_clusters: {}, }; for (const device of this.zigbee.devicesIterator((d) => !utils_1.default.objectIsEmpty(d.customClusters))) { data.custom_clusters[device.ieeeAddr] = device.customClusters; } await this.mqtt.publish("bridge/definitions", (0, json_stable_stringify_without_jsonify_1.default)(data), { clientOptions: { retain: true }, skipLog: true }); } getDefinitionPayload(device) { if (!device.definition) { return undefined; } // TODO: better typing to avoid @ts-expect-error // @ts-expect-error icon is valid for external definitions const definitionIcon = device.definition.icon; let icon = device.options.icon ?? definitionIcon; if (icon) { /* v8 ignore next */ icon = icon.replace("$zigbeeModel", utils_1.default.sanitizeImageParameter(device.zh.modelID ?? "")); icon = icon.replace("$model", utils_1.default.sanitizeImageParameter(device.definition.model)); } const payload = { source: device.definition.externalConverterName ? "external" : device.definition.generated ? "generated" : "native", model: device.definition.model, vendor: device.definition.vendor, description: device.definition.description, exposes: device.exposes(), supports_ota: !!device.definition.ota, options: device.definition.options ?? [], icon, }; return payload; } } exports.default = Bridge; __decorate([ bind_decorator_1.default ], Bridge.prototype, "onMQTTMessage", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceOptions", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "groupOptions", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "bridgeOptions", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceRemove", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "groupRemove", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "healthCheck", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "coordinatorCheck", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "groupAdd", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceRename", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "groupRename", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "restart", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "backup", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "installCodeAdd", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "permitJoin", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "touchlinkIdentify", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "touchlinkFactoryReset", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "touchlinkScan", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceConfigureReporting", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceInterview", null); __decorate([ bind_decorator_1.default ], Bridge.prototype, "deviceGenerateExternalDefinition", null); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJpZGdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vbGliL2V4dGVuc2lvbi9icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxzREFBeUI7QUFDekIsb0VBQWtDO0FBQ2xDLGtIQUE4RDtBQUM5RCxrREFBMEI7QUFDMUIsNEVBQWtEO0FBRWxELDBFQUEwQztBQUMxQyxxREFBb0M7QUFDcEMseUVBQTRFO0FBQzVFLGdFQUFrRDtBQUNsRCw2REFBcUM7QUFHckMsd0RBQWdDO0FBQ2hDLDREQUFvQztBQUNwQywyREFBNkM7QUFDN0MsdURBQXlFO0FBQ3pFLDREQUFvQztBQUVwQyxNQUFxQixNQUFPLFNBQVEsbUJBQVM7SUFDekMsYUFBYSxHQUFHLElBQUksTUFBTSxDQUFDLEdBQUcsUUFBUSxDQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxVQUFVLHNCQUFzQixDQUFDLENBQUM7SUFDcEYsaUJBQWlCO0lBQ2pCLE9BQU8sQ0FBdUM7SUFDdEMsa0JBQWtCLENBQTBDO0lBQzVELHFCQUFxQixDQUFxQjtJQUMxQywrQkFBK0IsQ0FBcUI7SUFDcEQsa0JBQWtCLENBQXlCO0lBQzNDLGVBQWUsR0FBRyxLQUFLLENBQUM7SUFDeEIsd0JBQXdCLENBQVU7SUFDbEMsd0JBQXdCLENBQVU7SUFDbEMsWUFBWSxDQUFxQjtJQUNqQyxhQUFhLEdBQWdIO1FBQ2pJLGdCQUFnQixFQUFFLElBQUksQ0FBQyxhQUFhO1FBQ3BDLDRCQUE0QixFQUFFLElBQUksQ0FBQyx3QkFBd0I7UUFDM0QsZUFBZSxFQUFFLElBQUksQ0FBQyxZQUFZO1FBQ2xDLGtCQUFrQixFQUFFLElBQUksQ0FBQyxlQUFlO1FBQ3hDLHFDQUFxQyxFQUFFLElBQUksQ0FBQyxnQ0FBZ0M7UUFDNUUsZUFBZSxFQUFFLElBQUksQ0FBQyxZQUFZO1FBQ2xDLFdBQVcsRUFBRSxJQUFJLENBQUMs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