init
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
config.yaml
|
||||
node_modules/
|
||||
package-lock.json
|
||||
10
config-example.yaml
Normal file
10
config-example.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
mqtt:
|
||||
host: "127.0.0.1"
|
||||
port: 1883
|
||||
username: "username"
|
||||
password: "password"
|
||||
lineaplug:
|
||||
- id: "sqy709956ply4inkx6ac87"
|
||||
key: "xxxxxxxxxxxxxxxx"
|
||||
topic: "tuya/device1"
|
||||
refresh: 30 #refresh intervall in s
|
||||
31
extractkeys.md
Normal file
31
extractkeys.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Extract Keys from iO.e app
|
||||
## Install iO.e in an android emulator
|
||||
I've used waydroid (https://waydro.id/#install). I've installed the iO.e app and signed in with my account. The app downloaded all relevant data and stored it in its application data directory on the android device or in this case: the emulator.
|
||||
|
||||
## Extract the relevant file
|
||||
Next step is finding the relevant file.
|
||||
|
||||
```
|
||||
WAYDROID_ROOT_FS="$HOME/.local/share/waydroid/data/data/com.iosmart.app/shared_prefs"
|
||||
sudo bash -c "grep s_home_data $WAYDROID_ROOT_FS/preferences_global_key* > /tmp/dump.raw"
|
||||
sudo chown $USER /tmp/dump.raw
|
||||
ls -l /tmp/dump.raw
|
||||
```
|
||||
|
||||
Now we have the raw data extracted. Next step is converting this to a readable json file.
|
||||
|
||||
First we have to remove the url encoding for quotes
|
||||
```
|
||||
sed -i 's/\"\;/\"/gm' /tmp/dump.raw
|
||||
```
|
||||
Next, remove the string at the beginning and end of the file:
|
||||
```
|
||||
sed -i 's/^.*deviceRespBeen/\{\"data/' /tmp/dump.raw
|
||||
sed -i 's/<\/string>$//' /tmp/dump.raw
|
||||
```
|
||||
Now we can format the file with `jq`
|
||||
```
|
||||
cat /tmp/dump.raw | jq
|
||||
|
||||
```
|
||||
This file contains an array of all configured tuya devices including `id`,`key`and `schema`information for decoding dps values. Use it as you wish.
|
||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "luminea2mqtt",
|
||||
"version": "1.0.0",
|
||||
"description": "Luminea to mqtt bridge",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Dennis Gunia",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"log4js": "^6.9.1",
|
||||
"mqtt": "^5.9.1",
|
||||
"tuyapi": "github:codetheweb/tuyapi",
|
||||
"yaml": "^2.5.0"
|
||||
}
|
||||
}
|
||||
3
src/discovery.js
Normal file
3
src/discovery.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function scanLumineaDevice(){
|
||||
|
||||
}
|
||||
61
src/index.js
Normal file
61
src/index.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const log4js = require('log4js');
|
||||
const logger = log4js.getLogger();
|
||||
const loggerInit = log4js.getLogger("initializer");
|
||||
logger.level = 'info';
|
||||
loggerInit.level = 'info';
|
||||
|
||||
async function main() {
|
||||
const mqtt = require("mqtt");
|
||||
const YAML = require('yaml')
|
||||
const fs = require('fs')
|
||||
const Lineplug = require('./lineaplug')
|
||||
loggerInit.info("Read configfile")
|
||||
const file = fs.readFileSync('./config.yaml', 'utf8')
|
||||
const config = YAML.parse(file)
|
||||
|
||||
|
||||
|
||||
|
||||
const mqttserver = `mqtt://${config.mqtt.host}:${config.mqtt.port}`
|
||||
let client = mqtt.connect(mqttserver, {
|
||||
// Clean session
|
||||
connectTimeout: 1000,
|
||||
// Authentication
|
||||
username: config.mqtt.username,
|
||||
password: config.mqtt.password,
|
||||
clientId: "test",
|
||||
debug: true,
|
||||
});
|
||||
client.on('connect', function () {
|
||||
loggerInit.info(`Connected to ${mqttserver}`)
|
||||
// Subscribe to a topic
|
||||
})
|
||||
client.on("reconnect", () => {
|
||||
loggerInit.info("reconnecting!")
|
||||
})
|
||||
client.stream.on('error', (err) => {
|
||||
loggerInit.error('error', err);
|
||||
client.end()
|
||||
});
|
||||
|
||||
let devices = []
|
||||
|
||||
config.lineaplug.forEach((device) => {
|
||||
loggerInit.info(`Setup device ${device.id} Type: Lineplug`)
|
||||
const newdev = new Lineplug(device,client)
|
||||
devices.push(newdev)
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
process.on('SIGINT',() =>{
|
||||
for (let device of devices){
|
||||
device.disconnect()
|
||||
}
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
main()
|
||||
162
src/lineaplug.js
Normal file
162
src/lineaplug.js
Normal file
@@ -0,0 +1,162 @@
|
||||
const TuyaDevice = require('tuyapi');
|
||||
const log4js = require('log4js');
|
||||
|
||||
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
|
||||
|
||||
this.lastdata = {
|
||||
voltage: 0,
|
||||
current: 0,
|
||||
power: 0,
|
||||
work: 0,
|
||||
status: false,
|
||||
cycle_time: 0,
|
||||
countdown_1: 0,
|
||||
random_time: 0,
|
||||
}
|
||||
try {
|
||||
this.device = new TuyaDevice({
|
||||
//ip: "10.110.0.126",
|
||||
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.logger.info(`Started watcher for id: ${this.deviceid}`)
|
||||
this.device.on('data', data => {
|
||||
this.processData(data)
|
||||
});
|
||||
this.device.on('dp-refresh', data => {
|
||||
this.processData(data)
|
||||
});
|
||||
// monitor queue
|
||||
this.mqtt.on('message', (topic, message) => {
|
||||
// message is Buffer
|
||||
//if (topic == this.topicname){
|
||||
let payload = message.toString()
|
||||
|
||||
try {
|
||||
const jsonpayload = JSON.parse(payload)
|
||||
if (jsonpayload.value != undefined) {
|
||||
this.logger.info(`Change status to ${jsonpayload.value}`)
|
||||
this.device.set({ set: jsonpayload.value }).then(el => {
|
||||
this.device.refresh()
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn(`Error parsing malformatted JSON message via mqtt`)
|
||||
this.logger.trace(payload)
|
||||
this.logger.trace(error)
|
||||
}
|
||||
// }
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
processData(data) {
|
||||
//console.log(data)
|
||||
if (!data.dps) {
|
||||
return
|
||||
}
|
||||
|
||||
const dps = data.dps
|
||||
const updatedValues = Object.keys(dps)
|
||||
let changed = false
|
||||
if (updatedValues.includes('20')) {
|
||||
this.lastdata.voltage = dps['20'] / 10
|
||||
changed = true
|
||||
}
|
||||
if (updatedValues.includes('18')) {
|
||||
this.lastdata.current = dps['18'] / 1000
|
||||
changed = true
|
||||
}
|
||||
if (updatedValues.includes('19')) {
|
||||
this.lastdata.power = dps['19'] / 10
|
||||
changed = true
|
||||
}
|
||||
if (updatedValues.includes('17')) {
|
||||
this.lastdata.power = dps['17'] / 100
|
||||
changed = true
|
||||
}
|
||||
|
||||
if (updatedValues.includes('9')) {
|
||||
this.lastdata.countdown_1 = dps['9']
|
||||
changed = true
|
||||
}
|
||||
if (updatedValues.includes('41')) {
|
||||
this.lastdata.cycle_time = dps['41']
|
||||
changed = true
|
||||
}
|
||||
if (updatedValues.includes('42')) {
|
||||
this.lastdata.random_time = dps['42']
|
||||
changed = true
|
||||
}
|
||||
|
||||
if (updatedValues.includes('1')) {
|
||||
this.lastdata.status = dps['1']
|
||||
changed = true
|
||||
this.mqtt.publish(this.topic_get, JSON.stringify({
|
||||
value: this.lastdata.status
|
||||
}))
|
||||
}
|
||||
if (changed) {
|
||||
this.mqtt.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
|
||||
5
src/logging.js
Normal file
5
src/logging.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const log4js = require('log4js');
|
||||
const logger = log4js.getLogger();
|
||||
logger.level = 'info';
|
||||
|
||||
module.exports.def = logger
|
||||
Reference in New Issue
Block a user