diff --git a/src/devicebase.js b/src/devicebase.js new file mode 100644 index 0000000..38371fa --- /dev/null +++ b/src/devicebase.js @@ -0,0 +1,120 @@ + +const TuyaDevice = require('tuyapi'); +const log4js = require('log4js'); + +class DeviceBase { + constructor(deviceconfig, mqtt) { + // setup logger + const loggername = deviceconfig.id ? deviceconfig.id : "undef device" + this.logger = log4js.getLogger(loggername); + this.logger.level = deviceconfig.loglevel; + // check device attributes + if (!deviceconfig.id) { + this.logger.error("missing attribute 'id' in device config") + return + } + if (!deviceconfig.key) { + this.logger.error("missing attribute 'key' in device config") + return + } + if (!deviceconfig.topic) { + this.logger.error("missing attribute 'topic' in device config") + return + } + // define device vars + this.mqtt = mqtt + this.topicname = deviceconfig.topic + this.deviceid = deviceconfig.id + this.devicekey = deviceconfig.key + + this.topic_get = `${deviceconfig.topic}/get` + this.topic_set = `${deviceconfig.topic}/set` + this.topic_state = `${deviceconfig.topic}/state` + + this.logger.debug(`use topic (all) : ${this.topicname}`) + this.logger.debug(`use topic (get) : ${this.topic_get}`) + this.logger.debug(`use topic (set) : ${this.topic_set}`) + this.logger.debug(`use topic (state) : ${this.topic_state}`) + + this.intervall_refresh = deviceconfig.refresh ? deviceconfig.refresh * 1000 : 10000 + this.reconnect_timout = deviceconfig.reconnect ? deviceconfig.reconnect * 1000 : 5000 + + this.logger.debug(`refresh timer (ms) : ${this.intervall_refresh}`) + this.logger.debug(`reconnect timer (ms) : ${this.reconnect_timout}`) + + this.lastdata = {} + // call init function + this.init() + + // connect to device + try { + this.device = new TuyaDevice({ + id: this.deviceid, + key: this.devicekey, + issueRefreshOnConnect: true, + }) + this.reconnect() + + } catch (error) { + this.logger.error(`Cannot connect to ${this.deviceid}`) + this.logger.error(error.message) + } + + this.device.on('connected', () => { + this.connected = true + this.logger.info(`Connected to tuya id: ${this.deviceid}, ip: ${this.device.device.ip}, prefix: ${this.topicname}`) + this.startWatcher() + // subscribe to topic + this.mqtt.subscribe(this.topic_set, (err) => { + if (err) { + this.logger.error(`Cannot subscribe to ${this.topic_set}`) + } else { + this.logger.info(`Subscribed to ${this.topic_set}`) + } + }); + this.logger.debug(`publish ${this.totopic_statepic_get}: "online"`) + this.mqtt.publish(this.topic_state, "online") + }); + + this.device.on('disconnected', () => { + clearInterval(this.timer) + this.logger.info(`Disconnected`) + this.logger.debug(`publish ${this.topic_state}: "offline"`) + this.mqtt.publish(this.topic_state, "offline") + setTimeout(() => this.reconnect(), this.reconnect_timout) + this.device.device.ip = undefined // will error without? WHY??? + }); + + this.device.on('error', error => { + clearInterval(this.timer) + this.logger.debug(`publish ${this.topic_state}: "offline"`) + this.mqtt.publish(this.topic_state, "offline") + setTimeout(() => this.reconnect(), this.reconnect_timout) + }); + } + + reconnect() { + this.device.find().then(el => { + if (el) { + this.device.connect() + } else { + this.logger.debug("Reconnect failed: device offline") + setTimeout(() => this.reconnect(), this.reconnect_timout) + } + }).catch(el => { + this.logger.debug("Reconnect failed: device timed out") + setTimeout(() => this.reconnect(), this.reconnect_timout) + }) + } + + disconnect() { + this.connected = false + clearInterval(this.timer) + this.device.disconnect() + this.logger.info(`Disconnected for id: ${this.deviceid}`) + this.reconnect = () => {} // prevent reconnect on exit + } + +} + +module.exports = DeviceBase \ No newline at end of file diff --git a/src/index.js b/src/index.js index a81e5bb..cc889d1 100644 --- a/src/index.js +++ b/src/index.js @@ -49,7 +49,7 @@ async function main() { // Subscribe to a topic }) client.on("reconnect", () => { - loggerInit.info("reconnecting!") + loggerInit.info(`Try reconnect to ${mqttserver}`) }) client.stream.on('error', (err) => { loggerInit.error('error') @@ -61,6 +61,7 @@ async function main() { if (config.devices){ config.devices.forEach((device) => { + device.loglevel = config.loglevel const deviceClassFile = `./modules/${device.type}` loggerInit.info(`Setup device ${device.id}, type: ${device.type}, class:'${deviceClassFile}.js'`) try { diff --git a/src/modules/luminea_nx_4458.js b/src/modules/luminea_nx_4458.js index 00dd0ca..6ec3536 100644 --- a/src/modules/luminea_nx_4458.js +++ b/src/modules/luminea_nx_4458.js @@ -1,31 +1,14 @@ -const TuyaDevice = require('tuyapi'); -const log4js = require('log4js'); +/* +* Device class definition for Luminea NX-445 +* https://www.luminea.info/Outdoor-WLAN-Steckdose-kompatibel-zu-Alexa-NX-4458-919.shtml +* +* by Dennis Gunia (08/2024) +*/ -class Lineplug { - constructor(deviceconfig, mqtt) { - const loggername = deviceconfig.id ? deviceconfig.id : "undef device" - this.logger = log4js.getLogger(loggername); - - if (!deviceconfig.id) { - this.logger.error("missing attribute 'id' in device config") - return - } - if (!deviceconfig.key) { - this.logger.error("missing attribute 'key' in device config") - return - } - if (!deviceconfig.topic) { - this.logger.error("missing attribute 'topic' in device config") - return - } - - this.mqtt = mqtt - this.topicname = deviceconfig.topic - this.deviceid = deviceconfig.id - this.topic_get = `${deviceconfig.topic}/get` - this.topic_set = `${deviceconfig.topic}/set` - this.intervall = deviceconfig.refresh ? deviceconfig.refresh * 1000 : 10000 +const DeviceBase = require('../devicebase') +class Lineplug extends DeviceBase { + init() { this.lastdata = { voltage: 0, current: 0, @@ -36,49 +19,27 @@ class Lineplug { countdown_1: 0, random_time: 0, } - try { - this.device = new TuyaDevice({ - id: deviceconfig.id, - key: deviceconfig.key, - issueRefreshOnConnect: true, - ip: deviceconfig.ip, - }) - this.device.find().then(el => { - this.device.connect().then(el => { - this.logger.info(`Connected to tuya id: ${this.deviceid} @ ${this.topicname}`) - this.startWatcher() - this.mqtt.subscribe(this.topic_set, (err) => { - if (err) { - this.logger.error(`Cannot subscribe to ${this.topic_set}`) - } else { - this.logger.info(`Subscribed to ${this.topic_set}`) - } - }); - }).catch(error => { - this.logger.error(`Cannot connect to ${this.deviceid}`) - this.logger.error(error.message) - }); - }) - } catch (error) { - this.logger.error(`Cannot connect to ${this.deviceid}`) - this.logger.error(error.message) - } } startWatcher() { // monitoring loop - this.timer = setInterval(() => this.device.refresh(), 10000) + this.timer = setInterval(() => { + this.device.refresh() + }, this.intervall_refresh) this.logger.info(`Started watcher for id: ${this.deviceid}`) this.device.on('data', data => { + this.logger.debug(`rx data: ${JSON.stringify(data)}`) this.processData(data) }); this.device.on('dp-refresh', data => { + this.logger.debug(`rx dp-refresh: ${JSON.stringify(data)}`) this.processData(data) }); // monitor queue this.mqtt.on('message', (topic, message) => { // message is Buffer let payload = message.toString() + this.logger.debug(`input ${topic}: ${payload}`) try { const jsonpayload = JSON.parse(payload) @@ -121,7 +82,7 @@ class Lineplug { changed = true } if (updatedValues.includes('9')) { - this.lastdata.countdown_1 = dps['9'] + this.lastdata.countdown_1 = dps['9'] changed = true } if (updatedValues.includes('41')) { @@ -135,20 +96,19 @@ class Lineplug { if (updatedValues.includes('1')) { this.lastdata.status = dps['1'] changed = true - this.mqtt.publish(this.topic_get, JSON.stringify({ + const msg = { value: this.lastdata.status - })) + } + this.mqtt.publish(this.topic_get, JSON.stringify(msg)) + this.logger.debug(`publish ${this.topic_get}: ${JSON.stringify(msg)}`) } if (changed) { this.mqtt.publish(this.topicname, JSON.stringify(this.lastdata)) + this.logger.debug(`publish ${this.topicname}: ${JSON.stringify(this.lastdata)}`) + } } - disconnect() { - clearInterval(this.timer) - this.device.disconnect() - this.logger.info(`Disconnected for id: ${this.deviceid}`) - } } module.exports = Lineplug \ No newline at end of file