31 Commits

Author SHA1 Message Date
Alex M
de14f71616 3.1.5 2024-02-19 21:03:42 +03:00
Alex M
adb59a0d9d Update README.md
Friendly topics
2024-02-19 19:48:39 +03:00
Alex M
f865e259a1 Added friendly topics for the devices behind gateway
You can now use:
tuya/gw1/lv/dps/2/command
instead of tuya/zgw1/2342cd828dfsxckkk/dps/2/command
2024-02-19 19:42:02 +03:00
Alex M
4ea2f0a58f Added friendly topics for the devices behind gateway
You can now use:
tuya/gw1/lv/dps/2/command
instead of tuya/zgw1/2342cd828dfsxckkk/dps/2/command
2024-02-19 19:40:13 +03:00
lehanspb
f3d0d0602e decription for systemd startup script 2023-02-15 16:47:43 +03:00
lehanspb
bb97e731a9 v3.1.4 2023-02-15 13:46:48 +03:00
lehanspb
9a9a314d14 decription for systemd startup script 2023-02-15 13:39:38 +03:00
lehanspb
9080a41d9a DPS Topics for devices behind Tuya Gateway 2023-02-15 13:30:18 +03:00
lehanspb
f6bd28c41a Description for MQTT DPS topics for subdevices (cid) behind Tuya Gateway 2023-02-15 12:37:46 +03:00
lehanspb
1d1ab0d62a Description for MQTT DPS topics for subdevices (cid) behind Tuya Gateway 2023-02-15 12:36:25 +03:00
lehanspb
a170287e9e OpenHAB 3.x exmples 2023-02-15 12:21:22 +03:00
lehanspb
47734e03d0 Description for MQTT DPS topics for subdevices (cid) behind Tuya Gateway 2023-02-15 12:16:10 +03:00
lehanspb
eb800133a0 Some comments 2023-02-15 10:37:22 +03:00
lehanspb
6aded16979 Added cid support and dp-refresh function 2023-02-15 10:34:17 +03:00
lehanspb
7cc3d1d5a6 Change packege.json and requirements 2023-02-15 10:22:14 +03:00
Alex M
59d1131db5 Update README.md 2023-02-13 17:04:58 +03:00
tsightler
36f3854ca9 Update README.md 2021-06-01 13:12:35 -04:00
tsightler
b7fc73de3c Update bug_report.md 2021-02-09 12:13:08 -05:00
tsightler
2b92e2b27c Update feature_request.md 2021-02-09 12:12:36 -05:00
tsightler
35dde4bd11 Update feature_request.md 2021-02-09 12:11:57 -05:00
tsightler
d833151beb Update feature_request.md 2021-02-09 12:10:06 -05:00
tsightler
c179282393 Update README.md 2021-02-09 12:07:19 -05:00
tsightler
8b532d9df1 v3.0.4
* Fix reconnect failure for some error cases
* Bump tuyapi version to latest
2021-01-27 13:46:55 -05:00
tsightler
02d5124b62 v3.03
* Fix reconnect failure in some disconnect cases
* Remove duplicate republish function
2021-01-12 20:19:42 -05:00
tsightler
a5b344faeb Update to 3.0.2 (#63)
* Update 3.0.2
* Fix (hopefully) uninitialized key values for devices which block get requests for some DPS values based on operating mode (seen with at least one RGBTW light that will not return color DPS key while in white mode).
* Modify HSB/HSBHEX guessing in RGBTW light to attempt to deal with issue above
* Implement automatic reconnect when device disconnects socket on it's side
* Update package dependencies to latest versions
2021-01-05 22:18:19 -05:00
tsightler
aa80270ba2 Update 3.0.1 (#61)
* Script to merge device additions and changes into devices.conf (#49)
* Add republish on reconnect
* Filter additional invalid characters '+','#','/' from topic line (replace with '_')
* Minor bugfixes for parse values
* Include script for merging new devices into existing config
* Update dependency

Co-authored-by: tsightler <tsightler@gmail.com>
Co-authored-by: Doug Krahmer <doug.git@remhark.com>
2020-12-23 20:22:09 -05:00
tsightler
b29b98911a Revert "Dev (#60)"
This reverts commit 44a5c6adbf.
2020-12-23 20:18:39 -05:00
tsightler
44a5c6adbf Dev (#60)
3.0.1 Update
* Script to merge device additions and changes into devices.conf (#49)
* Fix typos in README.md
* Add republish on reconnect
* Filter additional invalid characters '+','#','/' from topic line (replace with '_')
* Minor bugfixes for parse values
* Include script for merging new devices into existing config
* Update dependency

Co-authored-by: tsightler <tsightler@gmail.com>
Co-authored-by: Doug Krahmer <doug.git@remhark.com>
2020-12-23 20:10:29 -05:00
tsightler
bda39b03b4 Update DEVICES.md 2020-10-27 18:15:27 -04:00
tsightler
48dd1c64b0 Update README.md 2020-10-19 21:40:16 -04:00
tsightler
55014cda3b Update CHANGELOG.md 2020-10-18 21:44:17 -04:00
15 changed files with 980 additions and 572 deletions

View File

@@ -6,35 +6,35 @@ labels: ''
assignees: ''
---
**!!! Please Read This First !!!**\
**!!! Please Read This First !!!**
If you are having difficulty controlling a specific device, please verify that the device works with tuya-cli prior to opening an issue here. If you are unable to control the device via tuya-cli it will not be possible to control it with tuya-mqtt. If the device is working with tuya-cli, please post your working commands and the equivalent commands for tuya-mqtt.
**Describe the bug**\
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**\
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**\
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**\
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**\
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**\
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**\
**Additional context**
Add any other context about the problem here.

View File

@@ -7,17 +7,5 @@ assignees: ''
---
**!!! Please Read This First !!!**\
If this feature request is to support a device that currently doesn't work, please verify that the device works with tuya-cli first. If the device cannot be used with tuya-cli, then it won't be possible to add support to tuya-mqtt. If you can query and control your device with tuya-cli, please post the commands and the functions they perform as part of the feature request.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
**!!! Please Read This First !!!**
Feature requiest for this project are not being accepted at this time as this project is in maintainance mode only.

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
old/
test/
config.json
.vscode/
.bak
# Logs
logs

View File

@@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [3.1.5]
### Added
- Added support of friendly names for the subDevices behind gateway (processDpsKeyWcidNameCommand)
### Changed
- Functions publishTopics and publishDpsTopics now can publish subDevice's friendly name to MQTT
## [3.1.0]
Forked from https://github.com/TheAgentK/tuya-mqtt
### Added
- Added support for Zigdee subdevices for Tuya Wireless Gateway
- Added new dp-refresh funtion according to tuyapi documentation
- Command and state topics for DPS keys for subdevices
### Changed
- Updated some libraries to latest version
## [3.0.0]
The 3.0.0 release is a major refactor of the project with significant changes from previous version. Only major additions and changes are listed below.
### Added
- Added templating engine for mapping device DPS value to friendly topics
- Added pre-defined template for common devices
- Command topics for DPS keys
### Changed
- Configuration for devices is now via devices.conf file (based on output format of 'tuya-cli wizard' command)
- Default device presents only raw DPS data
- Commands sent only via MQTT messages to command topics
- Updates all libraries to latest version
### Removed
- Topic based configuraiton has been removed
## [2.1.0]
### Added
- Added ability to update validate communicaton with device and update state topic by issuing { "schema": true } command
@@ -48,4 +83,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- TuyAPI repository not found
### Removed
### Removed

154
README.md
View File

@@ -1,14 +1,21 @@
# tuya-mqtt
This project provides a method for locally controlling IOT devices manufactured by Tuya Inc., and sold under many different brands, via MQTT.
Using this script requires obtaining the device ID and local keys for each of your devices after they are configured via the Tuya/Smart Life or other Tuya compatible app (there are many). With this information it is possible to communicate locally with Tuya devices using Tuya protocol version 3.1 and 3.3 without using the Tuya Cloud service, however, getting the keys requires signing up for a Tuya IOT developer account or using one of several other alternative methods (such as dumping the memory of a Tuya based app running on Andriod).
# Note
I'm not a developer and I'm new to nodejs.
I just need to get data from Zigbee devices behind Tuya Wireless Gateway and the ability to control them using MQTT commands.
I forked this repository [TheAgentK](https://github.com/TheAgentK/tuya-mqtt) and made some changes.
# About
This project is a bridge that allows locally controlling IOT devices manufactured by Tuya Inc., and sold under many different brands, via simple MQTT topics. It effectively translate the Tuya protocol to easy to use topics.
Using this script requires obtaining the device ID and local keys for each of your devices after they are configured via the Tuya/Smart Life or other Tuya compatible app (there are many). With this information it is possible to communicate locally with Tuya devices using Tuya protocol version 3.1 and 3.3 without using the Tuya Cloud service, however, getting the keys requires signing up for a Tuya IOT developer account or using one of several other alternative methods (such as dumping the memory of a Tuya based app running on Android).
To acquire keys for your device please see the instructions at the TuyAPI project (on which this script is based) available at the [TuyAPI GitHub site](https://github.com/codetheweb/tuyapi/blob/master/docs/SETUP.md).
**Acquiring device keys is outside the scope of this project!** Issues opened regarding acquiring keys will likely be closed without comment. Please verify that your device can be queried and controlled via tuya-cli before opening any issue. If your device can't be controlled by tuya-cli then it cannot be used with this project.
**!!!!!!!!!! Important information regarding the 3.0 release !!!!!!!!!!**\
The 3.0.0 release (Oct 17th, 2020) is a major refactor of the tuya-mqtt project and, as such, is a breaking release for all users of previous versions. Almost everything about the project is different, including configuration method, topic names, etc. Upgrading users should carefully read the instructions below and assume they are starting over from scratch.
**Acquiring device keys is outside the scope of this project!** Issues opened regarding acquiring keys will likely be closed without comment. Please verify that your device can be queried and controlled via tuya-cli before opening any issue. If your device can't be controlled by tuya-cli then it cannot be used with this project.
## Installation
Download this project to your system into any directory (example below uses /opt/tuya-mqtt) and install tuyapi from the same folder that the tuya-mqtt.js is in
@@ -17,7 +24,7 @@ Download this project to your system into any directory (example below uses /opt
cd /opt
// clone this project
git clone https://github.com/TheAgentK/tuya-mqtt
git clone https://github.com/lehanspb/tuya-mqtt
// change directory to the project directory
cd tuya-mqtt
@@ -29,7 +36,7 @@ npm install
## Configuration
Tuya-mqtt has two different configuration files. The first is config.json, a simple file which contains settings for connection to the MQTT broker. The second is devices.conf, a JSON5 formatted file which defines the Tuya devices that the script should connect to and expose via MQTT. This file uses the same basic format as the "tuya-cli wizard" outputs when used to acquire the device keys, so it can be used as the basis for your tuya-mqtt device configuration.
### Seting up config.json:
### Setting up config.json:
```
cp config.json.sample config.json
```
@@ -44,19 +51,35 @@ If you use the "tuya-cli wizard" method to acquire your device keys you can leve
[
{
name: 'Tuya Device 1',
version: '3.3',
ip: '192.168.20.251',
id: '86435357d8b123456789',
key: '8b2a69c9876543210'
},
{
name: 'Tuya Device 2',
version: '3.3',
ip: '192.168.20.252',
id: 'eb532eea7d12345678abc',
key: '899810012345678'
key: '899810012345678',
subDevices:
[ { name: 'subdevice1',
id: 'zt431eda8d12345678awc',
cid: '1a24fkfffe6b4e24'
},
{ name: 'subdevice1',
id: 'rb737qea7v15342678aq3',
cid: '1a24fkfffe0t2c29'
}
]
}
]
```
Note that, because the format is JSON5, which is a superset of JSON, you can use standard, strict JSON syntax, or the more forgiving JSON5 format, or even mix and match in the same file.
While the above syntax is enough to create a working tuya-mqtt install with generic devices, the full power and simplicity of tuya-mqtt 3.0 is only unlocked by configuring device types to get . Please see the full [DEVICES](docs/DEVICES.md) documenation for details.
By default tuya-mqtt will attempt to find the device and automatically detect the Tuya protocol version, however, this only works if the system running tuya-mqtt is on the same network/subnet as the devices being controlled. If this is not the case, or if automatic detection fails for some other reason, it is possible to specify the IP address and protocol manually by adding the "ip:" property to the devices.conf file. Note that if the IP address is specified manually it is required to also manually specify the protocol version using the "version:" parameter as either "3.1" or "3.3". The easiest way to determine the protocol version is to try controlling the device with tuya-cli and try each version to see which one works.
While the above syntax may be enough to create a working tuya-mqtt install with raw DPS values accessible via DPS topics, the full functionality of tuya-mqtt 3.0 is only unlocked by configuring device types to get. Please see the full [DEVICES](docs/DEVICES.md) documentation for details.
### Starting tuya-mqtt
```
@@ -65,15 +88,53 @@ node tuya-mqtt.js
To enable debugging output (required when opening an issue):
```
DEBUG=tuya-mqtt:* tuya-mqtt.js
or
DEBUG=* tuya-mqtt.js
for full debugging
```
### Systemd script for Debian-like OS
Just create file /etc/systemd/system/tuya-mqtt.service
```
[Unit]
Description=tuya-mqtt
After=network.target
[Service]
ExecStart=/usr/bin/node /opt/tuya-mqtt/tuya-mqtt.js
Restart=always
User=openhab
Group=openhab
Environment=PATH=/usr/bin/
Environment=NODE_ENV=production
WorkingDirectory=/opt/tuya-mqtt/
[Install]
WantedBy=multi-user.target
```
Enable and run:
```
systemctl enable tuya-mqtt.service
systemctl start tuya-mqtt
```
### Updating devices.conf with new and/or changed devices:
After adding or changing devices to your Tuya account the devices.conf file can be automatically updated with all new devices and name/key changes by using the merge-devices.js script. Create a file named new-devices.conf with the new "tuya-cli wizard" output then run ```node merge-devices.js```. A dated backup of the original devices.conf file will be created automatically before changes are made. Devices are only added and updated, never removed. The resulting devices.conf file will be neatly formatted and sorted alphabetically by device name.
To prevent device entries from being updated by the merge script, add property "allowMerge: false" to the device definition in the devices.conf file.
### Usage Overview
Tuya devices work by mapping device functions to various values stored in data points (refered to as DPS values) which are referenced via an index number, referred to as the DPS key. For example, a simple on/off switch may have a single DPS value, stored in DPS kep 1 (DPS1). This value is likely to have a setting of true/false representing the on/off state of the device. The device state can be read via DPS1, and, for values that can be changed (some DPS values are read-only), sending true/false to DPS1 will turn the device on/off. A simple dimmer might have the same DPS1 value, but an additional DPS2 value from 1-255 representing the state of the dimmer. More complex devices use more DPS keys with various values representing the states and control functions of the device.
Tuya devices work by mapping device functions to various values stored in data points (referred to as DPS values) which are referenced via an index number, referred to as the DPS key. For example, a simple on/off switch may have a single DPS value, stored in DPS kep 1 (DPS1). This value is likely to have a setting of true/false representing the on/off state of the device. The device state can be read via DPS1, and, for values that can be changed (some DPS values are read-only), sending true/false to DPS1 will turn the device on/off. A simple dimmer might have the same DPS1 value, but an additional DPS2 value from 1-255 representing the state of the dimmer. More complex devices use more DPS keys with various values representing the states and control functions of the device.
The tuya-mqtt script provides access to these DPS keys and their values via MQTT, allowing any tool that can use MQTT to monitor and control these devices via a local network connection. In addition to providing access to the raw DPS data, there is also a template engine that allows those DPS values to be mapped to device specific topics, called "friendly topics", allowing for consistent mapping even between devices that use different DPS keys for the same functions. These friendly topics also support various transforms and other functions that make it easier for other devices to communicate with Tuya devices without a detailed understanding of the data formats Tuya devices use.
### MQTT Topic Overview
The top level topics are created using the device name or ID as the primary identifier. If the device name is available, it will be converted to lowercase and any spaces replace with underscores('_') characters so, for example, if the device as the name "Kitche Table", the top level topic would be:
The top level topics are created using the device name or ID as the primary identifier. If the device name is available, it will be converted to lowercase and any spaces replace with underscores('_') characters so, for example, if the device as the name "Kitchen Table", the top level topic would be:
```
tuya/kitchen_table/
```
@@ -83,11 +144,11 @@ tuya/86435357d8b123456789/
```
All additional state/command topics are then built below this level. You can view the connectivity status of the device using the status topic, which reports online/offline based on whether tuya-mqtt has an active connection to the device or not. The script monitors both the device socket connection for errors and also device heartbeats, to report proper status.
```
tuya/kitche_table/state --> online/offline
tuya/kitchen_table/state --> online/offline
```
You can also trigger the device to send an immediate update of all known device DPS topics by sending the message "get-states" to the command topic (this topic exist for all devices):
```
tuya/kitche_table/command <-- get-states
tuya/kitchen_table/command <-- get-states
```
As noted above, tuya-mqtt supports two distinct topic types for interfacing with and controlling devices. For all devices, the DPS topics are always published and commands are accepted, however, friendly topics are the generally recommended approach but require you to use a pre-defined device template or create a customer template for your device when using the generic device.
@@ -96,52 +157,75 @@ If you do create a template for your device, please feel free to share it with t
If you would like to use the raw DPS topics, please jump to the [DPS topics](#dps-topics) section of this document.
## Friendly Topics
Friendly topics are only available when using a pre-defined device template or, for the generic device, when you have defined a custom template for your device. Friendly topics use the tuyq-mqtt templating engine to map raw Tuya DPS key values to easy to consume topics and transform the data where needed.
Friendly topics are only available when using a pre-defined device template or, for the generic device, when you have defined a custom template for your device. Friendly topics use the tuya-mqtt templating engine to map raw Tuya DPS key values to easy to consume topics and transform the data where needed.
Another advantage of friendly topics is that not all devices respond to schema requets (i.e. a request to report all DPS topics the device uses). Because of this, it's not always possible for tuya-mqtt to know which DPS topics to acquire state information from during initial startup. With a defined template the required DPS keys for each friendly topic are configured and tuya-mqtt will always query these DPS key values during initial connection to the device and report their state appropriately.
Another advantage of friendly topics is that not all devices respond to schema requests (i.e. a request to report all DPS topics the device uses). Because of this, it's not always possible for tuya-mqtt to know which DPS topics to acquire state information from during initial startup. With a defined template the required DPS keys for each friendly topic are configured and tuya-mqtt will always query these DPS key values during initial connection to the device and report their state appropriately.
For more details on using friendly topics, please read the [DEVICES](docs/DEVICES.md) documentation which discusses how to configure supported devices or define a custom template.
## DPS Topics
Controlling devices directly via DPS topics requires enough knowledge of the device to know which topics accept what values. Described below are two differnt methods for interfacing with DPS values, the JSON DPS topic, and the individual DPS key topics.
Controlling devices directly via DPS topics requires enough knowledge of the device to know which topics accept what values. Described below are two different methods for interfacing with DPS values, the JSON DPS topic, and the individual DPS key topics.
### DPS JSON topic
The JSON DPS topic allows controlling Tuya devices by sending Tuya native style JSON messages to the command topic, and by monitoring for Tuya style JSON replies on the state topic. You can get more details on this format by reading the [TuyAPI documentaiton](https://codetheweb.github.io/tuyapi/index.html), but, for example, to turn off a dimmer switch you could issue a MQTT message containing the JSON value ```{dps: 1, set: false}``` to the DPS/command topic for the device. If you wanted to turn the dimmer on, and set brightness to 50%, you could issue separate messages ```{dps: 1, set: true}``` and then ```{dps: 2, set: 128}```, or, the Tuya JSON protocol also allows setting multiple values in a single set command using the format ```{'multiple': true, 'data': {'1': true, '2': 128}}```. JSON state and commands should use the DPS/state and DPS/command topics respectively. Below is an example of the topics:
The JSON DPS topic allows controlling Tuya devices by sending Tuya native style JSON messages to the command topic, and by monitoring for Tuya style JSON replies on the state topic. You can get more details on this format by reading the [TuyAPI documentation](https://codetheweb.github.io/tuyapi/index.html), but, for example, to turn off a dimmer switch you could issue a MQTT message containing the JSON value ```{dps: 1, set: false}``` to the DPS/command topic for the device. If you wanted to turn the dimmer on, and set brightness to 50%, you could issue separate messages ```{dps: 1, set: true}``` and then ```{dps: 2, set: 128}```, or, the Tuya JSON protocol also allows setting multiple values in a single set command using the format ```{'multiple': true, 'data': {'1': true, '2': 128}}```. JSON state and commands should use the DPS/state and DPS/command topics respectively. Below is an example of the topics:
```
tuya/dimmer_device/DPS/state
tuya/dimmer_device/DPS/command
```
### DPS Key topics
In addition to the JSON DPS topic, it's also possible to use the DPS key topics. DPS key topics allow you to monitor and send simple bool/number/string values directly to DPS keys without having to use the Tuya JSON format, the conversion to Tuya JSON is handled by tuya-mqtt. Using the example from above, turning on the dimmer and setting brightness to 50% you would simply issue the message "true" to DPS/1/command and the message "128" to DPS/2/command.
In addition to the JSON DPS topic, it's also possible to use the DPS key topics. DPS key topics allow you to monitor and send simple bool/number/string values directly to DPS keys without having to use the Tuya JSON format, the conversion to Tuya JSON is handled by tuya-mqtt. Using the example from above, turning on the dimmer and setting brightness to 50% you would simply issue the message "true" to DPS/1/command and the message "128" to DPS/2/command.
```
tuya/dimmer_device/DPS/1/state --> true/false for on/off state
tuya/dimmer_device/DPS/2/command <-- 1-255 for brightness state
tuya/dimmer_device/DPS/1/state --> accept true/false for turning device on/off
tuya/dimmer_device/DPS/2/command <-- accepts 1-255 for controlling brightness level
```
**!!! Important Note !!!**\
When sending commands directly to DPS values there are no limitation on what values are sent as tuya-mqtt has no way to know what are valid vs invalid for any given DPS key. Sending values that are out-of-range or of different types than the DPS key expects can cause unpredicatable behavior of your device, from causing timeouts, to reboots, to hanging the device. While I've never seen a device fail to recover after a restart, please keep this in mind when sending commands to your device.
**!!! Important Note !!!**
When sending commands directly to DPS values there are no limitation on what values are sent as tuya-mqtt has no way to know what are valid vs invalid for any given DPS key. Sending values that are out-of-range or of different types than the DPS key expects can cause unpredictable behavior of your device, from causing timeouts, to reboots, to hanging the device. While I've never seen a device fail to recover after a restart, please keep this in mind when sending commands to your device.
## DPS Topics for devices behind Tuya Gateway
In addition to the DPS Key topics, it's possible to use the DPS for devices behind Tuya Gateway.
'cid' - is the subdevice id.
'cidname' - is the name of subdevice (from devices.conf)
This example demostrates DPS values and commands for Tuya Smart Thermostat Radiator Valve behind Tuya Gateway:
```
Thermostat mode:
tuya/zgw1/thermostat/dsp/4/state --> {"4":"auto"}
Possible values: auto/temp_auto/holiday/manual/comfort/eco/BOOST
tuya/zgw1/thermostat/dps/4/command <-- auto
Temperature Setpoint:
tuya/zgw1/thermostat/dps/2/state --> {"2": 220}
Where 220 - 22.0 Celsius
tuya/zgw1/thermostat/dps/command <-- 225
Current Temperature:
tuya/zgw1/thermostat/dps/3/state --> {"3": 225}
Where 225 - 22.5 Celsius
Valve percent:
tuya/zgw1/thermostat/dps/109/state --> {"109": 30}
Where 30 - 30%
```
You can also use JSON commands with 'cid'
```
tuya/zgw1/dps/command <-- {"dps": 2, "set": 225, "cid": "2c34f13fde594a34"}
```
## Issues
Not all Tuya protocols are supported. For example, some devices use protocol 3.2 which currently remains unsupported by the TuyAPI project due to lack of enough information to reverse engineer the protcol. If you are unable to control your devices with tuya-mqtt please verify that you can query and control them with tuya-cli first. If tuya-cli works, then this script should also work, if it doesn't then this script will not work either.
Not all Tuya protocols are supported. For example, some devices use protocol 3.2 which currently remains unsupported by the TuyAPI project due to lack of enough information to reverse engineer the protocol. If you are unable to control your devices with tuya-mqtt please verify that you can query and control them with tuya-cli first. If tuya-cli works, then this script should also work, if it doesn't then this script will not work either.
## Integration with other Home Automation tools
openHAB examples are [here](docs/openHAB.md).
## Integration with openHAB
openHAB 3.x examples are [here](docs/openHAB.md).
## Contributors
- [TheAgentK](https://github.com/TheAgentK)
- [tsightler](https://github.com/tsightler)
- [Tycale](https://github.com/Tycale)
- [crashdummymch](https://github.com/crashdummymch)
- [GadgetAngel](https://github.com/GadgetAngel)
- [lehanspb](https://github.com/lehanspb)
## Related Projects:
- https://github.com/codetheweb/tuyapi
- https://github.com/unparagoned/njsTuya
- https://github.com/clach04/python-tuya
- https://github.com/Marcus-L/m4rcus.TuyaCore
- Specs: https://docs.tuya.com/en/cloudapi/cloud_access.html
[![forthebadge](https://forthebadge.com/images/badges/made-with-javascript.svg)](https://forthebadge.com)
[![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com)

View File

@@ -1,29 +0,0 @@
// Object to capture process exits and call app specific cleanup function
var debug = require('debug')('Cleanup');
function noOp() {};
exports.Cleanup = function Cleanup(callback) {
// attach user callback to the process event emitter
// if no callback, it will still exit gracefully on Ctrl-C
callback = callback || noOp;
process.on('cleanup', callback);
// do app specific cleaning before exiting
process.on('exit', function () {
process.emit('cleanup');
});
// catch ctrl+c event and exit normally
process.on('SIGINT', function () {
debug('Ctrl-C...');
process.exit(2);
});
//catch uncaught exceptions, trace, then exit normally
process.on('uncaughtException', function (e) {
debug('Uncaught Exception...', e.stack);
process.exit(99);
});
};

View File

@@ -3,6 +3,5 @@
"port": 1883,
"topic": "tuya/",
"mqtt_user": "",
"mqtt_pass": "",
"qos": 2
"mqtt_pass": ""
}

View File

@@ -148,7 +148,7 @@ class RGBTWLight extends TuyaDevice {
const mode2 = await this.device.get({"dps": 2})
const mode21 = await this.device.get({"dps": 21})
if (mode2 && (mode2 === 'white' || mode2 === 'colour' || mode2.toString().includes('scene'))) {
debug('Detected probably Tuya color bulb at DPS 1-5, checking more details...')
debug('Detected likely Tuya color bulb at DPS 1-5, checking more details...')
this.guess = {'dpsPower': 1, 'dpsMode': 2, 'dpsWhiteValue': 3, 'whiteValueScale': 255, 'dpsColorTemp': 4, 'colorTempScale': 255, 'dpsColor': 5}
} else if (mode21 && (mode21 === 'white' || mode21 === 'colour' || mode21.toString().includes('scene'))) {
debug('Detected likely Tuya color bulb at DPS 20-24, checking more details...')
@@ -159,14 +159,18 @@ class RGBTWLight extends TuyaDevice {
debug('Attempting to detect if bulb supports color temperature...')
const colorTemp = await this.device.get({"dps": this.guess.dpsColorTemp})
if (colorTemp !== '' && colorTemp >= 0 && colorTemp <= this.guess.colorTempScale) {
debug('Detected likely color temerature support')
debug('Detected likely color temperature support')
} else {
debug('No color temperature support detected')
this.guess.dpsColorTemp = 0
}
debug('Attempting to detect Tuya color format used by device...')
const color = await this.device.get({"dps": this.guess.dpsColor})
this.guess.colorType = (color && color.length === 12) ? 'hsb' : 'hsbhex'
if (this.guess.dpsPower === 1) {
this.guess.colorType = (color && color.length === 12) ? 'hsb' : 'hsbhex'
} else {
this.guess.colorType = (color && color.length === 14) ? 'hsbhex' : 'hsb'
}
debug ('Detected Tuya color format '+this.guess.colorType.toUpperCase())
}
}

View File

@@ -11,13 +11,14 @@ class TuyaDevice {
this.config = deviceInfo.configDevice
this.mqttClient = deviceInfo.mqttClient
this.topic = deviceInfo.topic
// Build TuyAPI device options from device config info
this.options = {
id: this.config.id,
key: this.config.key
}
if (this.config.name) { this.options.name = this.config.name.toLowerCase().replace(/ /g,'_') }
if (this.config.name) { this.options.name = this.config.name.toLowerCase().replace(/\s|\+|#|\//g,'_') }
if (this.config.ip) {
this.options.ip = this.config.ip
if (this.config.version) {
@@ -27,6 +28,9 @@ class TuyaDevice {
}
}
debug(' ############ Config ', JSON.stringify(this.config))
debug(' ############ Options ', JSON.stringify(this.options))
// Set default device data for Home Assistant device registry
// Values may be overridden by individual devices
this.deviceData = {
@@ -37,6 +41,7 @@ class TuyaDevice {
// Initialize properties to hold cached device state data
this.dps = {}
this.cid = {}
this.color = {'h': 0, 's': 0, 'b': 0}
// Device friendly topics
@@ -44,6 +49,7 @@ class TuyaDevice {
// Missed heartbeat monitor
this.heartbeatsMissed = 0
this.reconnecting = false
// Build the MQTT topic for this device (friendly name or device id)
if (this.options.name) {
@@ -55,10 +61,31 @@ class TuyaDevice {
// Create the new Tuya Device
this.device = new TuyAPI(JSON.parse(JSON.stringify(this.options)))
this.device.on('dp-refresh', (data) => {
if (typeof data === 'object') {
if (data.cid) {
debug('Received dp-refresh data from device '+this.options.id+' cid: '+data.cid+' ->', JSON.stringify(data.dps))
} else {
debug('Received dp-refresh data from device '+this.options.id+' ->', JSON.stringify(data.dps))
debug('Received dp-refresh data from device '+this.options.id+' ->', JSON.stringify(data))
}
this.updateState(data)
} else {
if (data !== 'json obj data unvalid') {
debug('Received string data from device '+this.options.id+' ->', data.replace(/[^a-zA-Z0-9 ]/g, ''))
}
}
})
// Listen for device data and call update DPS function if valid
this.device.on('data', (data) => {
if (typeof data === 'object') {
debug('Received JSON data from device '+this.options.id+' ->', JSON.stringify(data.dps))
if (data.cid) {
debug('Received JSON data from device '+this.options.id+' cid: '+data.cid+' ->', JSON.stringify(data.dps))
} else {
debug('Received JSON data from device '+this.options.id+' ->', JSON.stringify(data.dps))
debug('Received JSON data from device '+this.options.id+' ->', JSON.stringify(data))
}
this.updateState(data)
} else {
if (data !== 'json obj data unvalid') {
@@ -85,19 +112,19 @@ class TuyaDevice {
})
// On disconnect perform device specific disconnect
this.device.on('disconnected', () => {
this.device.on('disconnected', async () => {
this.connected = false
this.publishMqtt(this.baseTopic+'status', 'offline')
debug('Disconnected from device ' + this.toString())
await utils.sleep(5)
this.reconnect()
})
// On connect error call reconnect
this.device.on('error', async (err) => {
debugError(err)
await utils.sleep(1)
if (!this.device.isConnected()) {
this.reconnect()
}
this.reconnect()
})
// On heartbeat reset heartbeat timer
@@ -112,9 +139,9 @@ class TuyaDevice {
this.connected = false
for (let topic in this.deviceTopics) {
const key = this.deviceTopics[topic].key
if (!this.dps[key]) { this.dps[key] = {} }
try {
const result = await this.device.get({"dps": key})
this.dps[key].val = result
this.dps[key].val = await this.device.get({"dps": key})
this.dps[key].updated = true
} catch {
debugError('Could not get value for device DPS key '+key)
@@ -147,16 +174,27 @@ class TuyaDevice {
}
}
}
if (this.connected) {
this.publishTopics()
}
let cid = data.cid
this.cid = cid
// Had to comment this out 2024.02.19
// New Tuya multimode gateway (zigbee & ble) don't send connected state from BLE-devices
// if (this.connected) {
// this.publishTopics()
// }
this.publishTopics()
} else {
debug('Could not updateState ' + JSON.stringify(data))
}
}
// Publish device specific state topics
publishTopics() {
// Don't publish if device is not connected
if (!this.connected) return
// Had to comment it out 2024.02.19
// New Tuya multimode gateway (zigbee & ble) don't send connected state from BLE-devices
// if (!this.connected) return
// Loop through and publish all device specific topics
for (let topic in this.deviceTopics) {
@@ -166,7 +204,14 @@ class TuyaDevice {
if (this.dps[key] && this.dps[key].updated) {
const state = this.getTopicState(deviceTopic, this.dps[key].val)
if (state) {
this.publishMqtt(this.baseTopic + topic, state, true)
if (this.cid) {
// this.publishMqtt(this.baseTopic + cid + '/' + topic, state, true)
// Change cid to cidname
let cidname = this.config.subDevices.find(el => el.cid === this.cid);
this.publishMqtt(this.baseTopic + cidname.name + '/' + topic, state, true)
} else {
this.publishMqtt(this.baseTopic + topic, state, true)
}
}
}
}
@@ -180,7 +225,18 @@ class TuyaDevice {
try {
if (!Object.keys(this.dps).length) { return }
const dpsTopic = this.baseTopic + 'dps'
let dpsTopic
if (this.cid) {
// dpsTopic = this.baseTopic + this.cid + '/dps'
// Change cid to cidname
let cidname = this.config.subDevices.find(el => el.cid === this.cid);
dpsTopic = this.baseTopic + cidname.name + '/dps'
} else {
dpsTopic = this.baseTopic + 'dps'
}
// const dpsTopic = this.baseTopic + 'dps'
// Publish DPS JSON data if not empty
let data = {}
for (let key in this.dps) {
@@ -195,6 +251,7 @@ class TuyaDevice {
this.publishMqtt(dpsStateTopic, data, false)
// Publish dps/<#>/state value for each device DPS
// or cid/dps/<#>/state if cid exists
for (let key in this.dps) {
// Only publish values if different from previous value
if (this.dps[key].updated) {
@@ -249,10 +306,10 @@ class TuyaDevice {
// Perform any required math transforms before returing command value
switch (deviceTopic.type) {
case 'int':
value = (deviceTopic.stateMath) ? parseInt(Math.round(evaluate(value+deviceTopic.stateMath))) : value = parseInt(value)
value = (deviceTopic.stateMath) ? parseInt(Math.round(evaluate(value+deviceTopic.stateMath))) : parseInt(value)
break;
case 'float':
value = (deviceTopic.stateMath) ? parseFloat(evaluate(value+deviceTopic.stateMath)) : value = parseFloat(value)
value = (deviceTopic.stateMath) ? parseFloat(evaluate(value+deviceTopic.stateMath)) : parseFloat(value)
break;
}
@@ -288,7 +345,7 @@ class TuyaDevice {
const deviceTopic = this.deviceTopics.hasOwnProperty(stateTopic) ? this.deviceTopics[stateTopic] : ''
if (deviceTopic) {
debugCommand('Device '+this.options.id+' received command topic: '+commandTopic+', message: '+command)
// debugCommand('Device '+this.options.id+' received command topic: '+commandTopic+', message: '+command)
let commandResult = this.sendTuyaCommand(command, deviceTopic)
if (!commandResult) {
debugCommand('Command topic '+this.baseTopic+commandTopic+' received invalid value: '+command)
@@ -325,6 +382,24 @@ class TuyaDevice {
}
}
processDpsKeyWcidNameCommand(message, subDevDpsKey, cidName) {
if (utils.isJsonString(message)) {
debugCommand('Individual DPS command topics do not accept JSON values')
} else {
const dpsMessage = this.parseDpsMessage(message)
let subdev = this.config.subDevices.find(el => el.name === cidName);
const sdcid = subdev.cid
debugCommand('Received command for '+sdcid+' DPS'+subDevDpsKey+': ', message)
const command = {
dps: subDevDpsKey,
set: dpsMessage,
cid: sdcid
}
this.set(command)
}
}
// Parse string message into boolean and number types
parseDpsMessage(message) {
if (typeof message === 'boolean' ) {
@@ -596,10 +671,21 @@ class TuyaDevice {
// Retry connection every 10 seconds if unable to connect
async reconnect() {
debugError('Error connecting to device id '+this.options.id+'...retry in 10 seconds.')
await utils.sleep(10)
if (this.connected) { return }
this.connectDevice()
if (!this.reconnecting) {
this.reconnecting = true
debugError('Error connecting to device id '+this.options.id+'...retry in 10 seconds.')
await utils.sleep(10)
this.connectDevice()
this.reconnecting = false
}
}
// Republish device discovery/state data (used for Home Assistant state topic)
async republish() {
const status = (this.device.isConnected()) ? 'online' : 'offline'
this.publishMqtt(this.baseTopic+'status', status)
await utils.sleep(1)
this.init()
}
// Simple function to monitor heartbeats to determine if
@@ -612,7 +698,7 @@ class TuyaDevice {
await utils.sleep(1)
this.connectDevice()
} else if (this.heartbeatsMissed > 0) {
const errMessage = this.heartbeatsMissed > 1 ? " consecutive heartbeats" : " heartbeat"
const errMessage = this.heartbeatsMissed > 1 ? " heartbeats" : " heartbeat"
debugError('Device id '+this.options.id+' has missed '+this.heartbeatsMissed+errMessage)
}
this.heartbeatsMissed++
@@ -627,4 +713,4 @@ class TuyaDevice {
}
}
module.exports = TuyaDevice
module.exports = TuyaDevice

View File

@@ -193,7 +193,7 @@ The following tables define the available template value types and their options
### String values
| option | value |
| --- | --- |
| type | 'string' |
| type | 'str' |
| key | DPS key of the value |
### Tuya HSB values (newer style Tuya, 12 character color value)
@@ -210,4 +210,4 @@ The following tables define the available template value types and their options
| key | DPS key of the value |
| components | Comma separated list of HSB components that should be included in this topic |
Using these value types you can define templates for a wide range of devices. Additional types and options are likely to be included in future versions of tuya-mqtt.
Using these value types you can define templates for a wide range of devices. Additional types and options are likely to be included in future versions of tuya-mqtt.

View File

@@ -1,142 +1,217 @@
:exclamation: There is a greate Step-By-Step guide from user HolgiHab at openhab community ([Step-By-Step Guide](
https://community.openhab.org/t/step-by-step-guide-for-adding-tuya-bulbs-smart-life-to-oh2-using-tuya-mqtt-js-by-agentk/59371)). This guide is not only for light bulbs, but also applies to sockets. :exclamation:
## Example items for OpenHAB 3.x Bindings
## Example items for OpenHAB 1.x Bindings (still works with OH > 2.4 but only if legacy 1.x MQTT bindings are enabled)
### simple switch on/off
### Tuya Smart Thermostat Radiator Valve behind Tuya Gateway
### Things channels (configured via web):
#### Thermostat mode:
Channel identifier:
```
mode
```
Switch tuya_kitchen_coffeemachine_mqtt "Steckdose Kaffeemaschine" <socket> (<GROUPS>) ["Switchable"] {
mqtt="<[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/state:state:default:.*],
>[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command/on:command:ON:true],
>[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command/off:command:OFF:false]"
State:
```
tuya/zgw1/1a24fkfffe6b4e24/dsp/4/state
```
Example output: {"4":"auto"}
Possible values: auto/temp_auto/holiday/manual/comfort/eco/BOOST
Command:
```
tuya/zgw1/dps/command
```
Outgoing Value Format:
```
{"dps": 4, "set": "%s", "cid": "1a24fkfffe6b4e24"}
```
#### Temperature Setpoint
Channel identifier:
```
setpoint
```
State:
```
tuya/zgw1/1a24fkfffe6b4e24/dsp/2/state
```
Example output: {"2": 220}
Command:
```
tuya/zgw1/dps/command
```
Incoming Value Transformations:
```
JS:tuya-in.js
```
Outgoing Value Transformation:
```
JS:tuya-out.js
```
Outgoing Value Format:
```
{"dps": 2, "set": "%s", "cid": "1a24fkfffe6b4e24"}
```
#### Current Temperature
Channel identifier:
```
temperature
```
State:
```
tuya/zgw1/1a24fkfffe6b4e24/dsp/3/state
```
Incoming Value Transformations:
```
JS:tuya-in.js
```
#### Valve percent
Channel identifier:
```
valve_percent
```
State:
```
tuya/zgw1/1a24fkfffe6b4e24/dsp/109/state
```
Command:
```
tuya/zgw1/dps/command
```
Outgoing Value Format:
```
{"dps": 109, "set": %s, "cid": "1a24fkfffe6b4e24"}
```
### Transformations
tuya-in.js:
```
(function(i) {
return (i / 10)
})(input)
```
tuya-out.js:
```
(function(i) {
return (i * 10)
})(input)
```
### items/thermostat.items
```
String Radiator_Mode "Mode" <radiator> { channel="mqtt:topic:home:zgw1dev1:mode" }
Number Radiator_Setpoint "Temperature setpoint [%.1f °C]" <radiator> { channel="mqtt:topic:home:zgw1dev1:setpoint" }
Number Radiator_Temperature "Current temperature [%.1f °C]" <temperature> { channel="mqtt:topic:home:zgw1dev1:temperature" }
Number Radiator_Valve_Percent "Valve percent [%d %%]" { channel="mqtt:topic:home:zgw1dev1:valve_percent" }
```
### sitemaps/home.sitemap
```
Frame label="Heating" {
Setpoint item=Radiator_Setpoint minValue=15 maxValue=30 step=0.5
Selection item=Radiator_Mode mappings=[auto='Auto', temp_auto='Auto temp', manual='Manual', comfort='Comfort']
Text item=Radiator_Setpoint
Text item=Radiator_Temperature
Text item=Radiator_Valve_Percent
}
Switch tuya_livingroom_ledstrip_tv "LED Regal" <lightbulb> (<GROUPS>) ["Lighting"] {
mqtt="<[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/state:state:default:.*],
>[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command/on:command:ON:true],
>[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command/off:command:OFF:false]"
}
```
### change color of lightbulb
### Simple on/off switch with power measurement capability
### Things channels (configured via web):
#### Power switch
Channel identifier:
```
power
```
# .items
Group gTuyaLivingColor "Tuya color group" <lightbulb>
Color tuya_livingroom_colorpicker "Stehlampe farbe" (LivingDining)
String tuya_livingroom_ledstrip_tv_color "Set color [%s]" (gTuyaLivingColor, LivingDining) {
mqtt=">[broker:tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/color:command:*:default]"
}
# .rules
import org.openhab.core.library.types.HSBType;
rule "Set HSB value of item RGBLed to RGB color value"
when
Item tuya_livingroom_colorpicker received command
then
var appName = "Colorpicker.livingroom"
var color = receivedCommand.toString;
// get all colors and send it via mqtt if light ist enabled
gTuyaLivingColor.members.forEach[ i |
var name = i.name;
var stateName = name.toString.split("_color").get(0);
var stateItem = gTuyaLights.allMembers.filter [ conf | conf.name.contains(stateName.toString) ].head;
if(stateItem.state == ON){
logInfo(appName, name + " change to color: " + color);
i.sendCommand(color);
Thread::sleep(400);
}
]
end
State:
```
tuya/tuya_device_1/state
```
Example output: {"4":"auto"}
Possible values: auto/temp_auto/holiday/manual/comfort/eco/BOOST
Command:
```
tuya/tuya_device_1/command
```
## Example items for OpenHAB 2.4 Bindings
### simple switch on/off
With OpenHAB 2.X MQTT bindings you can add devices using a generic MQTT Thing via PaperUI or
configuration files. For PaperUI simply at the generic MQTT Thing and set the state and
command topics as follows:
Custom On/Open Value:
```
ON
```
tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/state
tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command
Custom Off/Closed Value:
```
OFF
```
If you prefer using configuration files vs PaperUI, it should look something like this:
See also OpenHAB 2.X MQTT binding [documentation](https://www.openhab.org/v2.4/addons/bindings/mqtt.generic/)
#### Power consumption watts
Channel identifier:
```
w
```
Bridge mqtt:broker:myUnsecureBroker [ host="localhost", secure=false ]
{
Thing mqtt:topic:myCustomMQTT {
Channels:
Type switch : tuya_kitchen_coffeemachine_mqtt_channel "Kitchen Coffee Machine MQTT Channel" [
stateTopic="tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/state",
commandTopic="tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/command",
// optional custom mqtt-payloads for ON and OFF
on="{ \"dps\": 1, \"set\": true }",
off="0"
]
}
}
# *.item Example
Switch tuya_kitchen_coffeemachine_mqtt "Kitchen Coffee Machine Switch" <socket> (gKitchen, gTuya) ["Switchable"] {
channel="mqtt:topic:myUnsecureBroker:myCustomMQTT:tuya_kitchen_coffeemachine_mqtt_channel"
}
State:
```
tuya/tuya_device_1/dps/19/state
```
For one RGB bulb you would need a separate channel with the command topic set to
`tuya/<tuyAPI-id>/<tuyAPI-key>/<tuyAPI-ip>/color` and link that to your color item.
Incoming Value Transformations:
JS:tuya-energy.js
#### Power consumption volts
Channel identifier:
```
v
```
Bridge mqtt:broker:myUnsecureBroker [ host="localhost", secure=false ]
{
Thing mqtt:topic:myCustomMQTT {
Channels:
Type colorHSB : livingroom_floorlamp_1_color "Livingroom floorlamp color MQTT Channel" [
stateTopic="tuya/05200399bcddc2e02ec9/b58cf92e8bc5c899/192.168.178.49/state",
commandTopic="tuya/05200399bcddc2e02ec9/b58cf92e8bc5c899/192.168.178.49/color"
]
}
}
# *.item Example
Color tuya_livingroom_colorpicker "Floorlamp colorpicker" (gLivingroom){
channel="mqtt:topic:myUnsecureBroker:myCustomMQTT:livingroom_floorlamp_1_color"
}
State:
```
tuya/tuya_device_1/dps/20/state
```
#### Basic UI sitemap
Incoming Value Transformations:
JS:tuya-energy.js
### transform/tuya-energy.js
```
(function(i) {
return Math.ceil(i / 10)
})(input)
```
Switch item=tuya_kitchen_coffeemachine_mqtt
### items/socket.items
# turn the color bulb off or on
Switch item=tuya_livingroom_colorpicker label="RGB lamp [%s]"
Switch Socket_Power "Socket" { channel="mqtt:topic:socket:power" }
Number Socket_W "Power (W)" { channel="mqtt:topic:socket:w" }
Number Socket_Vt "Power (V) [%s]" { channel="mqtt:topic:socket:v" }
# pick the color level to send to the color bulb via MQTT color Channel
Slider item=tuya_livingroom_colorpicker label="RGB lamp level [%s]" minValue=0 maxValue=100 step=1
### sitemaps/home.sitempa
# color picked and sent via MQTT Color channel
Colorpicker item=tuya_livingroom_colorpicker label="RGB lamp color [%s]" icon="colorpicker" sendFrequency=30000
```
Switch item=Socket_Power
Text item=Socket_W
Text item=Socket_Vt

116
merge-devices.js Normal file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env node
const fs = require('fs');
const json5 = require('json5');
const utils = require('./lib/utils');
// Setup Exit Handlers
process.on('exit', processExit.bind(0));
process.on('SIGINT', processExit.bind(0));
process.on('SIGTERM', processExit.bind(0));
process.on('uncaughtException', processExit.bind(1));
async function processExit(exitCode) {
if (exitCode || exitCode === 0)
console.error('Exit code: ' + exitCode)
process.exit();
}
// Main code function
const main = async() => {
const date = new Date();
const dateTimeStr = date.getFullYear() + '-' + ("0" + (date.getMonth() + 1)).slice(-2) + '-' + ("0" + date.getDate()).slice(-2) + '_' + ("0" + date.getHours()).slice(-2) + ("0" + date.getMinutes()).slice(-2) + ("0" + date.getSeconds()).slice(-2);
const configDevicesFilename = 'devices.conf';
const configDevicesBackupFilename = `${configDevicesFilename}_${dateTimeStr}.bak`;
const configNewDevicesFilename = `new-${configDevicesFilename}`;
let configDevices;
let configNewDevices;
try {
console.log(`Loading ${configNewDevicesFilename}...`);
configNewDevices = fs.readFileSync(`./${configNewDevicesFilename}`, 'utf8');
configNewDevices = json5.parse(configNewDevices);
}
catch (e) {
console.error(`Could not parse new devices config file [${configNewDevicesFilename}]!`);
console.error(e);
process.exit(1);
}
try {
console.log(`Loading ${configDevicesFilename}...`);
configDevices = fs.readFileSync(`./${configDevicesFilename}`, 'utf8');
configDevices = json5.parse(configDevices);
}
catch (e) {
console.error(`Could not parse devices config file [${configDevicesFilename}]!`);
console.error(e);
process.exit(1);
}
try {
console.log(`Backing up devices config file [${configDevicesFilename}] to [${configDevicesBackupFilename}]...`);
fs.copyFileSync(`./${configDevicesFilename}`, `./${configDevicesBackupFilename}`);
}
catch (e) {
console.error(`Could not make backup of devices config file [${configDevicesFilename}] to [${configDevicesBackupFilename}].`);
console.error(e);
process.exit(1);
}
console.log('Indexing devices...');
// Create a dictionary for faster lookups with many devices
const configDevicesDictionary = {};
for (let configDevice of configDevices) {
configDevicesDictionary[configDevice.id] = configDevice;
}
console.log('Merging devices...');
// Add new devices and update existing devices
for (let configNewDevice of configNewDevices) {
let configDevice = configDevicesDictionary[configNewDevice.id];
if (configDevice == null) {
// Add new device
console.log(`Adding device: ${configNewDevice.name} (id: ${configNewDevice.id})...`);
configDevices.push(configNewDevice);
configDevicesDictionary[configNewDevice.id] = configNewDevice;
continue;
}
if (configDevice.allowMerge === false)
continue; // No merge updates allowed for this device
// Update existing device
if (configDevice.name !== configNewDevice.name) {
console.log(`Updating device name: '${configDevice.name}' to '${configNewDevice.name}' (device id: ${configDevice.key})...`);
configDevice.name = configNewDevice.name;
}
if (configDevice.key !== configNewDevice.key) {
console.log(`Updating device key for: '${configDevice.name}' (device id: ${configDevice.key})...`);
configDevice.key = configNewDevice.key;
}
}
// Sort the devices by name
configDevices.sort((a, b) => a.name < b.name ? -1 : 1);
const configDevicesJson = json5.stringify(configDevices, { space: ' ', quote: '"' });
//console.log(configDevicesJson);
try {
console.log(`Saving devices config file [${configDevicesFilename}]...`);
fs.writeFileSync(`./${configDevicesFilename}`, configDevicesJson, { encoding: 'utf8' });
}
catch (e) {
console.error(`Could not write devices config file [${configDevicesFilename}]!`)
console.error(e)
process.exit(1)
}
console.log('Done!');
}
// Call the main code
main();

704
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"name": "tuya-mqtt",
"version": "3.0.0",
"version": "3.1.4",
"description": "Control Tuya devices locally via MQTT",
"homepage": "https://github.com/TheAgentK/tuya-mqtt#readme",
"homepage": "https://github.com/lehanspb/tuya-mqtt#readme",
"main": "tuya-mqtt.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@@ -13,17 +13,17 @@
},
"license": "ISC",
"dependencies": {
"@tuyapi/cli": "^1.13.4",
"@tuyapi/cli": "^1.15.0",
"color-convert": "^2.0.1",
"debug": "^4.1.1",
"debug": "^4.3.1",
"json5": "^2.1.3",
"mqtt": "^4.2.1",
"supports-color": "^7.2.0",
"tuyapi": "^6.0.1",
"mathjs": "7.5.1"
"mqtt": "^4.2.6",
"supports-color": "^8.1.0",
"tuyapi": "^7.5.1",
"mathjs": "8.1.1"
},
"repository": {
"type": "git",
"url": "git://github.com/TheAgentK/tuya-mqtt.git"
"url": "git://github.com/lehanspb/tuya-mqtt.git"
}
}

View File

@@ -64,8 +64,7 @@ async function republishDevices() {
debug('Resending device config/state in 30 seconds')
await utils.sleep(30)
for (let device of tuyaDevices) {
device.publishMqtt(device.baseTopic+'status', 'online')
device.init()
device.republish()
}
await utils.sleep(2)
}
@@ -166,6 +165,11 @@ const main = async() => {
const dpsKey = splitTopic[topicLength-2]
device.processDpsKeyCommand(message, dpsKey)
break;
case 6:
const subDevDpsKey = splitTopic[topicLength-2]
const cidName = splitTopic[topicLength-4]
device.processDpsKeyWcidNameCommand(message, subDevDpsKey, cidName)
break;
}
}
} catch (e) {