UNPKG

iobroker.ds18b20

Version:

This is an ioBroker-Adapter to integrate DS18B20 1-wire temperature sensors.

522 lines (521 loc) 20.5 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 __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 __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; var main_exports = {}; module.exports = __toCommonJS(main_exports); var import_register = require("source-map-support/register"); var import_promises = require("fs/promises"); var crypto = __toESM(require("crypto")); var import_adapter_core = require("@iobroker/adapter-core"); var import_autobind_decorator = require("autobind-decorator"); var import_sensor = require("./sensor"); var import_remote_server = require("./remote-server"); var import_utils = require("./lib/utils"); var import_i18n = require("./lib/i18n"); class Ds18b20Adapter extends import_adapter_core.Adapter { /** * Constructor to create a new instance of the adapter. * @param options The adapter options. */ constructor(options = {}) { super({ ...options, name: "ds18b20" }); /** * The server for remote sensors if enabled. */ this.remoteSensorServer = null; /** * Mapping of the ioBroker object IDs to the sensor class instances. */ this.sensors = {}; /** * Internal indicator if we are doing a migration from an old version. */ this.doingMigration = false; this.on("ready", this.onReady); this.on("stateChange", this.onStateChange); this.on("message", this.onMessage); this.on("unload", this.onUnload); } /** * Get a defined sensor from it's ioBroker ID or 1-wire address. * @param idOrAddress The ID or address of the sensor. * @return The sensor or null. */ getSensor(idOrAddress) { if (this.sensors[idOrAddress]) return this.sensors[idOrAddress]; const m = /^ds18b20\.\d+\.sensors\.(.+)$/.exec(idOrAddress); if (m && this.sensors[m[1]]) { return this.sensors[m[1]]; } return null; } async onReady() { void this.setState("info.connection", false, true); const systemConfig = await this.getForeignObjectAsync("system.config"); import_i18n.i18n.language = (systemConfig == null ? void 0 : systemConfig.common.language) ?? "en"; if (!this.config.w1DevicesPath) { this.config.w1DevicesPath = "/sys/bus/w1/devices"; } if (Object.keys(this.config).includes("_values")) { this.log.info("Migrate config from old version ..."); this.doingMigration = true; const instanceObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); if (!instanceObj) { this.log.error("Could not read instance object!"); this.terminate("Config migration required", import_adapter_core.EXIT_CODES.INVALID_ADAPTER_CONFIG); return; } const oldNative = instanceObj.native; const newNative = { defaultInterval: oldNative.defaultInterval, remoteEnabled: oldNative.remoteEnabled, remoteKey: "", // a new remote key must be created in admin! remotePort: oldNative.remotePort, w1DevicesPath: oldNative.w1DevicesPath, sensors: [] }; if (newNative.remoteEnabled) { this.log.warn("You have remote sensor enabled. It is required to set a new remote key in admin and update the remote configs!"); } oldNative._values.sort((a, b) => { if (typeof a.sortOrder === "number" && typeof b.sortOrder === "number") { return a.sortOrder - b.sortOrder; } return 0; }); for (const oldSensor of oldNative._values) { const sensor = { address: oldSensor.address, remoteSystemId: oldSensor.remoteSystemId ?? "", name: oldSensor.name || oldSensor.address, interval: oldSensor.interval ?? null, unit: oldSensor.unit ?? "\xB0C", factor: oldSensor.factor ?? 1, offset: oldSensor.offset ?? 0, decimals: oldSensor.decimals ?? 2, nullOnError: !!oldSensor.nullOnError, enabled: !!oldSensor.enabled }; this.log.info(`Migrate sensor ${JSON.stringify(sensor)}`); newNative.sensors.push(sensor); const sensorObj = await this.getObjectAsync(`sensors.${sensor.address}`); if (sensorObj) { sensorObj.native = {}; await this.setObjectAsync(`sensors.${sensor.address}`, sensorObj); } } await Promise.all([ this.delObjectAsync("actions"), this.delObjectAsync("actions.readNow"), this.delObjectAsync("info"), this.delObjectAsync("info.connection"), this.delObjectAsync("sensors") ]); instanceObj.native = newNative; this.log.info("Rewriting adapter config"); await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instanceObj); this.terminate("Restart adapter to apply config changes", import_adapter_core.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP); return; } if (this.config.remoteEnabled) { if (!this.config.remotePort || this.config.remotePort <= 0) { this.log.warn("Config: Invalid port for the remote sensor server! Using default port 1820."); this.config.remotePort = 1820; } if (typeof this.config.remoteKey !== "string" || this.config.remoteKey.length !== 64) { this.config.remoteKey = crypto.randomBytes(32).toString("hex"); this.log.error(`Config: Invalid key for the remote sensor server! Using random key "${this.config.remoteKey}".`); } await this.extendObject("info.remotesConnected", { type: "state", common: { name: import_i18n.i18n.getStringOrTranslated("Connected remote systems"), type: "string", role: "state", read: true, write: false, def: "" }, native: {} }); await this.setState("info.remotesConnected", "", true); this.remoteSensorServer = new import_remote_server.RemoteSensorServer(this.config.remotePort, this.config.remoteKey, this); this.remoteSensorServer.on("listening", () => { this.log.info(`Remote sensor server is listening on port ${this.config.remotePort}`); this.updateInfoConnection(); }); this.remoteSensorServer.on("error", (err) => { this.log.warn(`Remote sensor server error: ${err.toString()}`); this.log.debug(`${err.toString()} ${err.stack}`); this.updateInfoConnection(); }); this.remoteSensorServer.on("remotesChanged", (remotes) => { void this.setState("info.remotesConnected", remotes.join(","), true); }); } else { if (await this.getObjectAsync("info.remotesConnected")) { await this.delObjectAsync("info.remotesConnected"); } } if (!Array.isArray(this.config.sensors)) { this.config.sensors = []; } for (const sensorCfg of this.config.sensors) { if (!/^[0-9a-f]{2}-[0-9a-f]{12}$/.test(sensorCfg.address)) { this.log.warn(`Invalid sensor address configured: ${sensorCfg.address}`); continue; } if (this.sensors[sensorCfg.address]) { this.log.warn(`Sensor ${sensorCfg.address} is configured twice! Ignoring the all expect the first.`); continue; } if (sensorCfg.remoteSystemId && !this.config.remoteEnabled) { this.log.warn(`Sensor ${sensorCfg.address} is configured as remote sensor of ${sensorCfg.remoteSystemId} but remote sensors are not enabled!`); continue; } const name = sensorCfg.name || sensorCfg.address; await this.extendObject(`sensors.${sensorCfg.address}`, { type: "state", common: { name: sensorCfg.enabled ? name : import_i18n.i18n.getStringOrTranslated("%s (disabled)", name), type: "number", role: "value.temperature", unit: sensorCfg.unit || "\xB0C", read: true, write: false, def: null, icon: sensorCfg.enabled ? "ds18b20.png" : "sensor_disabled.png" }, native: {} }); if (!sensorCfg.enabled) { this.log.debug(`Sensor ${sensorCfg.address} is not enabled`); continue; } let interval; if (typeof sensorCfg.interval === "number") { interval = sensorCfg.interval; } else if (typeof sensorCfg.interval === "string" && sensorCfg.interval.length > 0) { interval = parseInt(sensorCfg.interval, 10); if (isNaN(interval)) { this.log.warn(`Query interval for sensor ${sensorCfg.address} is invalid! Using default.`); interval = typeof this.config.defaultInterval === "number" ? this.config.defaultInterval : parseInt(this.config.defaultInterval, 10); } } else { interval = typeof this.config.defaultInterval === "number" ? this.config.defaultInterval : parseInt(this.config.defaultInterval, 10); } this.sensors[sensorCfg.address] = new import_sensor.Sensor({ w1DevicesPath: this.config.w1DevicesPath, address: sensorCfg.address, interval, nullOnError: !!sensorCfg.nullOnError, factor: typeof sensorCfg.factor === "number" ? sensorCfg.factor : 1, offset: typeof sensorCfg.offset === "number" ? sensorCfg.offset : 0, decimals: typeof sensorCfg.decimals === "number" ? sensorCfg.decimals : null, remoteSystemId: typeof sensorCfg.remoteSystemId === "string" ? sensorCfg.remoteSystemId : null }, this); this.sensors[sensorCfg.address].on("value", this.handleSensorValue); this.sensors[sensorCfg.address].on("error", this.handleSensorError); this.sensors[sensorCfg.address].on("errorStateChanged", this.handleSensorErrorStateChanged); } const count = Object.keys(this.sensors).length; this.log.debug(`Loaded ${count} enabled sensors`); if (count === 0) { this.log.warn("No sensors configured or enabled!"); } const objListSensors = await this.getObjectListAsync({ startkey: `${this.namespace}.sensors.`, endkey: `${this.namespace}.sensors.\u9999` }); const reAddress = new RegExp(`^${this.name}\\.${this.instance}\\.sensors\\.(.+)$`); for (const item of objListSensors.rows) { const m = item.id.match(reAddress); if (m) { const addr = m[1]; if (!this.config.sensors.find((s) => s.address === addr)) { this.log.info(`Delete object ${item.id} since sensor is not configured`); await this.delObjectAsync(item.id); } } } this.subscribeStates("actions.*"); } async onUnload(callback) { try { for (const address in this.sensors) { this.sensors[address].stop(); } if (this.remoteSensorServer) { await this.remoteSensorServer.stop(); await this.setState("info.remotesConnected", "", true); } if (!this.doingMigration) { await this.setState("info.connection", false, true); } } catch (_err) { } callback(); } handleSensorValue(value, address) { if (!this.sensors[address]) return; this.log.debug(`Got value ${value} from sensor ${address}`); if (value === null) { void this.setState(`sensors.${address}`, { ack: true, val: null, q: 129 // general problem by sensor }); } else { void this.setState(`sensors.${address}`, { ack: true, val: value }); } } handleSensorError(err, address) { this.log.warn(`Error reading sensor ${address}: ${err}`); } handleSensorErrorStateChanged(hasError, address) { this.log.debug(`Error state of sensor ${address} changed to ${hasError}`); void this.extendObject(`sensors.${address}`, { common: { icon: hasError ? "sensor_error.png" : "sensor_ok.png" } }); this.updateInfoConnection(); } /** * Update the info.connection state depending on the error state of all * sensors and the listening state of the remote sensor server. */ updateInfoConnection() { if (this.remoteSensorServer && !this.remoteSensorServer.isListening()) { void this.setState("info.connection", false, true); return; } if (Object.keys(this.sensors).length === 0) { void this.setState("info.connection", false, true); return; } for (const address in this.sensors) { if (this.sensors[address].hasError) { void this.setState("info.connection", false, true); return; } } void this.setState("info.connection", true, true); } async readNow(idOrAddress) { if (typeof idOrAddress !== "string" || idOrAddress === "all" || idOrAddress === "") { this.log.info(`Read data from all sensors now`); const results = {}; for (const address in this.sensors) { try { results[address] = await this.sensors[address].read(); } catch (_err) { results[address] = null; } } return results; } else { const sens = this.getSensor(idOrAddress); if (!sens) { this.log.warn(`No sensor with address or id ${idOrAddress} found!`); return null; } this.log.info(`Read data from sensor ${sens.address} now`); return await sens.read(); } } /** * Search for local and remote sensors. * @returns Array of the found sensors */ async searchSensors() { const sensors = []; try { const files = await (0, import_promises.readdir)(this.config.w1DevicesPath); const proms = []; for (const file of files) { if (/^w1_bus_master\d+$/.test(file)) { this.log.debug(`Reading ${this.config.w1DevicesPath}/${file}/w1_master_slaves`); proms.push((0, import_promises.readFile)(`${this.config.w1DevicesPath}/${file}/w1_master_slaves`, "utf8")); } else if (file === "w1_master_slaves") { this.log.debug(`Reading ${this.config.w1DevicesPath}/w1_master_slaves`); proms.push((0, import_promises.readFile)(`${this.config.w1DevicesPath}/w1_master_slaves`, "utf8")); } } const localSensors = (await Promise.all(proms)).reduce((acc, cur) => { acc.push(...cur.trim().split("\n")); return acc; }, []).map((addr) => ({ address: addr, remoteSystemId: "" })); sensors.push(...localSensors); } catch (err) { this.log.warn(`Error while searching for local sensors: ${err}`); } if (this.config.remoteEnabled && this.remoteSensorServer) { try { const remoteSensors = await this.remoteSensorServer.search(); sensors.push(...remoteSensors); } catch (err) { this.log.warn(`Error while searching for remote sensors: ${err}`); } } this.log.debug(`Sensors found: ${JSON.stringify(sensors)}`); return sensors; } async onStateChange(id, state) { if (!state || state.ack) { return; } if (id === `${this.namespace}.actions.readNow`) { await this.readNow(state.val).catch(() => { }); await this.setState(this.namespace + ".actions.readNow", "", true); } } async onMessage(obj) { var _a; this.log.debug("Got message " + JSON.stringify(obj)); if (typeof obj === "object") { switch (obj.command) { case "read": case "readNow": try { const value = typeof obj.message === "string" ? await this.readNow(obj.message) : await this.readNow(); if (obj.callback) { this.sendTo(obj.from, obj.command, { err: null, value }, obj.callback); } return; } catch (err) { this.log.debug(`${err}`); if (obj.callback) { this.sendTo(obj.from, obj.command, { err: `${err}`, value: null }, obj.callback); } } break; case "getRemoteSystems": if (!obj.callback) return; if (!this.remoteSensorServer) { this.sendTo(obj.from, obj.command, [], obj.callback); return; } this.sendTo(obj.from, obj.command, this.remoteSensorServer.getConnectedSystems(), obj.callback); break; case "getRemoteSystemsAdminUi": { if (!obj.callback) return; let remotes = (_a = this.remoteSensorServer) == null ? void 0 : _a.getConnectedSystems().join(", "); if (!remotes) { remotes = "---"; } this.sendTo(obj.from, obj.command, remotes, obj.callback); break; } case "search": case "searchSensors": if (!obj.callback) return; this.sendTo(obj.from, obj.command, { sensors: await this.searchSensors() }, obj.callback); break; case "searchSensorsAdminUi": { if (!obj.callback) return; const sensors = []; if (typeof obj.message === "object" && Array.isArray(obj.message.sensors)) { sensors.push(...obj.message.sensors); } const foundSensors = await this.searchSensors(); for (const foundSensor of foundSensors) { if (sensors.findIndex((cfgSensor) => cfgSensor.address === foundSensor.address && cfgSensor.remoteSystemId === foundSensor.remoteSystemId) < 0) { sensors.push({ address: foundSensor.address, remoteSystemId: foundSensor.remoteSystemId, name: "", interval: null, unit: "\xB0C", factor: 1, offset: 0, decimals: 2, nullOnError: true, enabled: true }); } } this.sendTo(obj.from, obj.command, { native: { sensors } }, obj.callback); break; } case "getNewRemoteKey": if (!obj.callback) return; this.sendTo(obj.from, obj.command, { native: { remoteKey: (0, import_utils.genHexString)(64) } }, obj.callback); break; } } } } __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "onReady", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "onUnload", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "handleSensorValue", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "handleSensorError", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "handleSensorErrorStateChanged", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "onStateChange", 1); __decorateClass([ import_autobind_decorator.boundMethod ], Ds18b20Adapter.prototype, "onMessage", 1); if (require.main !== module) { module.exports = (options) => new Ds18b20Adapter(options); } else { (() => new Ds18b20Adapter())(); } //# sourceMappingURL=main.js.map