Added Autodiscovery and refactoring
This commit is contained in:
@@ -4,10 +4,14 @@ mqtt:
|
||||
username: "username"
|
||||
password: "password"
|
||||
clientid: "test"
|
||||
prefix: "luminea2mqtt"
|
||||
autodiscover:
|
||||
enabled: true
|
||||
topic: homeassistant
|
||||
devices:
|
||||
- id: "sqy709956ply4inkx6ac87"
|
||||
key: "xxxxxxxxxxxxxxxx"
|
||||
topic: "tuya/device1"
|
||||
friendlyname: "device1"
|
||||
refresh: 30
|
||||
reconnect: 10
|
||||
type: luminea_nx_4458
|
||||
|
||||
24
readme.md
24
readme.md
@@ -4,6 +4,12 @@ This should also work with almost all other tuya devices, provided therre is a f
|
||||
|
||||
This bridge is mostly based on the work of: https://github.com/codetheweb/tuyapi
|
||||
|
||||
## Features
|
||||
* Easy intigration of luminea (tuya) device into HomeAssistant
|
||||
* Auto reconnect to tuya device
|
||||
* Supports Homeassistant Auto-Discovery
|
||||
* Easily extendable
|
||||
|
||||
## Supported devices
|
||||
At the moment:
|
||||
* luminea nx-4458
|
||||
@@ -66,20 +72,31 @@ mqtt:
|
||||
username: "username"
|
||||
password: "password"
|
||||
clientid: "test"
|
||||
prefix: "luminea2mqtt"
|
||||
```
|
||||
These values define the port and ip of the server, as well as the credentials for this client. All values must be specified. Make sure that the `clientid` is unique.
|
||||
The `prefix` is the prefix of all mqtt topics.
|
||||
|
||||
### Autodiscover
|
||||
```
|
||||
autodiscover:
|
||||
enabled: true
|
||||
topic: homeassistant
|
||||
```
|
||||
This enables auto discovery for home assistant. (See https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery)
|
||||
* `enabled`: enables autodiscovery.
|
||||
* `topic`: topic prefix for discovery messages
|
||||
### Device config
|
||||
```
|
||||
devices:
|
||||
- id: "sqy709956ply4inkx6ac87"
|
||||
key: "xxxxxxxxxxxxxxxx"
|
||||
topic: "tuya/device1"
|
||||
friendlyname: "device1"
|
||||
refresh: 30 #refresh intervall in s
|
||||
type: luminea_nx_4458
|
||||
```
|
||||
* `id` and `key`: Specify id an local key of tuya device. There are severeal tutorials avaliable online on how to get these keys. This depends on the app you have used to register your devices if you don't want to reset them. If you use iO.e, see `extractkeys.md` on how to get extract these values.
|
||||
* `topic`: base topic for mqtt. `/get` and `/set` topics are also used for turning the switch on or off.
|
||||
* `friendlyname`: The displayname of the device. Also sets the base topic for this device as following: `/{mqtt.prefix}/{friendlyname}` If this property is not set, friendlyname defaults to the `id`. `/get` and `/set` topics are also used for turning the switch on or off.
|
||||
* `refresh`: refresh intervall in s
|
||||
* `type`: name of device class. Class files are located in `./src/modules`
|
||||
|
||||
@@ -112,6 +129,9 @@ These functions must be implementd:
|
||||
* `startWatcher` is called after the connection is established
|
||||
* `stopWatcher` is called after disconnect (stop timers, unsubscribe mqtt,...)
|
||||
|
||||
These functions can be implemented:
|
||||
* `pushAutodiscover(deviceConfig)` is called to publish the device config
|
||||
|
||||
These variables are available:
|
||||
* `this.mqtt` reference to the mqtt client
|
||||
* `this.topicname` String: topic prefix
|
||||
|
||||
59
src/autodiscover.js
Normal file
59
src/autodiscover.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const log4js = require('log4js');
|
||||
const logger = log4js.getLogger("autodiscover");
|
||||
const configldr = require('./config')
|
||||
|
||||
logger.level = 'debug';
|
||||
let discover_mqtt = undefined
|
||||
|
||||
module.exports.setup = async (mqtt) => {
|
||||
discover_mqtt = mqtt
|
||||
if (!configldr.config.autodiscover.enabled) {
|
||||
logger.info(`Autodiscover disabled`)
|
||||
} else {
|
||||
const mqtt_prefix = `${configldr.config.autodiscover.topic}`
|
||||
logger.info(`Setting up autodiscover with prefix ${mqtt_prefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.publishDevice = async (deviceid,device_name,config) => {
|
||||
// deviceid: unique device id, ideally based on the tuya device id
|
||||
// device_name: firendlyname displayed in the gui
|
||||
// config: device config
|
||||
|
||||
let unique_identifier = `luminea2mqtt_${deviceid}`
|
||||
Object.keys(config).forEach(component =>{
|
||||
|
||||
let items = config[component]
|
||||
Object.keys(items).forEach(item =>{
|
||||
let mqtt_topic = `${configldr.config.autodiscover.topic}/${component}/${deviceid}/${item}/config`
|
||||
let temp_data = JSON.parse(JSON.stringify(config[component][item]))
|
||||
temp_data.unique_id = `${deviceid}_${item}_luminea2mqtt`
|
||||
temp_data.object_id = `${device_name}_${item}`
|
||||
temp_data.origin = {
|
||||
name : "luminea2mqtt",
|
||||
support_url: "https://github.com/dennis9819/luminea2mqtt"
|
||||
}
|
||||
temp_data.device = {
|
||||
identifiers : [
|
||||
unique_identifier
|
||||
],
|
||||
name: device_name,
|
||||
manufacturer: "dennisgunia",
|
||||
model: "Unknown",
|
||||
|
||||
//via_device: `luminea2mqtt_bridge_${configldr.config.mqtt.devenv1}`
|
||||
}
|
||||
|
||||
const payload_str = JSON.stringify(temp_data)
|
||||
logger.debug(`publish ${mqtt_topic}: ${payload_str}`)
|
||||
discover_mqtt.publish(mqtt_topic, payload_str)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
20
src/config.js
Normal file
20
src/config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const log4js = require('log4js');
|
||||
const YAML = require('yaml')
|
||||
const fs = require('fs')
|
||||
|
||||
const loggerInit = log4js.getLogger("initializer");
|
||||
loggerInit.level = 'info';
|
||||
|
||||
module.exports.config = {}
|
||||
module.exports.loadConfig = async (configfile) => {
|
||||
loggerInit.info(`Read configfile ${configfile}`)
|
||||
try {
|
||||
const file = fs.readFileSync(configfile, 'utf8')
|
||||
module.exports.config = YAML.parse(file)
|
||||
} catch (error) {
|
||||
loggerInit.error(`error reading config: ${error.message}`)
|
||||
process.exit(10)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.config
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
const TuyaDevice = require('tuyapi');
|
||||
const log4js = require('log4js');
|
||||
const autodiscover = require('./autodiscover')
|
||||
const configldr = require('./config')
|
||||
|
||||
class DeviceBase {
|
||||
constructor(deviceconfig, mqtt) {
|
||||
@@ -17,19 +19,19 @@ class DeviceBase {
|
||||
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.deviceid = deviceconfig.id // tuya device id
|
||||
this.devicekey = deviceconfig.key // tuya device key
|
||||
|
||||
this.topic_get = `${deviceconfig.topic}/get`
|
||||
this.topic_set = `${deviceconfig.topic}/set`
|
||||
this.topic_state = `${deviceconfig.topic}/state`
|
||||
this.mqtt = mqtt // pointer to mqtt client
|
||||
// name to be displayed. If not set, default to prefixed tuya client id
|
||||
this.friendlyname = deviceconfig.friendlyname ? deviceconfig.friendlyname : `luminea2mqtt_${this.deviceid}`
|
||||
// define queue names. Prefix is set globally. Prefer friendly name. If not set, use client id
|
||||
this.queue_name = deviceconfig.friendlyname ? deviceconfig.friendlyname : this.deviceid
|
||||
this.topicname = `${configldr.config.mqtt.prefix}/${this.queue_name}`
|
||||
this.topic_get = `${configldr.config.mqtt.prefix}/${this.queue_name}/get`
|
||||
this.topic_set = `${configldr.config.mqtt.prefix}/${this.queue_name}/set`
|
||||
this.topic_state = `${configldr.config.mqtt.prefix}/${this.queue_name}/state`
|
||||
|
||||
this.logger.debug(`use topic (all) : ${this.topicname}`)
|
||||
this.logger.debug(`use topic (get) : ${this.topic_get}`)
|
||||
@@ -92,10 +94,17 @@ class DeviceBase {
|
||||
});
|
||||
}
|
||||
|
||||
pushAutodiscover(config){
|
||||
if (configldr.config.autodiscover.enabled){
|
||||
autodiscover.publishDevice(this.deviceid,this.friendlyname,config)
|
||||
}
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
this.device.find().then(el => {
|
||||
if (el) {
|
||||
this.device.connect().catch(el => {
|
||||
console.log(this.device)
|
||||
this.logger.debug("Reconnect failed: device offline")
|
||||
})
|
||||
} else {
|
||||
|
||||
64
src/devicemanager.js
Normal file
64
src/devicemanager.js
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
const TuyaDevice = require('tuyapi');
|
||||
const log4js = require('log4js');
|
||||
const autodiscover = require('./autodiscover')
|
||||
const configldr = require('./config')
|
||||
|
||||
class DeviceManager {
|
||||
constructor() {
|
||||
// setup logger
|
||||
this.devices = []
|
||||
this.logger = log4js.getLogger("devicemanager");
|
||||
this.logger.level = configldr.config.loglevel;
|
||||
this.config = configldr.config
|
||||
this.connected = false
|
||||
|
||||
|
||||
}
|
||||
|
||||
setClient(mqtt){
|
||||
this.mqtt = mqtt
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (!this.connected) {
|
||||
this.logger.info(`Setup all devices...`)
|
||||
this.connected = true
|
||||
if (this.config.devices) {
|
||||
this.config.devices.forEach((device) => {
|
||||
device.loglevel = configldr.config.loglevel;
|
||||
const deviceClassFile = `./modules/${device.type}`
|
||||
this.logger.info(`Setup device ${device.id}, type: ${device.type}, class:'${deviceClassFile}.js'`)
|
||||
try {
|
||||
const DeviceClass = require(deviceClassFile)
|
||||
const newdev = new DeviceClass(device, this.mqtt)
|
||||
this.devices.push(newdev)
|
||||
} catch (error) {
|
||||
this.logger.error(`Error initializing device class ${deviceClassFile}`);
|
||||
this.logger.error(error.message);
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
this.logger.error(`Missing 'devices' in config.`)
|
||||
process.exit(10)
|
||||
}
|
||||
} else {
|
||||
this.logger.debug("Devices already connected")
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (this.connected) {
|
||||
this.connected = false
|
||||
for (let device of devices) {
|
||||
await device.disconnect()
|
||||
}
|
||||
} else {
|
||||
this.logger.debug("Devices already disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DeviceManager
|
||||
65
src/index.js
65
src/index.js
@@ -3,10 +3,15 @@ const logger = log4js.getLogger();
|
||||
const loggerInit = log4js.getLogger("initializer");
|
||||
const { Command } = require('commander');
|
||||
const program = new Command();
|
||||
const configldr = require('./config')
|
||||
const autodiscover = require('./autodiscover')
|
||||
const mqtt = require("mqtt");
|
||||
const DeviceManager = require('./devicemanager')
|
||||
|
||||
logger.level = 'info';
|
||||
loggerInit.level = 'info';
|
||||
|
||||
|
||||
program
|
||||
.name('luminea2mqtt')
|
||||
.version('1.0.0')
|
||||
@@ -14,39 +19,31 @@ program
|
||||
.parse(process.argv);
|
||||
|
||||
async function main() {
|
||||
const mqtt = require("mqtt");
|
||||
const YAML = require('yaml')
|
||||
const fs = require('fs')
|
||||
|
||||
loggerInit.info("Read configfile")
|
||||
|
||||
const options = program.opts();
|
||||
loggerInit.info(`User config from ${options.config}`)
|
||||
let config = {}
|
||||
try {
|
||||
const file = fs.readFileSync(options.config, 'utf8')
|
||||
config = YAML.parse(file)
|
||||
} catch (error) {
|
||||
loggerInit.error(`error reading config: ${error.message}`)
|
||||
process.exit(10)
|
||||
}
|
||||
|
||||
configldr.loadConfig(options.config)
|
||||
|
||||
|
||||
const mqttserver = `mqtt://${config.mqtt.host}:${config.mqtt.port}`
|
||||
let deviceManager = new DeviceManager()
|
||||
|
||||
const mqttserver = `mqtt://${configldr.config.mqtt.host}:${configldr.config.mqtt.port}`
|
||||
let client = mqtt.connect(mqttserver, {
|
||||
// Clean session
|
||||
connectTimeout: 1000,
|
||||
// Authentication
|
||||
username: config.mqtt.username,
|
||||
password: config.mqtt.password,
|
||||
clientId: config.mqtt.clientid,
|
||||
username: configldr.config.mqtt.username,
|
||||
password: configldr.config.mqtt.password,
|
||||
clientId: configldr.config.mqtt.clientid,
|
||||
debug: true,
|
||||
});
|
||||
loggerInit.info(`Connect to ${mqttserver}, user: ${config.mqtt.username}, clientid: ${config.mqtt.clientid}`)
|
||||
|
||||
loggerInit.info(`Connect to ${mqttserver}, user: ${configldr.config.mqtt.username}, clientid: ${configldr.config.mqtt.clientid}`)
|
||||
autodiscover.setup(client)
|
||||
deviceManager.setClient(client)
|
||||
|
||||
client.on('connect', function () {
|
||||
loggerInit.info(`Connected to ${mqttserver}`)
|
||||
// Subscribe to a topic
|
||||
deviceManager.connect()
|
||||
})
|
||||
client.on("reconnect", () => {
|
||||
loggerInit.info(`Try reconnect to ${mqttserver}`)
|
||||
@@ -57,32 +54,8 @@ async function main() {
|
||||
client.end()
|
||||
});
|
||||
|
||||
let devices = []
|
||||
|
||||
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 {
|
||||
const DeviceClass = require(deviceClassFile)
|
||||
const newdev = new DeviceClass(device, client)
|
||||
devices.push(newdev)
|
||||
} catch (error) {
|
||||
loggerInit.error(`Error initializing device class ${deviceClassFile}`);
|
||||
loggerInit.error(error.message);
|
||||
}
|
||||
|
||||
})
|
||||
}else{
|
||||
loggerInit.error(`Missing 'devices' in config.`)
|
||||
process.exit(10)
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
for (let device of devices) {
|
||||
device.disconnect()
|
||||
}
|
||||
deviceManager.disconnect()
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,52 @@ class Lineplug extends DeviceBase {
|
||||
countdown_1: 0,
|
||||
random_time: 0,
|
||||
}
|
||||
|
||||
this.deviceConfig = {
|
||||
switch: {
|
||||
switch: {
|
||||
component: "switch",
|
||||
command_topic: this.topic_set,
|
||||
state_topic: this.topicname,
|
||||
payload_on: "{\"value\": true}",
|
||||
payload_off: "{\"value\": false}",
|
||||
optimistic: false,
|
||||
device_class: "outlet",
|
||||
value_template: "{{ value_json.status }}",
|
||||
state_off: false,
|
||||
state_on: true,
|
||||
enabled_by_default: true
|
||||
},
|
||||
},
|
||||
sensor: {
|
||||
voltage: {
|
||||
state_topic: this.topicname,
|
||||
device_class: "voltage",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.voltage }}",
|
||||
unit_of_measurement: "V",
|
||||
enabled_by_default: true
|
||||
},
|
||||
current: {
|
||||
state_topic: this.topicname,
|
||||
device_class: "current",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.current }}",
|
||||
unit_of_measurement: "A",
|
||||
enabled_by_default: true
|
||||
},
|
||||
power: {
|
||||
state_topic: this.topicname,
|
||||
device_class: "power",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.power }}",
|
||||
unit_of_measurement: "W",
|
||||
enabled_by_default: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
this.pushAutodiscover(this.deviceConfig)
|
||||
}
|
||||
|
||||
startWatcher() {
|
||||
@@ -57,7 +103,7 @@ class Lineplug extends DeviceBase {
|
||||
})
|
||||
}
|
||||
|
||||
stopWatcher(){
|
||||
stopWatcher() {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user