forked from Mirrors/node-red-contrib-mi-devices
230 lines
8.8 KiB
JavaScript
230 lines
8.8 KiB
JavaScript
const dgram = require('dgram'); // Given by udp node
|
|
const miDevicesUtils = require('../src/utils');
|
|
|
|
// UDP node copy/paste...
|
|
|
|
module.exports = (RED) => {
|
|
var udpInputPortsInUse = {};
|
|
|
|
function XiaomiGatewayNode(config) {
|
|
RED.nodes.createNode(this, config);
|
|
this.gateway = RED.nodes.getNode(config.gateway);
|
|
|
|
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
|
|
if (this.gateway) {
|
|
this.on('input', (msg) => {
|
|
var payload = msg.payload;
|
|
|
|
// Input from gateway
|
|
if(payload.sid) {
|
|
if (payload.sid == this.gateway.sid) {
|
|
if(payload.data.rgb) {
|
|
var decomposed = miDevicesUtils.computeColor(payload.data.rgb);
|
|
payload.data.brightness = decomposed.brightness;
|
|
payload.data.color = decomposed.color;
|
|
}
|
|
this.send([msg]);
|
|
}
|
|
}
|
|
// Prepare for request
|
|
else {
|
|
msg.gateway = this.gateway;
|
|
msg.sid = this.gateway.sid;
|
|
this.send(msg);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
RED.nodes.registerType("xiaomi-gateway", XiaomiGatewayNode);
|
|
|
|
// The Input Node
|
|
function GatewayIn(n) {
|
|
RED.nodes.createNode(this,n);
|
|
this.gatewayNodeId = n.gateway;
|
|
this.gateway = RED.nodes.getNode(n.gateway);
|
|
this.group = "224.0.0.50";
|
|
this.port = 9898;
|
|
this.iface = null;
|
|
this.addr = n.ip;
|
|
this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4";
|
|
|
|
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
|
|
var opts = {type:this.ipv, reuseAddr:true};
|
|
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
var server;
|
|
|
|
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
|
|
server = dgram.createSocket(opts); // default to udp4
|
|
udpInputPortsInUse[this.port] = server;
|
|
}
|
|
else {
|
|
this.warn(RED._("udp.errors.alreadyused",this.port));
|
|
server = udpInputPortsInUse[this.port]; // re-use existing
|
|
}
|
|
|
|
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
|
|
server.on("error", (err) => {
|
|
if ((err.code == "EACCES") && (this.port < 1024)) {
|
|
this.error(RED._("udp.errors.access-error"));
|
|
} else {
|
|
this.error(RED._("udp.errors.error",{error:err.code}));
|
|
}
|
|
server.close();
|
|
});
|
|
|
|
server.on('message', (message, remote) => {
|
|
var msg;
|
|
if(remote.address == this.addr) {
|
|
var msg = message.toString('utf8');
|
|
var jsonMsg = JSON.parse(msg);
|
|
if(jsonMsg.data) {
|
|
jsonMsg.data = JSON.parse(jsonMsg.data) || jsonMsg.data;
|
|
if(jsonMsg.data.voltage) {
|
|
jsonMsg.data.batteryLevel = miDevicesUtils.computeBatteryLevel(jsonMsg.data.voltage);
|
|
}
|
|
}
|
|
msg = { payload: jsonMsg };
|
|
if(this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) {
|
|
if(jsonMsg.token) {
|
|
this.gateway.lastToken = jsonMsg.token;
|
|
if(!this.gateway.sid) {
|
|
this.gateway.sid = jsonMsg.sid;
|
|
}
|
|
}
|
|
RED.nodes.eachNode((tmpNode) => {
|
|
if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == this.gatewayNodeId) {
|
|
let tmpNodeInst = RED.nodes.getNode(tmpNode.id);
|
|
if(tmpNode.type === "xiaomi-gateway out" && !this.gateway.lastToken) {
|
|
tmpNodeInst.status({fill:"yellow", shape:"ring", text: "waiting input"});
|
|
}
|
|
tmpNodeInst.status({fill:"blue", shape:"dot", text: "online"});
|
|
}
|
|
});
|
|
}
|
|
this.send(msg);
|
|
}
|
|
});
|
|
|
|
server.on('listening', () => {
|
|
var address = server.address();
|
|
this.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
|
|
server.setBroadcast(true);
|
|
try {
|
|
server.setMulticastTTL(128);
|
|
server.addMembership(this.group,this.iface);
|
|
this.log(RED._("udp.status.mc-group",{group:this.group}));
|
|
} catch (e) {
|
|
if (e.errno == "EINVAL") {
|
|
this.error(RED._("udp.errors.bad-mcaddress"));
|
|
} else if (e.errno == "ENODEV") {
|
|
this.error(RED._("udp.errors.interface"));
|
|
} else {
|
|
this.error(RED._("udp.errors.error",{error:e.errno}));
|
|
}
|
|
}
|
|
});
|
|
|
|
this.on("close", () => {
|
|
if (udpInputPortsInUse.hasOwnProperty(this.port)) {
|
|
delete udpInputPortsInUse[this.port];
|
|
}
|
|
try {
|
|
server.close();
|
|
this.log(RED._("udp.status.listener-stopped"));
|
|
} catch (err) {
|
|
//this.error(err);
|
|
}
|
|
});
|
|
|
|
try { server.bind(this.port, this.iface); }
|
|
catch(e) { } // Don't worry if already bound
|
|
}
|
|
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), (req,res) => {
|
|
res.json(Object.keys(udpInputPortsInUse));
|
|
});
|
|
RED.nodes.registerType("xiaomi-gateway in",GatewayIn);
|
|
|
|
|
|
// The Output Node
|
|
function GatewayOut(n) {
|
|
RED.nodes.createNode(this,n);
|
|
this.port = 9898;
|
|
this.outport = 9898;
|
|
this.iface = null;
|
|
this.addr = n.ip;
|
|
this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4";
|
|
this.multicast = false;
|
|
|
|
this.gatewayNodeId = n.gateway;
|
|
this.gateway = RED.nodes.getNode(n.gateway);
|
|
|
|
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
|
|
var opts = {type:this.ipv, reuseAddr:true};
|
|
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
|
|
var sock;
|
|
if (udpInputPortsInUse[this.outport]) {
|
|
sock = udpInputPortsInUse[this.outport];
|
|
}
|
|
else {
|
|
sock = dgram.createSocket(opts); // default to udp4
|
|
sock.on("error", (err) => {
|
|
// Any async error will also get reported in the sock.send call.
|
|
// This handler is needed to ensure the error marked as handled to
|
|
// prevent it going to the global error handler and shutting node-red
|
|
// down.
|
|
});
|
|
udpInputPortsInUse[this.outport] = sock;
|
|
}
|
|
|
|
if (!udpInputPortsInUse[this.outport]) {
|
|
sock.bind(this.outport);
|
|
this.log(RED._("udp.status.ready",{outport:this.outport,host:this.addr,port:this.port}));
|
|
} else {
|
|
this.log(RED._("udp.status.ready-nolocal",{host:this.addr,port:this.port}));
|
|
}
|
|
|
|
this.on("input", (msg) => {
|
|
if (msg.hasOwnProperty("payload")) {
|
|
var add = this.addr || msg.ip || "";
|
|
var por = this.port || msg.port || 0;
|
|
if (add === "") {
|
|
this.warn(RED._("udp.errors.ip-notset"));
|
|
} else if (por === 0) {
|
|
this.warn(RED._("udp.errors.port-notset"));
|
|
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
|
|
this.warn(RED._("udp.errors.port-invalid"));
|
|
} else {
|
|
if(msg.payload.cmd === "write" && !msg.payload.data.key && this.gateway && this.gateway.sid && this.gateway.key && this.gateway.lastToken) {
|
|
msg.payload.data.key = miDevicesUtils.getGatewayKey(this.gateway.key, this.gateway.lastToken);
|
|
}
|
|
var message = Buffer.from(JSON.stringify(msg.payload));
|
|
sock.send(message, 0, message.length, por, add, (err, bytes) => {
|
|
if (err) {
|
|
this.error("udp : "+err,msg);
|
|
}
|
|
message = null;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
this.on("close", () => {
|
|
if (udpInputPortsInUse.hasOwnProperty(this.outport)) {
|
|
delete udpInputPortsInUse[this.outport];
|
|
}
|
|
try {
|
|
sock.close();
|
|
this.log(RED._("udp.status.output-stopped"));
|
|
} catch (err) {
|
|
//this.error(err);
|
|
}
|
|
});
|
|
}
|
|
RED.nodes.registerType("xiaomi-gateway out", GatewayOut);
|
|
}
|