feat(gateway): addset light and play sounds
This commit is contained in:
@@ -1,204 +1,3 @@
|
||||
|
||||
|
||||
|
||||
<!-- The Gateway light Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mi-devices-actions gateway_light', {
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
name: {value: ""},
|
||||
brightness: {value: 100},
|
||||
hexRgbColor: {value: "#ffffff"},
|
||||
color: {value:{red: 255, green: 255, blue: 255}}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
paletteLabel: "set light",
|
||||
icon: "mi-bulb.png",
|
||||
label: function () {
|
||||
return this.name || "set light";
|
||||
},
|
||||
oneditsave: function() {
|
||||
var hexRgbColor = $("#node-input-hexRgbColor").val();
|
||||
var split = hexRgbColor.slice(1).match(/.{1,2}/g).map(function(hexColor) {
|
||||
return parseInt(hexColor, 16);
|
||||
});
|
||||
this.color = {
|
||||
red: split[0],
|
||||
green: split[1],
|
||||
blue: split[2]
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="mi-devices-actions gateway_light">
|
||||
<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-brightness"><i class="icon-tag"></i> Brightness</label>
|
||||
<input type="range" id="node-input-brightness" min="0" max="100">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-hexRgbColor"><i class="icon-tag"></i> Color</label>
|
||||
<input type="color" id="node-input-hexRgbColor">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mi-devices-actions gateway_light">
|
||||
<p>Change the light of the gateway.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>brightness
|
||||
<span class="property-type">number</span>
|
||||
</dt>
|
||||
<dd>The brightness value between <code>0</code> and <code>100</code>.</dd>
|
||||
<dt>color
|
||||
<span class="property-type">object</span>
|
||||
</dt>
|
||||
<dd>The color itself. This object must contain the followinf properties:
|
||||
<ul>
|
||||
<li>
|
||||
<code>red</code> - amout of red, between <code>0</code> and <code>255</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>green</code> - amout of green, between <code>0</code> and <code>255</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>blue</code> - amout of blue, between <code>0</code> and <code>255</code>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Message to connect to a gateway out node.</li>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- The Gateway sound Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mi-devices-actions gateway_sound', {
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
name: {value: ""},
|
||||
mid: {value: ""},
|
||||
volume: {value: ""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
paletteLabel: "play sound",
|
||||
icon: "mi-sound.png",
|
||||
label: function () {
|
||||
return this.name || "play sound";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="mi-devices-actions gateway_sound">
|
||||
<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-mid"><i class="icon-tag"></i> Sound</label>
|
||||
<select id="node-input-mid">
|
||||
<option value="0">Police car tone 1</option>
|
||||
<option value="1">Police car tone 2</option>
|
||||
<option value="2">Safety incident sound</option>
|
||||
<option value="3">Missile countdown</option>
|
||||
<option value="4">Ghost cry</option>
|
||||
<option value="5">Sniper rifle</option>
|
||||
<option value="6">Battle sound</option>
|
||||
<option value="7">Air raid alarm</option>
|
||||
<option value="8">Barking</option>
|
||||
<option value="10">Doorbell tone</option>
|
||||
<option value="11">Door knocking tone</option>
|
||||
<option value="12">Funny tone</option>
|
||||
<option value="13">Alarm clock tone</option>
|
||||
<option value="20">MiMix</option>
|
||||
<option value="21">Enthusisatic</option>
|
||||
<option value="22">GuitarClassic</option>
|
||||
<option value="23">IceWorldPiano</option>
|
||||
<option value="24">LeisureTime</option>
|
||||
<option value="25">ChildHood</option>
|
||||
<option value="26">MorningStreamLet</option>
|
||||
<option value="27">MusicBox</option>
|
||||
<option value="28">Orange</option>
|
||||
<option value="29">Thinker</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-volume"><i class="icon-tag"></i> Volume</label>
|
||||
<input type="range" id="node-input-volume" min="0" max="100">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mi-devices-actions gateway_sound">
|
||||
<p>Play a sound on the gateway.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>mid
|
||||
<span class="property-type">number</span>
|
||||
</dt>
|
||||
<dd>Music ID (user define sounds start from <code>1001</code>, <code>1000</code> means stop).</dd>
|
||||
<dt>volume
|
||||
<span class="property-type">number</span>
|
||||
</dt>
|
||||
<dd>Playing volume, between <code>0</code> and <code>100</code>.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Message to connect to a gateway out node.</li>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- The Gateway stop sound Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mi-devices-actions gateway_stop_sound',{
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
paletteLabel: "stop sound",
|
||||
icon: "mi-mute.png",
|
||||
label: function() {
|
||||
return this.name||"stop sound";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script type="text/x-red" data-template-name="mi-devices-actions gateway_stop_sound">
|
||||
<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="mi-devices-actions gateway_stop_sound">
|
||||
<p>
|
||||
Stop current playing sound on the gateway.
|
||||
</p>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Message to connect to a gateway out node.</li>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- The "on" Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('mi-devices-actions on',{
|
||||
|
||||
@@ -1,133 +1,5 @@
|
||||
const miDevicesUtils = require('../src/utils');
|
||||
|
||||
module.exports = (RED) => {
|
||||
/*********************************************
|
||||
Read data from Gateway
|
||||
*********************************************/
|
||||
function XiaomiActionRead(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
|
||||
this.on('input', (msg) => {
|
||||
if(msg.sid) {
|
||||
msg.payload = { cmd: "read", sid: msg.sid };
|
||||
this.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions read", XiaomiActionRead);
|
||||
|
||||
/*********************************************
|
||||
Get registred ids of devices on gateway
|
||||
*********************************************/
|
||||
function XiaomiActionGetIdList(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
|
||||
this.on('input', (msg) => {
|
||||
msg.payload = { cmd: "get_id_list" };
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions get_id_list", XiaomiActionGetIdList);
|
||||
|
||||
/*********************************************
|
||||
Virtual single click on a button
|
||||
*********************************************/
|
||||
function XiaomiActionSingleClick(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
|
||||
this.on('input', (msg) => {
|
||||
msg.payload = {
|
||||
cmd: "write",
|
||||
data: { status: "click", sid: msg.sid }
|
||||
};
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions click", XiaomiActionSingleClick);
|
||||
|
||||
/*********************************************
|
||||
Virtual Double click on a button
|
||||
*********************************************/
|
||||
function XiaomiActionDoubleClick(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
|
||||
this.on('input', (msg) => {
|
||||
msg.payload = {
|
||||
cmd: "write",
|
||||
data: { status: "double_click", sid: msg.sid }
|
||||
};
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions double_click", XiaomiActionDoubleClick);
|
||||
|
||||
/*********************************************
|
||||
Set the gateway light
|
||||
*********************************************/
|
||||
function XiaomiActionGatewayLight(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.color = config.color;
|
||||
this.brightness = config.brightness;
|
||||
|
||||
this.on('input', (msg) => {
|
||||
let color = msg.color || this.color;
|
||||
let brightness = msg.brightness || this.brightness;
|
||||
if(msg.sid) {
|
||||
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("mi-devices-actions gateway_light", XiaomiActionGatewayLight);
|
||||
|
||||
/*********************************************
|
||||
Play a sound on the gateway
|
||||
*********************************************/
|
||||
function XiaomiActionGatewaySound(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.mid = config.mid;
|
||||
this.volume = config.volume;
|
||||
|
||||
this.on('input', (msg) => {
|
||||
msg.payload = {
|
||||
cmd: "write",
|
||||
data: {
|
||||
mid: parseInt(msg.mid || this.mid),
|
||||
volume: parseInt(msg.volume || this.volume),
|
||||
sid: msg.sid
|
||||
}
|
||||
};
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions gateway_sound", XiaomiActionGatewaySound);
|
||||
|
||||
/*********************************************
|
||||
Stop playing a sound on the gateway
|
||||
*********************************************/
|
||||
function XiaomiActionGatewayStopSound(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
|
||||
this.on('input', (msg) => {
|
||||
msg.payload = {
|
||||
cmd: "write",
|
||||
data: { mid: 1000, sid: msg.sid }
|
||||
};
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("mi-devices-actions gateway_stop_sound", XiaomiActionGatewayStopSound);
|
||||
|
||||
/*********************************************
|
||||
Turn device on
|
||||
*********************************************/
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('xiaomi-all', {
|
||||
category: 'xiaomi',
|
||||
color: '#3FADB5',
|
||||
defaults: {
|
||||
gateway: {value:"", type:"xiaomi-configurator"},
|
||||
name: {value: ""},
|
||||
onlyModels: {value: []},
|
||||
excludedSids: { value: []}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
outputLabels: ["All devices"],
|
||||
paletteLabel: "all",
|
||||
icon: "mi-all.png",
|
||||
label: function () {
|
||||
return this.name || "xiaomi-all";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
|
||||
function getOnlyModelsValue(input) {
|
||||
var cleanOnlyModels = [];
|
||||
input.forEach((value) => {
|
||||
cleanOnlyModels = cleanOnlyModels.concat(value.split(','));
|
||||
});
|
||||
return cleanOnlyModels;
|
||||
}
|
||||
|
||||
function changeGateway(gateway, onlyModels, excludedSids) {
|
||||
var configNodeID = gateway || $('#node-input-gateway').val();
|
||||
if (configNodeID) {
|
||||
var configNode = RED.nodes.node(configNodeID);
|
||||
if(configNode) {
|
||||
onlyModels = getOnlyModelsValue(onlyModels || $('#node-input-onlyModels').val() || []);
|
||||
excludedSids = excludedSids || $('#node-input-excludedSids').val() || [];
|
||||
$('#node-input-excludedSids').empty();
|
||||
for (key in configNode.deviceList) {
|
||||
var device = configNode.deviceList[key];
|
||||
if (onlyModels.length == 0 || onlyModels.indexOf(device.model) >= 0) {
|
||||
var option = $('<option value="' + device.sid + '">' + device.desc + '</option>');
|
||||
if(excludedSids && excludedSids.indexOf(device.sid) >= 0) {
|
||||
option.prop('selected', true);
|
||||
}
|
||||
$('#node-input-excludedSids').append(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeGateway(this.gateway, this.onlyModels, this.excludedSids);
|
||||
$("#node-input-gateway, #node-input-onlyModels").change(function () {
|
||||
changeGateway();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
if(!$('#node-input-onlyModels').val()) {
|
||||
this.onlyModels = [];
|
||||
}
|
||||
if(!$('#node-input-excludedSids').val()) {
|
||||
this.excludedSids = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="xiaomi-all">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<hr />
|
||||
<h5>Filters</h5>
|
||||
<div class="form-row">
|
||||
<label for="node-input-onlyModels"><i class="icon-tag"></i> Only</label>
|
||||
<select multiple id="node-input-onlyModels">
|
||||
<option value="sensor_ht,weather.v1">Temperature/humidty</option>
|
||||
<option value="motion">Motion</option>
|
||||
<option value="switch,sensor_switch.aq2">Switches</option>
|
||||
<option value="magnet,sensor_magnet.aq2">Contacts</option>
|
||||
<option value="plug">Plugs</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-excludedSids"><i class="icon-tag"></i> Exclude</label>
|
||||
<select multiple id="node-input-excludedSids" size=10></select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="xiaomi-all">
|
||||
<p>All devices registred in the gateway, except gateway itself.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>payload
|
||||
<span class="property-type">object</span>
|
||||
</dt>
|
||||
<dd>When use as an incoming filter node, <code>sid</code> and <code>model</code> are mandatory.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Devices output
|
||||
<dl class="message-properties">
|
||||
<dt>payload <span class="property-type">array</span></dt>
|
||||
<dd>Array of devices.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h4>Details</h4>
|
||||
<p>Sample payload:</p>
|
||||
<p><pre>[
|
||||
{sid: "128d0901db1fa8", desc: "Door sensor" model: "magnet"},
|
||||
{sid: "151d0401ab2491", desc: "Heat sensor", model: "sensor_ht"},
|
||||
{sid: "658d030171427c", desc: "Button", model: "switch"}
|
||||
]</pre>
|
||||
</p>
|
||||
</script>
|
||||
@@ -1,50 +0,0 @@
|
||||
module.exports = (RED) => {
|
||||
function getOnlyModelsValue(input) {
|
||||
var cleanOnlyModels = [];
|
||||
input.forEach((value) => {
|
||||
cleanOnlyModels = cleanOnlyModels.concat(value.split(','));
|
||||
});
|
||||
return cleanOnlyModels;
|
||||
}
|
||||
|
||||
function XiaomiAllNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
this.gateway = RED.nodes.getNode(config.gateway);
|
||||
this.onlyModels = getOnlyModelsValue(config.onlyModels || []);
|
||||
this.excludedSids = config.excludedSids;
|
||||
|
||||
|
||||
this.isDeviceValid = (device) => {
|
||||
if((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) {
|
||||
return true;
|
||||
}
|
||||
// Is excluded
|
||||
if((this.excludedSids && this.excludedSids.length != 0) && this.excludedSids.indexOf(device.sid) >= 0) {
|
||||
return false;
|
||||
}
|
||||
if((this.onlyModels && this.onlyModels.length != 0) && this.onlyModels.indexOf(device.model) >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.gateway) {
|
||||
this.on('input', (msg) => {
|
||||
// Filter input
|
||||
if(msg.payload && msg.payload.model && msg.payload.sid) {
|
||||
if(!this.isDeviceValid(msg.payload)) {
|
||||
msg = null;
|
||||
}
|
||||
}
|
||||
// Prepare for request
|
||||
else {
|
||||
msg.payload = this.gateway.deviceList.filter((device) => this.isDeviceValid(device));
|
||||
}
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("xiaomi-all", XiaomiAllNode);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class Gateway extends events.EventEmitter {
|
||||
|
||||
if (msg.isGetIdListAck()) {
|
||||
(<MessageData.GatewayMessageGetIdListData> msg.data).forEach((sid) => {
|
||||
this.send({cmd: "read", sid: sid});
|
||||
this.read(sid);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +69,17 @@ export class Gateway extends events.EventEmitter {
|
||||
return !!this._subdevices[sid];
|
||||
}
|
||||
|
||||
setLight(brightness, rgb) {
|
||||
getIdList() {
|
||||
this.send({
|
||||
cmd: "get_id_list",
|
||||
})
|
||||
}
|
||||
|
||||
read(sid?: string) {
|
||||
this.send({cmd: "read", sid: sid || this.sid});
|
||||
}
|
||||
|
||||
setLight(brightness: number, rgb: { red: number, green: number, blue: number }) {
|
||||
this.send({
|
||||
cmd: "write",
|
||||
data: {
|
||||
@@ -79,8 +89,15 @@ export class Gateway extends events.EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
playSound() {
|
||||
|
||||
playSound(musicId: number, volume: number) {
|
||||
this.send({
|
||||
cmd: "write",
|
||||
data: {
|
||||
mid: musicId,
|
||||
volume: volume,
|
||||
sid: this.sid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
send(message: any) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export class GatewayServer extends events.EventEmitter {
|
||||
if ((msg.isHeartbeat() || msg.isIam()) && msg.model === "gateway") {
|
||||
if (!this._gateways[msg.sid]) {
|
||||
this._gateways[msg.sid] = new Gateway(msg.sid, remote.address);
|
||||
this.sendToGateway(msg.sid, {cmd: "get_id_list"});
|
||||
this._gateways[msg.sid].getIdList();
|
||||
this.emit("gateway-online", msg.sid);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Red, NodeProperties } from "node-red";
|
||||
import { Constants } from "../constants";
|
||||
|
||||
export default (RED:Red) => {
|
||||
class GatewayLight {
|
||||
public color:string;
|
||||
public brightness:number;
|
||||
|
||||
constructor(props:NodeProperties) {
|
||||
RED.nodes.createNode(<any> this, props);
|
||||
(<any> this).setListeners();
|
||||
}
|
||||
|
||||
protected setListeners() {
|
||||
(<any> this).on('input', (msg) => {
|
||||
let color = msg.color || this.color;
|
||||
let brightness = msg.brightness || this.brightness;
|
||||
if(msg.sid) {
|
||||
msg.payload = {
|
||||
cmd: "write",
|
||||
data: { rgb: 123, sid: msg.sid }
|
||||
};
|
||||
}
|
||||
else {
|
||||
msg.payload = {
|
||||
brightness: brightness
|
||||
};
|
||||
}
|
||||
(<any> this).send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType(`${Constants.NODES_PREFIX}-actions gateway_light`, <any> GatewayLight);
|
||||
};
|
||||
81
src/nodes/actions/GatewayPlaySound.ejs
Normal file
81
src/nodes/actions/GatewayPlaySound.ejs
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- The Gateway sound Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('<%= NODES_PREFIX %>-actions gateway_play_sound', {
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
name: {value: ""},
|
||||
mid: {value: ""},
|
||||
volume: {value: ""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
paletteLabel: "play sound",
|
||||
icon: "mi-sound.png",
|
||||
label: function () {
|
||||
return this.name || "play sound";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="<%= NODES_PREFIX %>-actions gateway_play_sound">
|
||||
<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-mid"><i class="icon-tag"></i> Sound</label>
|
||||
<select id="node-input-mid">
|
||||
<option value="0">Police car tone 1</option>
|
||||
<option value="1">Police car tone 2</option>
|
||||
<option value="2">Safety incident sound</option>
|
||||
<option value="3">Missile countdown</option>
|
||||
<option value="4">Ghost cry</option>
|
||||
<option value="5">Sniper rifle</option>
|
||||
<option value="6">Battle sound</option>
|
||||
<option value="7">Air raid alarm</option>
|
||||
<option value="8">Barking</option>
|
||||
<option value="10">Doorbell tone</option>
|
||||
<option value="11">Door knocking tone</option>
|
||||
<option value="12">Funny tone</option>
|
||||
<option value="13">Alarm clock tone</option>
|
||||
<option value="20">MiMix</option>
|
||||
<option value="21">Enthusisatic</option>
|
||||
<option value="22">GuitarClassic</option>
|
||||
<option value="23">IceWorldPiano</option>
|
||||
<option value="24">LeisureTime</option>
|
||||
<option value="25">ChildHood</option>
|
||||
<option value="26">MorningStreamLet</option>
|
||||
<option value="27">MusicBox</option>
|
||||
<option value="28">Orange</option>
|
||||
<option value="29">Thinker</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-volume"><i class="icon-tag"></i> Volume</label>
|
||||
<input type="range" id="node-input-volume" min="0" max="100">
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="<%= NODES_PREFIX %>-actions gateway_play_sound">
|
||||
<p>Play a sound on the gateway.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>mid
|
||||
<span class="property-type">number</span>
|
||||
</dt>
|
||||
<dd>Music ID (user define sounds start from <code>1001</code>, <code>1000</code> means stop).</dd>
|
||||
<dt>volume
|
||||
<span class="property-type">number</span>
|
||||
</dt>
|
||||
<dd>Playing volume, between <code>0</code> and <code>100</code>.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Message to connect to a gateway out node.</li>
|
||||
</ol>
|
||||
|
||||
</script>
|
||||
31
src/nodes/actions/GatewayPlaySound.ts
Normal file
31
src/nodes/actions/GatewayPlaySound.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {Red, NodeProperties} from "node-red";
|
||||
import {Constants} from "../constants";
|
||||
|
||||
export default (RED: Red) => {
|
||||
class GatewayPlaySound {
|
||||
public mid: number;
|
||||
public volume: number;
|
||||
|
||||
constructor(props: NodeProperties) {
|
||||
RED.nodes.createNode(<any> this, props);
|
||||
this.mid = parseInt((<any>props).mid);
|
||||
this.volume = parseInt((<any>props).volume);
|
||||
(<any> this).setListeners();
|
||||
}
|
||||
|
||||
protected setListeners() {
|
||||
(<any> this).on('input', (msg) => {
|
||||
if (msg.sid) {
|
||||
msg.payload = {
|
||||
action: "playSound",
|
||||
mid: msg.mid || this.mid,
|
||||
volume: msg.volume || this.volume
|
||||
};
|
||||
}
|
||||
(<any> this).send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType(`${Constants.NODES_PREFIX}-actions gateway_play_sound`, <any> GatewayPlaySound);
|
||||
};
|
||||
36
src/nodes/actions/GatewayStopSound.ejs
Normal file
36
src/nodes/actions/GatewayStopSound.ejs
Normal file
@@ -0,0 +1,36 @@
|
||||
<!-- The Gateway stop sound Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('<%= NODES_PREFIX %>-actions gateway_stop_sound', {
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
name: {value: ""}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
paletteLabel: "stop sound",
|
||||
icon: "mi-mute.png",
|
||||
label: function () {
|
||||
return this.name || "stop sound";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script type="text/x-red" data-template-name="<%= NODES_PREFIX %>-actions gateway_stop_sound">
|
||||
<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="<%= NODES_PREFIX %>-actions gateway_stop_sound">
|
||||
<p>
|
||||
Stop current playing sound on the gateway.
|
||||
</p>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Message to connect to a gateway out node.</li>
|
||||
</ol>
|
||||
|
||||
</script>
|
||||
26
src/nodes/actions/GatewayStopSound.ts
Normal file
26
src/nodes/actions/GatewayStopSound.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Red, NodeProperties} from "node-red";
|
||||
import {Constants} from "../constants";
|
||||
|
||||
export default (RED: Red) => {
|
||||
class GatewayStopSound {
|
||||
|
||||
constructor(props: NodeProperties) {
|
||||
RED.nodes.createNode(<any> this, props);
|
||||
(<any> this).setListeners();
|
||||
}
|
||||
|
||||
protected setListeners() {
|
||||
(<any> this).on('input', (msg) => {
|
||||
if (msg.sid) {
|
||||
msg.payload = {
|
||||
action: "playSound",
|
||||
mid: 1000
|
||||
};
|
||||
}
|
||||
(<any> this).send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType(`${Constants.NODES_PREFIX}-actions gateway_stop_sound`, <any> GatewayStopSound);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- The Gateway light Node -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('<%= NODES_PREFIX %>-actions gateway_light', {
|
||||
RED.nodes.registerType('<%= NODES_PREFIX %>-actions light', {
|
||||
category: 'xiaomi actions',
|
||||
color: '#64C4CD',
|
||||
defaults: {
|
||||
@@ -30,7 +30,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="<%= NODES_PREFIX %>-actions gateway_light">
|
||||
<script type="text/x-red" data-template-name="<%= NODES_PREFIX %>-actions light">
|
||||
<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">
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="<%= NODES_PREFIX %>-actions gateway_light">
|
||||
<script type="text/x-red" data-help-name="<%= NODES_PREFIX %>-actions light">
|
||||
<p>Change the light of the gateway.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
31
src/nodes/actions/Light.ts
Normal file
31
src/nodes/actions/Light.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {Red, NodeProperties} from "node-red";
|
||||
import {Constants} from "../constants";
|
||||
|
||||
export default (RED: Red) => {
|
||||
class Light {
|
||||
public color: string;
|
||||
public brightness: number;
|
||||
|
||||
constructor(props: NodeProperties) {
|
||||
RED.nodes.createNode(<any> this, props);
|
||||
this.color = (<any>props).color;
|
||||
this.brightness = (<any>props).brightness;
|
||||
(<any> this).setListeners();
|
||||
}
|
||||
|
||||
protected setListeners() {
|
||||
(<any> this).on('input', (msg) => {
|
||||
if (msg.sid) {
|
||||
msg.payload = {
|
||||
action: "setLight",
|
||||
color: msg.color || this.color,
|
||||
brightness: msg.brightness || this.brightness
|
||||
};
|
||||
}
|
||||
(<any> this).send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType(`${Constants.NODES_PREFIX}-actions light`, <any> Light);
|
||||
};
|
||||
@@ -30,4 +30,6 @@
|
||||
docTitle: "Virtual double click for switch."
|
||||
}) %>
|
||||
|
||||
<%- include('./GatewayLight', {}) %>
|
||||
<%- include('./Light', {}) %>
|
||||
<%- include('./GatewayPlaySound', {}) %>
|
||||
<%- include('./GatewayStopSound', {}) %>
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Red, NodeProperties } from "node-red";
|
||||
import * as LumiAqara from 'lumi-aqara';
|
||||
import {Red} from "node-red";
|
||||
|
||||
import {default as ReadAction} from './ReadAction';
|
||||
import {default as WriteAction} from './WriteAction';
|
||||
import {default as GatewayLight} from './GatewayLight';
|
||||
import {default as Light} from './Light';
|
||||
import {default as GatewayPlaySound} from './GatewayPlaySound';
|
||||
import {default as GatewayStopSound} from './GatewayStopSound';
|
||||
|
||||
export = (RED:Red) => {
|
||||
GatewayLight(RED);
|
||||
export = (RED: Red) => {
|
||||
["read", "get_id_list"].forEach((action) => {
|
||||
ReadAction(RED, action);
|
||||
});
|
||||
|
||||
|
||||
["click", "double_click"].forEach((action) => {
|
||||
WriteAction(RED, action);
|
||||
});
|
||||
Light(RED);
|
||||
GatewayPlaySound(RED);
|
||||
GatewayStopSound(RED);
|
||||
};
|
||||
@@ -4,32 +4,43 @@ import { Constants } from "../constants";
|
||||
|
||||
export interface IGatewayNode extends Node {
|
||||
gatewayConf:any;
|
||||
gateway: LumiAqara.Gateway;
|
||||
|
||||
setGateway(gateway:LumiAqara.Gateway);
|
||||
}
|
||||
|
||||
export default (RED:Red) => {
|
||||
class Gateway {
|
||||
protected gatewayConf: any;
|
||||
protected gateway: LumiAqara.Gateway;
|
||||
|
||||
constructor(props:NodeProperties){
|
||||
RED.nodes.createNode(<any> this, props);
|
||||
this.gatewayConf = RED.nodes.getNode((<any> props).gateway);
|
||||
this.gateway = null;
|
||||
|
||||
(<any> this).status({fill:"red", shape:"ring", text: "offline"});
|
||||
(<any>this).status({fill: "red", shape: "ring", text: "offline"});
|
||||
|
||||
if (this.gatewayConf.gateway) {
|
||||
this.gatewayOnline();
|
||||
}
|
||||
|
||||
this.gatewayConf.on('gateway-online', () => this.gatewayOnline());
|
||||
this.gatewayConf.on('gateway-offline', () => this.gatewayOffline());
|
||||
|
||||
this.setMessageListener();
|
||||
}
|
||||
|
||||
protected gatewayOnline() {
|
||||
(<any>this).status({fill: "blue", shape: "dot", text: "online"});
|
||||
}
|
||||
|
||||
protected gatewayOffline() {
|
||||
(<any>this).status({fill: "red", shape: "ring", text: "offline"});
|
||||
}
|
||||
|
||||
protected setMessageListener() {
|
||||
(<any> this).on('input', (msg) => {
|
||||
if (this.gateway) {
|
||||
if (this.gatewayConf.gateway) {
|
||||
var payload = msg.payload;
|
||||
|
||||
// Input from gateway
|
||||
if(payload.sid && payload.sid == this.gateway.sid) {
|
||||
if(payload.sid && payload.sid == this.gatewayConf.gateway.sid) {
|
||||
if(payload.data.rgb) {
|
||||
/*var decomposed = miDevicesUtils.computeColor(payload.data.rgb);
|
||||
payload.data.brightness = decomposed.brightness;
|
||||
@@ -39,23 +50,13 @@ export default (RED:Red) => {
|
||||
}
|
||||
// Prepare for request
|
||||
else {
|
||||
msg.sid = this.gateway.sid;
|
||||
msg.sid = this.gatewayConf.gateway.sid;
|
||||
msg.gateway = this.gatewayConf.gateway;
|
||||
(<any> this).send(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setGateway(gateway:LumiAqara.Gateway) {
|
||||
this.gateway = gateway;
|
||||
this.gateway.setPassword(this.gatewayConf.password);
|
||||
(<any> this).status({fill:"blue", shape:"dot", text: "online"});
|
||||
|
||||
this.gateway.on('offline', () => {
|
||||
this.gateway = null;
|
||||
(<any> this).status({fill:"red", shape:"ring", text: "offline"});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
RED.nodes.registerType(`${Constants.NODES_PREFIX}-gateway`, <any> Gateway);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Red, NodeProperties} from "node-red";
|
||||
import {Constants} from "../constants";
|
||||
import {GatewayServer} from "../../devices/GatewayServer";
|
||||
|
||||
export default (RED: Red) => {
|
||||
class GatewayOut {
|
||||
@@ -11,7 +12,22 @@ export default (RED: Red) => {
|
||||
protected setMessageListener() {
|
||||
(<any> this).on("input", (msg) => {
|
||||
if (msg.hasOwnProperty("payload") && msg.hasOwnProperty("gateway")) {
|
||||
msg.gateway.gateway.send(msg);
|
||||
let gateway = GatewayServer.getInstance().getGateway(msg.gateway.sid);
|
||||
if(gateway) {
|
||||
if (msg.payload.cmd) {
|
||||
gateway.send(msg);
|
||||
}
|
||||
else if (msg.payload.action) {
|
||||
switch (msg.payload.action) {
|
||||
case 'setLight':
|
||||
gateway.setLight(msg.payload.brightness, msg.payload.color);
|
||||
break;
|
||||
case 'playSound':
|
||||
gateway.playSound(msg.payload.mid, msg.payload.volume);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
94
src/utils.js
94
src/utils.js
@@ -1,94 +0,0 @@
|
||||
var crypto = require("crypto");
|
||||
var iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]);
|
||||
|
||||
module.exports = {
|
||||
computeBatteryLevel: function(voltage) {
|
||||
/*
|
||||
When full, CR2032 batteries are between 3 and 3.4V
|
||||
http://farnell.com/datasheets/1496885.pdf
|
||||
*/
|
||||
return Math.min(Math.round((voltage - 2200) / 10), 100);
|
||||
},
|
||||
setStatus: function(node, data) {
|
||||
if (data.voltage) {
|
||||
var batteryPercent = Math.min(Math.round((data.voltage - 2200) / 14), 100);
|
||||
var status = {
|
||||
fill: "green", shape: "dot",
|
||||
text: "battery - " + batteryPercent + "%"
|
||||
};
|
||||
|
||||
if (data.voltage < 2500) {
|
||||
status.color = "red";
|
||||
} else if (data.voltage < 2900) {
|
||||
status.color = "yellow";
|
||||
}
|
||||
node.status(status);
|
||||
}
|
||||
},
|
||||
prepareFullDataOutput: function(payload) {
|
||||
if(payload.data.voltage) {
|
||||
payload.data.batteryLevel = this.computeBatteryLevel(payload.data.voltage);
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
getGatewayKey: function(password, token) {
|
||||
var cipher = crypto.createCipheriv('aes-128-cbc', password, iv);
|
||||
var gatewayToken = token;
|
||||
var key = cipher.update(gatewayToken, "ascii", "hex");
|
||||
cipher.final('hex');
|
||||
|
||||
return key;
|
||||
},
|
||||
prepareForGatewayRequest: function(node, msg) {
|
||||
msg.sid = node.sid;
|
||||
msg.gateway = node.gateway;
|
||||
},
|
||||
computeColorValue: function (red, green, blue, brightness) {
|
||||
return (brightness !== undefined ? 256*256*256*brightness : 0) + (256*256*red) + (256*green) + blue;
|
||||
},
|
||||
computeColor: function (rgb) {
|
||||
var blue = rgb % 256;
|
||||
rgb = Math.max(rgb - blue, 0);
|
||||
|
||||
var green = rgb % (256 * 256);
|
||||
rgb = Math.max(rgb - green, 0);
|
||||
green /= 256;
|
||||
|
||||
var red = rgb % (256 * 256 * 256);
|
||||
rgb = Math.max(rgb - red, 0);
|
||||
red /= 256 * 256;
|
||||
|
||||
var brightness = rgb / (256*256*256);
|
||||
|
||||
return {
|
||||
brightness: brightness,
|
||||
color: { red: red, green: green, blue: blue }
|
||||
};
|
||||
},
|
||||
defaultNode: function(RED, config, node) {
|
||||
RED.nodes.createNode(node, config);
|
||||
node.gateway = RED.nodes.getNode(config.gateway);
|
||||
node.sid = config.sid;
|
||||
|
||||
node.status({fill:"grey", shape:"ring", text:"battery - na"});
|
||||
|
||||
if (node.gateway) {
|
||||
node.on('input', (msg) => {
|
||||
let payload = msg.payload;
|
||||
|
||||
// Input from gateway
|
||||
if (payload.sid) {
|
||||
if (payload.sid == node.sid) {
|
||||
this.setStatus(node, payload.data);
|
||||
node.send([msg]);
|
||||
}
|
||||
}
|
||||
// Prepare for request
|
||||
else {
|
||||
this.prepareForGatewayRequest(node, msg);
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user