20 Commits

Author SHA1 Message Date
338419dca7 test 2026-02-28 21:58:16 +01:00
5183879654 upd 2026-02-28 21:40:58 +01:00
524e691645 typescript 2026-02-28 21:31:07 +01:00
0fccbca7b2 upd 2026-02-28 21:23:04 +01:00
c24007e42c upd 2026-02-28 21:17:22 +01:00
7698040f1c cleanup 2026-02-21 21:08:06 +01:00
e404c9a7ab mattes-seins 2026-02-21 20:41:14 +01:00
Pierre CLÉMENT
4c8978decf Update README.md 2018-04-17 21:52:46 +02:00
Pierre CLEMENT
627946c3d8 chore(distrib): version bump 2018-04-17 12:56:12 +02:00
Pierre CLÉMENT
7f033a442b Merge pull request #38 from readeral/xiaomi-actions-get-id-patch
Xiaomi actions get id patch
2018-04-17 12:54:09 +02:00
Pierre CLÉMENT
b984f415d5 Merge pull request #37 from readeral/update_docs
Update README.md
2018-04-17 12:53:52 +02:00
Pierre CLÉMENT
aeb6fcd047 Merge pull request #34 from artemsuv/fix_issue33
fix(socket) miDevicesUtils is not defined
2018-04-17 12:53:11 +02:00
Pierre CLÉMENT
4e3c3055d7 Merge pull request #35 from artemsuv/fix_data_not_defined
fix(socket) fix ReferenceError: data is not define
2018-04-17 12:52:54 +02:00
readeral
ec82e3882c Update xiaomi-actions.js 2018-02-13 13:32:10 +11:00
readeral
7880cc4f74 Update README.md 2018-02-12 23:33:34 +11:00
Artem Suvorov
a07eef88ae fix(socket) fix ReferenceError: data is not define 2018-02-04 00:11:15 +02:00
Artem Suvorov
af67f19217 fix(socket) miDevicesUtils is not defined 2018-02-03 23:16:25 +02:00
Pierre CLEMENT
e37c4aa71c Merge branch 'development' 2018-01-20 13:24:59 +01:00
Pierre CLEMENT
600f8d859c Merge branch 'master' of github.com:pierrecle/node-red-contrib-mi-devices 2018-01-11 00:44:08 +01:00
Pierre CLEMENT
6a9863814b feat(devices): handle yeelight basic support
Delete gateway in action config.
Close #4, close #8 and close #9
2018-01-11 00:43:29 +01:00
20 changed files with 438 additions and 52 deletions

36
.drone.yml Normal file
View File

@@ -0,0 +1,36 @@
---
kind: secret
name: git_username
get:
path: secret/data/gitea
name: api_access_user
---
kind: secret
name: git_password
get:
path: secret/data/gitea
name: api_access_token
---
kind: pipeline
type: docker
name: default
steps:
- name: build-and-publish
image: node:16
environment:
GITEA_TOKEN:
from_secret: git_password
commands:
- npm install
- npm install typescript@latest
- npm run build
# Erstellt die Authentifizierung für die Registry (URL aus package.json)
- echo "//git.familie-berner.de/api/packages/Public/npm/:_authToken=$GITEA_TOKEN" > .npmrc
- npm publish
when:
branch:
- master

29
.gitignore vendored
View File

@@ -1,4 +1,29 @@
*.iml
# Dependencies
/node_modules
.log
# Build output
dist/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS
.DS_Store
Thumbs.db
# Editor / IDE
*.iml
.idea/
.vscode/
# Package manager files
package-lock.json
yarn.lock
*.tgz
# Secrets
.npmrc

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
lts/*

18
.woodpecker/pipeline.yml Normal file
View File

@@ -0,0 +1,18 @@
steps:
build-and-publish:
image: node:16
environment:
# Woodpecker sucht nach einem Secret namens "GITEA_TOKEN"
GITEA_TOKEN:
from_secret: GITEA_TOKEN
commands:
- npm install
- npm install typescript@latest
- npm run build
# Nutzt die Variable aus dem Environment
- echo "//git.familie-berner.de/api/packages/Public/npm/:_authToken=$GITEA_TOKEN" > .npmrc
- npm publish
when:
branch:
- master
event: push

View File

@@ -53,6 +53,18 @@ Here an example of how to use the different nodes (screenshot of [importable flo
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")
### Interpreting payload.msg
The following is an (incomplete) summary of interpreting payload.msg output from the mi-devices nodes
#### payload.msg.cmd
When utilising a gateway, device and debug node, you will see the following property of msg.payload.cmd along with a number of possible values:
* cmd: 'read' - this is the result generated from a flow that utilises the 'read' node. (see 'outgoing' example above. The result of the 'xiaomi-ht' device connected to the 'read' node, will be output from the xiaomi-gateway incoming node, with a property of 'cmd' and value 'read'). Using a read node is the best way to obtain up to date values.
* cmd: 'report' - is a result of a direct change in status of a device, for example the opening or closing of a magnet sensor, or a change in temperature.
* cmd: 'heartbeat' - If the device is the gateway, then a heartbeat cmd will be sent every 10 seconds. If it is a sub-device, then plug-in devices (such as sockets and aircon helpers) will send a heartbeat every 10 minutes, other devices that sleep (e.g. zigbee devices), will send a heartbeat message every 60 minutes. If a device's current status is lost, a heartbeat message may be used to remedy the issue.
* cmd 'write' - used to change the state of a Smart Socket
## Enable LAN mode
### Gateway

View File

@@ -24,7 +24,7 @@ module.exports = (RED) => {
this.on('input', (msg) => {
msg.payload = { cmd: "get_id_list" };
node.send(msg);
this.send(msg);
});
}
RED.nodes.registerType("xiaomi-actions get_id_list", XiaomiActionGetIdList);

View File

@@ -79,7 +79,9 @@
<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="sensor_ht,weather.v1">Temperature/Humidty</option>
<option value="natgas">Natgas/Alarm/Density</option>
<option value="smoke">Smoke/Alarm/Density</option>
<option value="motion">Motion</option>
<option value="switch,sensor_switch.aq2">Switches</option>
<option value="magnet,sensor_magnet.aq2">Contacts</option>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -15,6 +15,8 @@
var node = this;
var tw_sensor_ht = {value:"sensor_ht", label:"sensor ht", icon:"icons/node-red-contrib-mi-devices/thermometer-tw-icon.png"};
var tw_natgas = {value:"natgas", label:"natgas", icon:"icons/node-red-contrib-mi-devices/natgas-tw-icon.png"};
var tw_smoke = {value:"smoke", label:"smoke", icon:"icons/node-red-contrib-mi-devices/smoke-tw-icon.png"};
var tw_magnet = {value:"magnet", label:"contact", icon:"icons/node-red-contrib-mi-devices/door-tw-icon.png"};
var tw_motion = {value:"motion", label:"motion", icon:"icons/node-red-contrib-mi-devices/motion-tw-icon.png"};
var tw_plug = {value:"plug", label:"plug", icon:"icons/node-red-contrib-mi-devices/outlet-tw-icon.png"};
@@ -31,7 +33,7 @@
var sid = $('<input/>',{id:"node-config-input-sid-"+i,type:"text", placeholder:"SID", style:"width:auto;vertical-align:top"}).appendTo(row);
sid.typedInput({
default: 'sensor_ht',
types: [tw_sensor_ht, tw_magnet, tw_motion, tw_plug, tw_switch]
types: [tw_sensor_ht, tw_natgas, tw_smoke, tw_magnet, tw_motion, tw_plug, tw_switch]
});
$('<label/>',{for:"node-config-input-desc-"+i, style:"margin-left: 7px; width: 20px;vertical-align:middle"}).html('<i class="fa fa-pencil-square-o"></i>').appendTo(row);
@@ -114,6 +116,8 @@
<p>At the moment the following devices are supported:
<lu>
<li>Humidity & Temperature sensor [sensor ht/]</li>
<li>Natgas sensor [natgas]</li>
<li>Smoke sensor [smoke]</li>
<li>Body motion sensor [motion]</li>
<li>Magnet contact sensor [contact]</li>
<li>Wall socket plug (zigbee) [plug]</li>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,131 @@
<script type="text/javascript">
RED.nodes.registerType('xiaomi-natgas', {
category: 'xiaomi',
color: '#3FADB5',
defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""},
sid: {value: "", required: true},
natgasmsg: {value: ""},
nonatgasmsg: {value: ""},
output: {value: "0"}
},
inputs: 1,
outputs: 1,
paletteLabel: "natgas",
icon: "natgas-icon.png",
label: function () {
return this.name || "xiaomi-natgas";
},
oneditprepare: function() {
var node = this;
if(node.sid) {
$('#node-input-sid').val(node.sid);
}
function changeGateway(model) {
var configNodeID = $('#node-input-gateway').val();
if (configNodeID) {
var configNode = RED.nodes.node(configNodeID);
if(configNode) {
$('#node-input-sid').empty();
for (key in configNode.deviceList) {
var device = configNode.deviceList[key];
if (device.model === model) {
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
}
}
if(node.sid) {
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
}
}
}
}
$("#node-input-sid").change(function () {
if(!this.name) {
$("#node-input-name").val($('#node-input-sid option:selected').text());
}
});
$("#node-input-gateway").change(function () {
changeGateway("natgas");
});
},
oneditsave: function() {
var node = this;
node.sid = $("#node-input-sid").val();
}
});
</script>
<script type="text/x-red" data-template-name="xiaomi-natgas">
<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>
<div class="form-row">
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-natgas">
<p>The Xiaomi natgas sensor 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 messages:</p>
<p><pre>{
cmd: "report"
model: "natgas"
sid: "158d00015ef56c"
short_id: 21672
data: {
density: "0"
}
}</pre>
<p><pre>{
cmd: "heartbeat"
model: "natgas"
sid: "158d00015ef56c"
short_id: 21672
data: {
voltage: 3035,
alarm: "0",
batteryLevel: 45
}
}</pre>
Where <code>batteryLevel</code> is a computed percentage of remaining battery.
</p>
</script>

View File

@@ -0,0 +1,9 @@
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
// natgas
function XiaomiNatgasNode(config) {
miDevicesUtils.defaultNode(RED, config, this);
}
RED.nodes.registerType("xiaomi-natgas", XiaomiNatgasNode);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,131 @@
<script type="text/javascript">
RED.nodes.registerType('xiaomi-smoke', {
category: 'xiaomi',
color: '#3FADB5',
defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""},
sid: {value: "", required: true},
smokemsg: {value: ""},
nosmokemsg: {value: ""},
output: {value: "0"}
},
inputs: 1,
outputs: 1,
paletteLabel: "smoke",
icon: "smoke-icon.png",
label: function () {
return this.name || "xiaomi-smoke";
},
oneditprepare: function() {
var node = this;
if(node.sid) {
$('#node-input-sid').val(node.sid);
}
function changeGateway(model) {
var configNodeID = $('#node-input-gateway').val();
if (configNodeID) {
var configNode = RED.nodes.node(configNodeID);
if(configNode) {
$('#node-input-sid').empty();
for (key in configNode.deviceList) {
var device = configNode.deviceList[key];
if (device.model === model) {
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
}
}
if(node.sid) {
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
}
}
}
}
$("#node-input-sid").change(function () {
if(!this.name) {
$("#node-input-name").val($('#node-input-sid option:selected').text());
}
});
$("#node-input-gateway").change(function () {
changeGateway("smoke");
});
},
oneditsave: function() {
var node = this;
node.sid = $("#node-input-sid").val();
}
});
</script>
<script type="text/x-red" data-template-name="xiaomi-smoke">
<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>
<div class="form-row">
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-smoke">
<p>The Xiaomi smoke sensor 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: "smoke"
sid: "158d00015ef56c"
short_id: 21672
data: {
density: "0"
}
}</pre>
<p><pre>{
cmd: "heartbeat"
model: "smoke"
sid: "158d00015ef56c"
short_id: 21672
data: {
voltage: 3035,
alarm: "0",
batteryLevel: 45
}
}</pre>
Where <code>batteryLevel</code> is a computed percentage of remaining battery.
</p>
</script>

View File

@@ -0,0 +1,9 @@
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
// smoke
function XiaomiSmokeNode(config) {
miDevicesUtils.defaultNode(RED, config, this);
}
RED.nodes.registerType("xiaomi-smoke", XiaomiSmokeNode);
};

View File

@@ -1,4 +1,5 @@
const crypto = require("crypto");
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
function XiaomiPlugNode(config) {
@@ -13,9 +14,9 @@ module.exports = (RED) => {
var payload = msg.payload;
if(payload.sid) {
if (payload.sid == this.sid) {
if (data.status && data.status == "on") {
if (payload.data && payload.data.status && payload.data.status == "on") {
this.status({fill:"green", shape:"dot", text:"on"});
} else if (data.status && data.status == "off") {
} else if (payload.data && payload.data.status && payload.data.status == "off") {
this.status({fill:"red", shape:"dot", text:"off"});
}
this.send(msg);

View File

@@ -1,47 +1,7 @@
{
"name": "node-red-contrib-mi-devices",
"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.",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com:pierrecle/node-red-contrib-mi-devices.git"
"author": {
"name": "Pierre CLEMENT"
},
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && npm run build:ts && npm run build:ejs && npm run build:icons",
"build:ts": "tsc --allowUnreachableCode -p .",
"build:ejs": "npm run build:ejs:indexes",
"build:ejs:indexes": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"**/index.ejs\" --out dist/",
"build:ejs:devices": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"nodes/devices/*.ejs\" --out dist/",
"build:icons": "npm run build:icons:yeelight",
"build:icons:gateway": "cp -pr icons/gateway dist/nodes/gateway/icons",
"build:icons:devices": "cp -pr icons/devices dist/nodes/devices/icons",
"build:icons:actions": "cp -pr icons/actions dist/nodes/actions/icons",
"build:icons:yeelight": "cp -pr icons/yeelight dist/nodes/yeelight/icons"
},
"license": "MIT",
"keywords": [
"Xiaomi",
"Aqara",
"Yeelight",
"node-red"
],
"node-red": {
"nodes": {
"xiaomi-ht": "node-red-contrib-xiaomi-ht/xiaomi-ht.js",
"xiaomi-magnet": "node-red-contrib-xiaomi-magnet/xiaomi-magnet.js",
"xiaomi-motion": "node-red-contrib-xiaomi-motion/xiaomi-motion.js",
"xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js",
"xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js",
"xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js",
"xiaomi-all": "node-red-contrib-xiaomi-all/xiaomi-all.js",
"xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js",
"xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js",
"xiaomi-actions": "node-red-contrib-xiaomi-actions/xiaomi-actions.js",
"xiaomi-yeelight": "dist/nodes/yeelight/index.js"
}
},
"author": "Pierre CLEMENT",
"bugs": {
"url": "https://github.com/pierrecle/node-red-contrib-mi-devices/issues"
},
@@ -51,14 +11,61 @@
"miio": "^0.14.1",
"yeelight-wifi": "^2.3.0"
},
"engines": {
"node": ">=4.4.5"
},
"description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway, and the Gateway itself.",
"devDependencies": {
"@types/node-red": "^0.17.1",
"ejs": "^2.5.7",
"ejs-cli": "^2.0.0",
"rimraf": "^2.6.2",
"typescript": "^2.6.2"
},
"engines": {
"node": ">=4.4.5"
},
"homepage": "https://github.com/pierrecle/node-red-contrib-mi-devices#readme",
"keywords": [
"Xiaomi",
"Aqara",
"Yeelight",
"node-red"
],
"license": "MIT",
"name": "node-red-contrib-mi-devices",
"node-red": {
"nodes": {
"xiaomi-ht": "node-red-contrib-xiaomi-ht/xiaomi-ht.js",
"xiaomi-natgas": "node-red-contrib-xiaomi-natgas/xiaomi-natgas.js",
"xiaomi-smoke": "node-red-contrib-xiaomi-smoke/xiaomi-smoke.js",
"xiaomi-magnet": "node-red-contrib-xiaomi-magnet/xiaomi-magnet.js",
"xiaomi-motion": "node-red-contrib-xiaomi-motion/xiaomi-motion.js",
"xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js",
"xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js",
"xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js",
"xiaomi-all": "node-red-contrib-xiaomi-all/xiaomi-all.js",
"xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js",
"xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js",
"xiaomi-actions": "node-red-contrib-xiaomi-actions/xiaomi-actions.js"
}
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/pierrecle/node-red-contrib-mi-devices.git"
},
"scripts": {
"build": "npm run clean && npm run build:ts && npm run build:ejs && npm run build:icons",
"build:ejs": "npm run build:ejs:indexes",
"build:ejs:devices": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"nodes/devices/*.ejs\" --out dist/",
"build:ejs:indexes": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"**/index.ejs\" --out dist/",
"build:icons": "npm run build:icons:yeelight",
"build:icons:actions": "cp -pr icons/actions dist/nodes/actions/icons",
"build:icons:devices": "cp -pr icons/devices dist/nodes/devices/icons",
"build:icons:gateway": "cp -pr icons/gateway dist/nodes/gateway/icons",
"build:icons:yeelight": "cp -pr icons/yeelight dist/nodes/yeelight/icons",
"build:ts": "tsc --allowUnreachableCode -p .",
"clean": "rimraf dist"
},
"version": "1.1.9",
"publishConfig": {
"registry": "https://git.familie-berner.de/api/packages/Public/npm/"
}
}