2
0

feat(devices): handle yeelight basic support

Delete gateway in action config.
Close #4, #8 and #9
This commit is contained in:
Pierre CLEMENT
2018-01-11 00:41:54 +01:00
parent c85e131bc1
commit 809f9a6efb
18 changed files with 286 additions and 1163 deletions

View File

@@ -14,6 +14,8 @@ The following devices are currently supported:
* Motion sensor * Motion sensor
* Power plug (zigbee) * Power plug (zigbee)
* Power plug (wifi) * Power plug (wifi)
* Yeelight White (mono)
* Yeelight RGB
## Preperation ## Preperation
@@ -42,13 +44,13 @@ Tip: use the configurator from the side-panel (hamburger menu, configuration nod
### How to use different nodes ### How to use different nodes
Here an example of how to use the different nodes (screenshot of [importable flows-overview.json](flows-overview.json "Mi Devices overview")): Here an example of how to use the different nodes (screenshot of [importable flows-overview.json](flows-overview.json?raw=true "Mi Devices overview")):
![Mi devices example in node-red](resources/mi-devices-overview.png?raw=true "Mi devices example in node-red") ![Mi devices example in node-red](resources/mi-devices-overview.png?raw=true "Mi devices example in node-red")
### Sample flows ### Sample flows
Here are different flow (screenshot of [importable flows-sample.json](flows-sample.json "Different flows using Mi Devices")): Here are different flow (screenshot of [importable flows-sample.json](flows-sample.json?raw=true "Different flows using Mi Devices")):
![Mi devices example in node-red](resources/mi-devices-sample.png?raw=true "Mi devices flow sample") ![Mi devices example in node-red](resources/mi-devices-sample.png?raw=true "Mi devices flow sample")
## Enable LAN mode ## Enable LAN mode
@@ -59,7 +61,7 @@ Here are different flow (screenshot of [importable flows-sample.json](flows-samp
2. Make sure you set your region to: Mainland China under settings -> Locale - required for the moment. 2. Make sure you set your region to: Mainland China under settings -> Locale - required for the moment.
Mainland China and language can set on English. Mainland China and language can set on English.
3. Select your Gateway in Mi Home 3. Select your Gateway in Mi Home
4. Then the 3 dots at the top right of the screen 4. Then click the 3 dots at the top right of the screen
5. Then click on about 5. Then click on about
6. Tap under Tutorial menu (on the blank part) repeatedly 6. Tap under Tutorial menu (on the blank part) repeatedly
7. You should see now 3 extra options listed in Chinese until you did now enable the developer mode (like the first screenshot below, if not try all steps again!) 7. You should see now 3 extra options listed in Chinese until you did now enable the developer mode (like the first screenshot below, if not try all steps again!)
@@ -72,6 +74,19 @@ If you change here something, you lose your password!
![Gateway advanced mode](resources/xiaomi-gateway-advanced-mode.png?raw=true "Gateway advanced mode") ![Gateway advanced mode](resources/xiaomi-gateway-advanced-mode.png?raw=true "Gateway advanced mode")
![Gateway LAN mode enabled](resources/xiaomi-gateway-lan-enabled.png?raw=true "Gateway LAN mode enabled") ![Gateway LAN mode enabled](resources/xiaomi-gateway-lan-enabled.png?raw=true "Gateway LAN mode enabled")
### Yeelight
1. Install Yeelight App
2. Select your Yeelight in Mi Home
3. Then click the third icon at the bottom of the screen
4. Then click on the lightning icon "LAN control"
5. In the new panel, toggle the switch to "on"
The lightning icon should be underline un yellow.
![Yeelight options](resources/xiaomi-yeelight-options.png?raw=true "Yeelight options")
![Yeelight LAN mode enabled](resources/xiaomi-yeelight-lan-enabled.png?raw=true "Yeelight LAN mode enabled")
## Sources ## Sources
* [Harald Rietman node-red module](https://github.com/hrietman/node-red-contrib-xiaomi-devices) * [Harald Rietman node-red module](https://github.com/hrietman/node-red-contrib-xiaomi-devices)
@@ -79,6 +94,7 @@ If you change here something, you lose your password!
* [louisZl Gateway Local API](https://github.com/louisZL/lumi-gateway-local-api) * [louisZl Gateway Local API](https://github.com/louisZL/lumi-gateway-local-api)
* [Domoticz Gateway Code](https://github.com/domoticz/domoticz/blob/development/hardware/XiaomiGateway.cpp) * [Domoticz Gateway Code](https://github.com/domoticz/domoticz/blob/development/hardware/XiaomiGateway.cpp)
* [Node-red UDP nodes](https://github.com/node-red/node-red/blob/master/nodes/core/io/32-udp.js) * [Node-red UDP nodes](https://github.com/node-red/node-red/blob/master/nodes/core/io/32-udp.js)
* [Yeelight specs](http://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf)
## Credits ## Credits

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -86,10 +86,7 @@
</script> </script>
<script type="text/x-red" data-help-name="xiaomi-actions click"> <script type="text/x-red" data-help-name="xiaomi-actions click">
<p> <p>Virtual single click for switch.</p>
Virtual single click for switch.
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
@@ -136,10 +133,7 @@
</script> </script>
<script type="text/x-red" data-help-name="xiaomi-actions double_click"> <script type="text/x-red" data-help-name="xiaomi-actions double_click">
<p> <p>Virtual double click for switch.</p>
Virtual double click for switch.
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
@@ -183,7 +177,6 @@
category: 'xiaomi actions', category: 'xiaomi actions',
color: '#64C4CD', color: '#64C4CD',
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""}, name: {value: ""},
brightness: {value: 100}, brightness: {value: 100},
hexRgbColor: {value: "#ffffff"}, hexRgbColor: {value: "#ffffff"},
@@ -211,10 +204,6 @@
</script> </script>
<script type="text/x-red" data-template-name="xiaomi-actions gateway_light"> <script type="text/x-red" data-template-name="xiaomi-actions gateway_light">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@@ -230,9 +219,7 @@
</script> </script>
<script type="text/x-red" data-help-name="xiaomi-actions gateway_light"> <script type="text/x-red" data-help-name="xiaomi-actions gateway_light">
<p>Change the light of the gateway. <p>Change the light of the gateway.</p>
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
@@ -271,7 +258,6 @@
category: 'xiaomi actions', category: 'xiaomi actions',
color: '#64C4CD', color: '#64C4CD',
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""}, name: {value: ""},
mid: {value: ""}, mid: {value: ""},
volume: {value: ""} volume: {value: ""}
@@ -287,10 +273,6 @@
</script> </script>
<script type="text/x-red" data-template-name="xiaomi-actions gateway_sound"> <script type="text/x-red" data-template-name="xiaomi-actions gateway_sound">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@@ -330,9 +312,7 @@
</script> </script>
<script type="text/x-red" data-help-name="xiaomi-actions gateway_sound"> <script type="text/x-red" data-help-name="xiaomi-actions gateway_sound">
<p>Play a sound on the gateway. <p>Play a sound on the gateway.</p>
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
@@ -359,7 +339,6 @@
category: 'xiaomi actions', category: 'xiaomi actions',
color: '#64C4CD', color: '#64C4CD',
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value:""} name: {value:""}
}, },
inputs:1, inputs:1,
@@ -372,10 +351,6 @@
}); });
</script> </script>
<script type="text/x-red" data-template-name="xiaomi-actions gateway_stop_sound"> <script type="text/x-red" data-template-name="xiaomi-actions gateway_stop_sound">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@@ -385,7 +360,6 @@
<script type="text/x-red" data-help-name="xiaomi-actions gateway_stop_sound"> <script type="text/x-red" data-help-name="xiaomi-actions gateway_stop_sound">
<p> <p>
Stop current playing sound on the gateway. Stop current playing sound on the gateway.
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p> </p>
<h3>Outputs</h3> <h3>Outputs</h3>
@@ -395,13 +369,12 @@
</script> </script>
<!-- The plug "on" Node --> <!-- The "on" Node -->
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('xiaomi-actions on',{ RED.nodes.registerType('xiaomi-actions on',{
category: 'xiaomi actions', category: 'xiaomi actions',
color: '#64C4CD', color: '#64C4CD',
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value:""} name: {value:""}
}, },
inputs:1, inputs:1,
@@ -414,10 +387,6 @@
}); });
</script> </script>
<script type="text/x-red" data-template-name="xiaomi-actions on"> <script type="text/x-red" data-template-name="xiaomi-actions on">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@@ -427,23 +396,21 @@
<script type="text/x-red" data-help-name="xiaomi-actions on"> <script type="text/x-red" data-help-name="xiaomi-actions on">
<p> <p>
Turn input device to on. Turn input device to on.
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p> </p>
<h3>Outputs</h3> <h3>Outputs</h3>
<ol class="node-ports"> <ol class="node-ports">
<li>Message to connect to a gateway out node.</li> <li>Message to connect to a gateway/yeelight out node.</li>
</ol> </ol>
</script> </script>
<!-- The plug "off" Node --> <!-- The "off" Node -->
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('xiaomi-actions off',{ RED.nodes.registerType('xiaomi-actions off',{
category: 'xiaomi actions', category: 'xiaomi actions',
color: '#64C4CD', color: '#64C4CD',
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value:""} name: {value:""}
}, },
inputs:1, inputs:1,
@@ -456,10 +423,6 @@
}); });
</script> </script>
<script type="text/x-red" data-template-name="xiaomi-actions off"> <script type="text/x-red" data-template-name="xiaomi-actions off">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@@ -469,11 +432,44 @@
<script type="text/x-red" data-help-name="xiaomi-actions off"> <script type="text/x-red" data-help-name="xiaomi-actions off">
<p> <p>
Turn input device to off. Turn input device to off.
Note: a gateway input node must be present and have receive a message to get gateway tokens and be able to do the action.
</p> </p>
<h3>Outputs</h3> <h3>Outputs</h3>
<ol class="node-ports"> <ol class="node-ports">
<li>Message to connect to a gateway out node.</li> <li>Message to connect to a gateway/yeelight out node.</li>
</ol>
</script>
<!-- The "toggle" Node -->
<script type="text/javascript">
RED.nodes.registerType('xiaomi-actions toggle',{
category: 'xiaomi actions',
color: '#64C4CD',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
paletteLabel: "toggle",
icon: "mi-toggle.png",
label: function() {
return this.name||"toggle power";
}
});
</script>
<script type="text/x-red" data-template-name="xiaomi-actions toggle">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-actions toggle">
<p>Toggle device.</p>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Message to connect to a gateway/yeelight out node.</li>
</ol> </ol>
</script> </script>

View File

@@ -36,8 +36,11 @@ module.exports = (RED) => {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.on('input', (msg) => { this.on('input', (msg) => {
this.gateway = msg.gateway; msg.payload = {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {status: "click", sid: msg.sid}); cmd: "write",
data: { status: "click", sid: msg.sid }
};
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions click", XiaomiActionSingleClick); RED.nodes.registerType("xiaomi-actions click", XiaomiActionSingleClick);
@@ -49,7 +52,11 @@ module.exports = (RED) => {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.on('input', (msg) => { this.on('input', (msg) => {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {status: "double_click", sid: msg.sid}); msg.payload = {
cmd: "write",
data: { status: "double_click", sid: msg.sid }
};
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions double_click", XiaomiActionDoubleClick); RED.nodes.registerType("xiaomi-actions double_click", XiaomiActionDoubleClick);
@@ -59,15 +66,26 @@ module.exports = (RED) => {
*********************************************/ *********************************************/
function XiaomiActionGatewayLight(config) { function XiaomiActionGatewayLight(config) {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway); this.color = config.color;
this.color = RED.nodes.getNode(config.color); this.brightness = config.brightness;
this.brightness = RED.nodes.getNode(config.brightness);
this.on('input', (msg) => { this.on('input', (msg) => {
let color = msg.color || this.color; let color = msg.color || this.color;
let brightness = msg.brightness || this.brightness; let brightness = msg.brightness || this.brightness;
let rgb = miDevicesUtils.computeColorValue(brightness, color.red, color.green, color.blue); if(msg.sid) {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {rgb: rgb}); let rgb = miDevicesUtils.computeColorValue(color.red, color.green, color.blue, brightness);
msg.payload = {
cmd: "write",
data: { rgb: rgb, sid: msg.sid }
};
}
else {
msg.payload = {
color: miDevicesUtils.computeColorValue(color.red, color.green, color.blue),
brightness: brightness
};
}
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions gateway_light", XiaomiActionGatewayLight); RED.nodes.registerType("xiaomi-actions gateway_light", XiaomiActionGatewayLight);
@@ -77,15 +95,19 @@ module.exports = (RED) => {
*********************************************/ *********************************************/
function XiaomiActionGatewaySound(config) { function XiaomiActionGatewaySound(config) {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.mid = config.mid; this.mid = config.mid;
this.volume = config.volume; this.volume = config.volume;
this.on('input', (msg) => { this.on('input', (msg) => {
miDevicesUtils.sendWritePayloadToGateway(this, msg, { msg.payload = {
mid: parseInt(msg.mid || this.mid), cmd: "write",
volume: parseInt(msg.volume || this.volume) data: {
}); mid: parseInt(msg.mid || this.mid),
volume: parseInt(msg.volume || this.volume),
sid: msg.sid
}
};
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions gateway_sound", XiaomiActionGatewaySound); RED.nodes.registerType("xiaomi-actions gateway_sound", XiaomiActionGatewaySound);
@@ -95,10 +117,13 @@ module.exports = (RED) => {
*********************************************/ *********************************************/
function XiaomiActionGatewayStopSound(config) { function XiaomiActionGatewayStopSound(config) {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.on('input', (msg) => { this.on('input', (msg) => {
miDevicesUtils.sendWritePayloadToGateway(this, msg, { mid: 1000 }); msg.payload = {
cmd: "write",
data: { mid: 1000, sid: msg.sid }
};
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions gateway_stop_sound", XiaomiActionGatewayStopSound); RED.nodes.registerType("xiaomi-actions gateway_stop_sound", XiaomiActionGatewayStopSound);
@@ -108,16 +133,18 @@ module.exports = (RED) => {
*********************************************/ *********************************************/
function XiaomiActionPowerOn(config) { function XiaomiActionPowerOn(config) {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.on('input', (msg) => { this.on('input', (msg) => {
if(msg.sid){ if(msg.sid){
miDevicesUtils.sendWritePayloadToGateway(this, msg, { status: "on", sid: msg.sid}); msg.payload = {
cmd: "write",
data: { status: "on", sid: msg.sid }
};
} }
else { else {
msg.payload = "off"; msg.payload = "on";
this.send(msg);
} }
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions on", XiaomiActionPowerOn); RED.nodes.registerType("xiaomi-actions on", XiaomiActionPowerOn);
@@ -127,17 +154,32 @@ module.exports = (RED) => {
*********************************************/ *********************************************/
function XiaomiActionPowerOff(config) { function XiaomiActionPowerOff(config) {
RED.nodes.createNode(this, config); RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.on('input', (msg) => { this.on('input', (msg) => {
if(msg.sid){ if(msg.sid){
miDevicesUtils.sendWritePayloadToGateway(this, msg, { status: "off", sid: msg.sid}); msg.payload = {
cmd: "write",
data: { status: "off", sid: msg.sid }
};
} }
else { else {
msg.payload = "off"; msg.payload = "off";
this.send(msg);
} }
this.send(msg);
}); });
} }
RED.nodes.registerType("xiaomi-actions off", XiaomiActionPowerOff); RED.nodes.registerType("xiaomi-actions off", XiaomiActionPowerOff);
/*********************************************
Toggle device
*********************************************/
function XiaomiActionToggle(config) {
RED.nodes.createNode(this, config);
this.on('input', (msg) => {
msg.payload = "toggle";
this.send(msg);
});
}
RED.nodes.registerType("xiaomi-actions toggle", XiaomiActionToggle);
} }

View File

@@ -12,11 +12,9 @@ module.exports = (RED) => {
this.gateway = RED.nodes.getNode(config.gateway); this.gateway = RED.nodes.getNode(config.gateway);
this.onlyModels = getOnlyModelsValue(config.onlyModels || []); this.onlyModels = getOnlyModelsValue(config.onlyModels || []);
this.excludedSids = config.excludedSids; this.excludedSids = config.excludedSids;
console.log(this.onlyModels);
this.isDeviceValid = (device) => { this.isDeviceValid = (device) => {
console.log(device.sid, device.model, this.onlyModels, this.excludeSids);
if((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) { if((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) {
return true; return true;
} }

View File

@@ -88,18 +88,21 @@ module.exports = (RED) => {
} }
msg = { payload: jsonMsg }; msg = { payload: jsonMsg };
if(this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) { if(this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) {
RED.nodes.eachNode((tmpNode) => {
if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == this.gatewayNodeId) {
let tmpNodeInst = RED.nodes.getNode(tmpNode.id);
tmpNodeInst.status({fill:"blue", shape:"dot", text: "online"});
}
});
if(jsonMsg.token) { if(jsonMsg.token) {
this.gateway.lastToken = jsonMsg.token; this.gateway.lastToken = jsonMsg.token;
if(!this.gateway.sid) { if(!this.gateway.sid) {
this.gateway.sid = jsonMsg.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); this.send(msg);
} }
@@ -155,6 +158,9 @@ module.exports = (RED) => {
this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4"; this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4";
this.multicast = false; this.multicast = false;
this.gatewayNodeId = n.gateway;
this.gateway = RED.nodes.getNode(n.gateway);
this.status({fill:"red", shape:"ring", text: "offline"}); this.status({fill:"red", shape:"ring", text: "offline"});
var opts = {type:this.ipv, reuseAddr:true}; var opts = {type:this.ipv, reuseAddr:true};
@@ -193,6 +199,9 @@ module.exports = (RED) => {
} else if (isNaN(por) || (por < 1) || (por > 65535)) { } else if (isNaN(por) || (por < 1) || (por > 65535)) {
this.warn(RED._("udp.errors.port-invalid")); this.warn(RED._("udp.errors.port-invalid"));
} else { } 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)); var message = Buffer.from(JSON.stringify(msg.payload));
sock.send(message, 0, message.length, por, add, (err, bytes) => { sock.send(message, 0, message.length, por, add, (err, bytes) => {
if (err) { if (err) {

View File

@@ -5,10 +5,7 @@
defaults: { defaults: {
gateway: {value:"", type:"xiaomi-configurator"}, gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""}, name: {value: ""},
sid: {value: "", required: true}, sid: {value: "", required: true}
outmsg: {value: "{{click}}"},
outmsgdbcl: {value: "{{double_click}}"},
output: {value: "0"}
}, },
inputs: 1, inputs: 1,
outputs: 1, outputs: 1,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,79 @@
<script type="text/javascript">
RED.nodes.registerType('xiaomi-yeelight out', {
category: 'xiaomi in out',
color: '#087F8A',
defaults: {
name: {value: ""},
ip: {value: ""},
port: {value:55443, required: true}
},
inputs: 1,
outputs: 0,
paletteLabel: "yeelight out",
icon: "mi-yeelight.png",
align: "right",
label: function () {
return this.name || "yeelight out";
}
});
</script>
<script type="text/x-red" data-template-name="xiaomi-yeelight out">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-ip"><i class="icon-tag"></i> IP</label>
<input type="text" id="node-input-ip" placeholder="IP">
</div>
<div class="form-row">
<label for="node-input-port"><i class="icon-tag"></i> Port</label>
<input type="text" id="node-input-port" placeholder="Port">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-yeelight out">
<p>The Xiaomi Yeelight node</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">object</span>
</dt>
<dd>
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
<hr>
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>.<br>
<hr>
Input Gateway node produces message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>.
</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway when used as a filter (see below).</dd>
<dt>sid <span class="property-type">string</span></dt>
<dd>Device SID.</dd>
<dt>gateway <span class="property-type">object</span></dt>
<dd>The <code>xiaomi-configurator</code> object where the device is registred.</dd>
</dl>
</ol>
<h4>Details</h4>
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
<p>Sample message:</p>
<p><pre>{
cmd: "report"
model: "switch"
sid: "158d000128b124"
short_id: 56773
data: {
status: "click",
batteryLevel: 23
}
}</pre></p>
</script>

View File

@@ -0,0 +1,57 @@
const miDevicesUtils = require('../src/utils');
const Yeelight = require("yeelight2");
module.exports = (RED) => {
function XiaomiYeelightOutputNode(config) {
RED.nodes.createNode(this, config);
this.ip = config.ip;
this.port = config.port;
this.status({fill:"grey", shape:"ring", text:"na"});
this.setupConnection = function(){
try {
this.light = Yeelight(`yeelight://${this.ip}:${this.port}`);
this.status({fill:"blue", shape:"dot", text:"connected"});
} catch(err) {
this.status({fill:"red",shape:"ring",text:err.message});
this.light = null;
this.error(err);
// try to reconnect in 5 minutes
window.setTimeout((function(self) {
return function() {
self.setupConnection.apply(self, arguments);
}
})(this), 1000*60*5);
}
}
if (this.ip && this.port) {
this.setupConnection();
this.on('input', (msg) => {
if(msg.payload === "on") {
this.light && this.light.set_power('on');
}
else if(msg.payload === "off") {
this.light && this.light.set_power('off');
}
else if(msg.payload === "toggle") {
this.light && this.light.toggle();
}
if(msg.payload.color !== undefined) {
this.light && this.light.set_rgb(msg.payload.color);
}
if(msg.payload.brightness !== undefined) {
this.light && this.light.set_bright(msg.payload.brightness);
}
});
this.on('close', () => {
this.light && this.light.exit();
});
}
}
RED.nodes.registerType("xiaomi-yeelight out", XiaomiYeelightOutputNode);
};

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red-contrib-mi-devices", "name": "node-red-contrib-mi-devices",
"version": "1.0.6", "version": "1.1.0",
"description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway, and the Gateway itself.", "description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway, and the Gateway itself.",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -23,7 +23,8 @@
"xiaomi-all": "node-red-contrib-xiaomi-all/xiaomi-all.js", "xiaomi-all": "node-red-contrib-xiaomi-all/xiaomi-all.js",
"xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js", "xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js",
"xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js", "xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js",
"xiaomi-actions": "node-red-contrib-xiaomi-actions/xiaomi-actions.js" "xiaomi-actions": "node-red-contrib-xiaomi-actions/xiaomi-actions.js",
"xiaomi-yeelight": "node-red-contrib-xiaomi-yeelight/xiaomi-yeelight.js"
} }
}, },
"author": "Pierre CLEMENT", "author": "Pierre CLEMENT",
@@ -32,7 +33,8 @@
}, },
"dependencies": { "dependencies": {
"cryptojs": "^2.5.3", "cryptojs": "^2.5.3",
"miio": "0.13.0" "miio": "0.13.0",
"yeelight2": "^1.3.5"
}, },
"engines": { "engines": {
"node": ">=4.4.5" "node": ">=4.4.5"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -39,24 +39,12 @@ module.exports = {
return key; return key;
}, },
sendWritePayloadToGateway: function(node, msg, data) {
let gateway = node.gateway;
if(gateway && gateway.sid && gateway.key && gateway.lastToken) {
data.sid = data.sid || gateway.sid;
data.key = this.getGatewayKey(gateway.key, gateway.lastToken);
msg.payload = {
cmd: "write",
data: data
};
node.send(msg);
}
},
prepareForGatewayRequest: function(node, msg) { prepareForGatewayRequest: function(node, msg) {
msg.sid = node.sid; msg.sid = node.sid;
msg.gateway = node.gateway; msg.gateway = node.gateway;
}, },
computeColorValue: function (brightness, red, green, blue) { computeColorValue: function (red, green, blue, brightness) {
return Math.round(256*256*256*brightness) + (256*256*red) + (256*green) + blue; return (brightness !== undefined ? 256*256*256*brightness : 0) + (256*256*red) + (256*green) + blue;
}, },
computeColor: function (rgb) { computeColor: function (rgb) {
var blue = rgb % 256; var blue = rgb % 256;