Skip to content

Commit

Permalink
Add gateway as a device
Browse files Browse the repository at this point in the history
  • Loading branch information
lasthead0 committed Feb 10, 2022
1 parent e8f2be2 commit 90a6019
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 66 deletions.
16 changes: 14 additions & 2 deletions admin/tab_m.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,19 @@ async function getDevices(ids/* Array */) {
await new Promise(resolve => {
sendTo(namespace, 'GetDevices', {ids}, function (msg) {
if (isArray(msg)) {
const _devices = msg.reduce((acc, d) => Object.assign(acc, {[d.id]: d}), {});
const _devices = msg
.sort((a, b) => {
if (a.type == 'gateway') {
return -1;
} else if (b.type == 'gateway') {
return 1;
} else {
const aInt = a.mac.match(/[\da-f]{2}/g).reduce((acc, c) => acc + parseInt(c, 16), 0);
const bInt = b.mac.match(/[\da-f]{2}/g).reduce((acc, c) => acc + parseInt(c, 16), 0);
return bInt - aInt;
}
})
.reduce((acc, d) => Object.assign(acc, {[d.id]: d}), {});

devices = Object.assign(devices, _devices);
}
Expand All @@ -169,7 +181,7 @@ function showDevices() {
const html = Object.values(devices).map(device => {
const {id, mac, type, did, model, name, fwVer, friendlyName, stateVal, stateCommon} = device;

if (type == 'gateway') return '';
// if (type == 'gateway') return '';

let img_src = '';

Expand Down
85 changes: 52 additions & 33 deletions lib/gateway3.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {

/* */
const {isObject, isArray} = require('./tools');
const {reverseMac, sleep} = require('./utils');
const {sleep, reverseMac, decodeMiioJson} = require('./utils');
const {SQLite} = require('./utilsdb');
const {Gateway3Helper} = require('./helpers');
const Lumi = require('./lumi');
Expand Down Expand Up @@ -350,13 +350,13 @@ class Gateway3 extends XiaomiDevice {
let raw = await this.#shell.readFile('/data/zigbee/coordinator.info');

let device = JSON.parse(raw);
const {name, spec} = Lumi.getDevice('lumi.gateway.mgl03');
const {name, spec, model} = Lumi.getDevice('lumi.gateway.mgl03');

devices.push({
'did': await this.#shell.getDid(),
'mac': device['mac'],
'type': 'gateway',
'model': 'lumi.gateway.mgl03',
model,
name,
'fwVer': this.#fw_version,
'wlan_mac': await this.#shell.getWlanMac(),
Expand Down Expand Up @@ -489,17 +489,20 @@ class Gateway3 extends XiaomiDevice {
stat: new XiaomiDeviceStat(resetCnt)
});

/* */
if (type == 'gateway') {
this.mac = mac;
this.did = did;
this.topic = `gw/${String(mac).substr(2).toUpperCase()}/`;
} else {
findOrCreateDevice({
mac,
model,
spec: filteredSpec.map(el => el[2]).filter(el => el != undefined),
init: init || {}
});
}

/* Call callback */
findOrCreateDevice({
mac,
model,
spec: filteredSpec.map(el => el[2]).filter(el => el != undefined),
init: init || {}
});
}
}

Expand Down Expand Up @@ -636,6 +639,45 @@ class Gateway3 extends XiaomiDevice {
cb(mac, payload);
}

processMessageLogMiio(message, cb) {
const decodeMessage = decodeMiioJson(message, 'event.gw.heartbeat');

if (decodeMessage.length > 0) {
const messageParams = decodeMessage[0]['params'][0];

const {spec: deviceSpec} = this.devices[this.did];
const statesKeyVal = deviceSpec
.reduce((pv, [, state]) => Object.assign({}, pv, state.decode(messageParams)), {});
const payload = deviceSpec
.map(([, state]) => [state, statesKeyVal[state.stateName]]);

cb(this.mac, payload);
}
}

/* */
processMessageReceived(message, cb) {
const {eui64} = message;
const did = `lumi.${eui64.replace(/\b0x0+/g, '').toLowerCase()}`;
const device = this.devices[did];

const payload = [];

if (device != undefined && device.stat != undefined) {
device.stat.update(message);

const [, statState] = (device.spec.find(([, state]) => state.stateName == 'messages_stat') || []);

if (statState != undefined) {
const stat = Object.assign({}, device.stat.object, {did});

payload.push([statState, statState.normalizeLeft(stat)]);
}

cb(device.mac, payload);
}
}

/* */
sendMessage(id, /* object */states, cb) {
const device = Object.values(this.devices).find(el => el.mac == `0x${id}`);
Expand Down Expand Up @@ -669,29 +711,6 @@ class Gateway3 extends XiaomiDevice {
}
}

/* */
processMessageReceived(message, cb) {
const {eui64} = message;
const did = `lumi.${eui64.replace(/\b0x0+/g, '').toLowerCase()}`;
const device = this.devices[did];

const payload = [];

if (device != undefined && device.stat != undefined) {
device.stat.update(message);

const [, statState] = (device.spec.find(([, state]) => state.stateName == 'messages_stat') || []);

if (statState != undefined) {
const stat = Object.assign({}, device.stat.object, {did});

payload.push([statState, statState.normalizeLeft(stat)]);
}

cb(device.mac, payload);
}
}

/* Check is gateway3 port open or not */
async _checkPort(port) {
return Gateway3Helper.checkPort(port, this.#localip);
Expand Down
33 changes: 32 additions & 1 deletion lib/lumi.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
ColorTemperature,
Conductivity,
Contact,
LoadAvg,
CurtainLevel,
CurtainMotor,
Formaldehyde,
Expand All @@ -31,7 +32,9 @@ const {
Power,
Pressure,
Remaining,
Rssi,
RunState,
RunTime,
Switch,
Temperature,
TiltAngle,
Expand Down Expand Up @@ -88,6 +91,9 @@ module.exports = class Zigbee {
{
'lumi.gateway.mgl03': ['Xiaomi', 'Gateway 3', 'ZNDMWG03LM'],
'spec': [
[undefined, undefined, LoadAvg, [], [LoadAvgParser]],
[undefined, undefined, Rssi, [], [RssiParser]],
[undefined, undefined, RunTime, [], [RunTimeParser]],
// ['8.0.2012', undefined, 'power_tx'],
// ['8.0.2024', undefined, 'channel'],
// ['8.0.2081', undefined, 'pairing_stop'],
Expand Down Expand Up @@ -745,7 +751,13 @@ module.exports = class Zigbee {
normalizeRight(val) {
return super.normalizeRight(val);
}


/**
* Decode payload
*
* @param {*} keyVal Array of arrays ([[lumi, val]]) or Object ({})
* @returns {stateName: stateValue, ...}
*/
decode(/* [[lumi, val]] */keyVal) {
let payload;

Expand Down Expand Up @@ -933,6 +945,17 @@ function HumidityParser(model) {
return val => val;
}

function LoadAvgParser() {
return val => {
const [a, b, c] = val['load_avg'].split('|');
return [
Number(a).toFixed(2),
Number(b).toFixed(2),
Number(c).toFixed(2)
];
};
}

function PowerParser(model) {
return val => {
if (typeof val === 'number')
Expand All @@ -949,6 +972,10 @@ function PressureParser(model) {
return val => val;
}

function RssiParser() {
return val => val['rssi'] * -1;
}

function RunStateParser(model) {
return val => {
// # https://github.com/AlexxIT/XiaomiGateway3/issues/139
Expand All @@ -961,6 +988,10 @@ function RunStateParser(model) {
};
}

function RunTimeParser() {
return val => Math.floor(Number(val['run_time']) / 60);
}

function SwitchParser(model) {
return val => ([1, 0][['on', 'off'].indexOf(val)] || val);
}
Expand Down
50 changes: 50 additions & 0 deletions lib/stateClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,28 @@ module.exports = {
this._update(...args);
}
},
LoadAvg: class extends StateClass {
stateName = 'load_avg';
stateCommon = {
name: 'Load average',
role: 'state',
type: 'string',
write: false
};

/* */
constructor(...args) {
super();
this._update(...args);
}

/* */
setter(dev, cb, context, timers) {
const [, val] = context[this.stateName] || [];

cb(JSON.stringify(val).replace(/[\[\]\"]/g, '').replace(/,/g, ', '));
}
},
LoadPower: class extends StateClass {
stateName = 'load_power';
stateCommon = {
Expand Down Expand Up @@ -730,6 +752,20 @@ module.exports = {
this._update(...args);
}
},
Rssi: class extends StateClass {
stateName = 'rssi';
stateCommon = {
name: 'RSSI',
role: 'value',
unit: 'dBm'
};

/* */
constructor(...args) {
super();
this._update(...args);
}
},
RunState: class extends StateClass {
stateName = 'run_state';
stateCommon = {
Expand All @@ -746,6 +782,20 @@ module.exports = {
this._update(...args);
}
},
RunTime: class extends StateClass {
stateName = 'run_time';
stateCommon = {
name: 'Run time',
role: 'value',
unit: 'minutes'
};

/* */
constructor(...args) {
super();
this._update(...args);
}
},
Switch: class extends StateClass {
stateName = 'switch';
stateCommon = {
Expand Down
34 changes: 29 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
const crypto = require('crypto');

function reverseMac(mac) {
return mac.match(/[\da-f]{2}/g).reverse().join('');
}

async function sleep(ms) {
return new Promise(resolve => {
const id = crypto.randomBytes(8).toString('hex');
Expand All @@ -15,7 +11,35 @@ async function sleep(ms) {
});
}

function reverseMac(mac) {
return mac.match(/[\da-f]{2}/g).reverse().join('');
}

function decodeMiioJson(raw, search) {
const RE_JSON1 = /msg:(.+) length:([0-9]+) bytes/g;
const RE_JSON2 = /\{.+\}/g;

if (raw.includes(search) == false)
return [];

let m = RE_JSON1.exec(raw);

if (m != undefined) {
raw = String(m[1]).substring(0, Number(m[2]));
} else {
m = RE_JSON2.exec(raw);
raw = m[0];
}

const items = raw.replace('}{', '}\n{').split('\n');
return items
.filter(el => el.includes(search))
.map(el => JSON.parse(el));
}

/* */
module.exports = {
sleep,
reverseMac,
sleep
decodeMiioJson
};
Loading

0 comments on commit 90a6019

Please sign in to comment.