Skip to content

55. Howto: add a new device since v. 2.2.4

Marco Sillano edited this page May 20, 2024 · 59 revisions

Since version 2.2.4 Tuya_DAEMON introduces access to Tuya OpenAPI, which changes the way new devices and 'mirror devices' are managed. This article explores the new possibilities, using a 'smoke alarm device', WiFi battery powered, as an example.

  • The goal is always to identify, document, and make accessible via TuyaDEAMON all the functions of a new device.
  • The study of a new Tuya device and the design phase of a 'mirror' device are both greatly facilitated by the use of core_OPENAPI and the 'Tuya IoT Development Platform'.
  • Mirror devices are Tuya devices that are not supported MQTT by the tuya-smart-device node. However, they are still accessible through core_TRIGGER and core_OPENAPI, TuyaDAEMON optional extensions.
  • core_OPENAPI allows you to access Tuya Cloud directly, but to do so, you must add the device to the real section of global.alldevices. This is also for 'mirror' devices using core_OPENAPI, no longer in the 'fake' section.

note: This document is focused on TuyaDAEMON, but addresses general features of Tuya Cloud: so this information may also be useful in other contexts. In particular, I developed a standalone node-red flow for testing with OpenAPI without TuyaDAEMON.

Step 0 (preconditions)

  • TuyaDAEMON is installed and configured with the core_OPENAPI and (for 'mirror' devices) core_TRIGGER extensions.
  • Optionally, you can install and configure the tuyadaemon.toolkit for additional features.
  • Alternative without TuyaDAEMON: install nr_Tuya_OpenAPI_2.0 on node-red.
  • The new device is present in the 'SmartLife' app and is working correctly.
  • You can access the Tuya IoT Platform and see the new device listed in the 'Cloud/Open Project/Device' section ( or in the 'Debug Devices' dropdown list). Here is a good guide.

note: The following steps replace 5..8 Steps in 50. Howto: add a new device to tuyaDAEMON when core_OPENAPI is available.

Step 1 (info & DP data collection)

  • Find the deviceID of the new device

    • in SmartLife, device info
    • using Tuya IoT Platform
    • using tuyapi-cli wizard
    • The last best and fastest solution is to use TuyaUIweb modified to display the id and key of each device in the tooltips.
  • Create this simple flow (e.g. in 'mirror_devices' flow), with two nodes: an input node and a link out node (to core.std_cmds), to call some Tuya APIs for testing.

  • API 'Query Device Details'

Set in the input node a msg.payload like this (JSON), to call this API:

           {
            "device": "_openapi",
            "property": "_callAPI",
            "value": {
                 "method": "GET",
                 "APIurl": "/v2.0/cloud/thing/<your_deviceID>",
                 "body": ""
                 }
           }

On the debug pad you must have: RESULT: "[RX: openapi/_callAPI", object]", and, opening it:

  payload: {
    success: true
    result: {
        active_time: 1692989353
        category: "kg"              // manufacturer defined 
        create_time: 1613403089
        custom_name: "tuya_bridge"  // in  SmartLife
        icon: "smart/device_icon/eu1580902146346G23Gy/bf8c4fd0c03067079cplb1461381627983590.png"
        id: "1234567xx"             // same as in the call, required by tuya-smart-device (Device Virtual ID)
        local_key: "12345kk"        // required by tuya-smart-device (Device key)
        ip: "12.23.34.45"           // your public network IP address
        is_online: true
        product_id: "123456pp"      // Tuya unique label for each independent product. 
        model: "1CH"                // manufacturer defined
        name: "1CH 2"               // (brand) + (product) + (module model), manufacturer defined
        product_name: "1CH"         // manufacturer defined
        sub: false                  // is subdevice?
        lat: "41.9"
        lon: "12.4"
        time_zone: "+02:00"
        update_time: 1693051814
        uuid: "987654321"           // Tuya internal use
}}
  • Alternative: use the flow 'Get Device Details V2.0'

Copy and paste the data in a notepad, you can need it later.

  • API 'Send Properties'

Set in the input node a msg.payload like this (JSON), to call the API:

    {
    "device": "_openapi",
    "property": "_callAPI",
    "value": {
        "method": "GET",
        "APIurl": "/v2.0/cloud/thing/<your_deviceID>/shadow/properties",
        "body": ""
    }
}

On the debug pad you must have: RESULT: "[RX: openapi/_callAPI", object]", and, opening it:

  payload: {
    success: true
    result: {
       properties: array[8]        // This device is with 8 DPs (codes)
       0:  {
           code: "switch_1"        // used by Tuya APIs
           custom_name: "Free"
           dp_id: 1                // used by TuyaDAEMON  
           time: 1702399814901
           value: true             //  actual DP's value
           }
       1: object
       2: object
       ...more...
}}

  • Alternative: Use the flow 'GET device SCHEMA V2.0'.

Expand all, then copy and paste the data into a notepad.

Now we group all information about DPs in a table: DP, and code are from the API result; standard, status (sta), instruction (ins), and values are from 'Tuya IoT Platform'; condition (con) and action (act) from 'SmartLife' Automation definition pages.

DP code standard sta ins con act values notes&behavior
1 smoke_sensor_state smoke_sensor_status yes - yes - 1..2 ? => "Smoke alarm", "normal" code <> standard, More codes?
2 smoke_sensor_value smoke_sensor_value yes - yes - 0.0..10.0 [ppm] pushed only on "Smoke alarm" change
11 fault -- - - - - 0..? err code?
14 battery_state battery_state yes - yes - "low", "middle", "high" by Tuya Cloud, never by device
15 battery_percentage battery_percentage yes - yes - 0..100 [%] pushed on any alarm state change
16 muffling muffling yes yes yes yes true/false SET only during alarm, returns to false at alarm end. Also HW
101 test -- - - - - true/false 'Test alarm', only HW

Step 2: Using 'pseudoDP' with the device

The minimal global.alldevices - in CORE_devices flow, *Global CORE config node - update is (if you like, you can start to use tuyadaemon_toolkit to do that), in 'real' section:

        {
            "id": "<your_deviceID>",
            "name": "smoke detector",               // user name, used by TuyaDAEMON interfaces, any  
            "dps": [
                {
                    "dp": 1,
                    "capability": "TRG",
                    "name": "smoke_sensor_state"   // user name, used by TuyaDAEMON interfaces, here is the `code` 
                },
                {
                    "dp": 2,
                    "capability": "TRG",
                    "typefield": "BYTESMALLFLOAT",  // to make the value human-readable
                    "name": "smoke_sensor_value"
                },
                {
                    "dp": 11,
                    "capability": "TRG",
                    "name": "fault"
                },
                {
                    "dp": 14,
                    "capability": "TRG",
                    "name": "battery_state"
                },
                {
                    "dp": 15,
                    "capability": "TRG",
                    "name": "battery_percentage"
                },
                {
                    "dp": 16,
                    "capability": "TRG",
                    "name": "muffling"
                },
                {
                    "dp": 101,
                    "capability": "TRG",
                    "name": "test"
                }
            ]
        },

The "capability": "TRG" is used here, since this mirror device does not have the smart-tuya-device node. The "BYTESMALLFLOAT" decode function divides by ten the value: see CORE_devices flow, *ENCODE/DECODE user library node.

Now you are enabled to use core_OPENAPI pseudoDP (_APIstatus, and _APIinstruction): You must set the following command in the test input node, equivalent to GET SCHEMA:

          {
           "device": "smoke detector",
           "property": "_APIstatus",
           "value": "any"
          }

and all DPs will be updated (in global.tuyastatus) with the returned values.

Or you can do the following command, equivalent to SET (also MULTIPLE):

          {
           "device": "smoke detector",
           "property": "_APIinstruction",
           "value": {
                 "properties": {
                      "test": true
                      }
                 }
          }

In the 'Tuya device log' this is reported as:

    2023-12-15 10:22:21 | Publish | 手动测试报警 (Manual test alarm) | on | unknown

but without any visible effect on the device: i.e. SET 'test' is not available via Tuya API 'send_properties'.

Step 3: device behavior

The definition and value of all DPs are not enough to use the device with confidence. Further investigations are necessary to identify behavior and quirks in DP use. The tools available are:

  1. SmartLife commands on the user interface and in automation, and the resulting effects.
  2. The tuya log (max one week) in 'Tuya IoT Platform' - 'debug device', which shows all messages to/from a device.
  3. Test sequences of actions can be performed with the device directly, with SmartLife, or using TuyaDEAMON, checking the results in the device's log and status.

Tuya_DAEMON 'smoke detector' behavior

This is the information I found for the "smoke detector" device and its DPs (codes), also summarized in the 'notes & behavior' column:

  • The alarm sound can be triggered by 2 different causes:
    • 'Test': only starts with the HW button; the alarm lasts 5 seconds
    • 'Smoke': starts at the ON-level (9.0 ppm?) and ends at the OFF-level (1.0 ppm?)
  • The code 'smoke_sensor_state'(RO) is 1 only for a 'Smoke alarm', 2 for normal
  • The code 'test' (RO) is true only for a 'Test alarm'.
  • The code 'fault' (RO) is 0 (code for no error?) (? untested).
  • The code 'smoke_sensor_value' is pushed by the device at any smoke alarm state change.
  • The code 'battery_percentage' is pushed by the device at any alarm state change (so 'test' can be used to GET the battery status).
  • I found differences between values of battery_percentage at the start (e.g. 93%) and the end (79%) of an alarm. (note. 0% = low battery value, e.g. 7.0 V, 100% = hight battery value, e.g. 9.7 V, defined in FW by producer).
  • The code 'battery_state' is NEVER pushed by the device; maybe it is calculated by Tuya Cloud at any read.
  • The code 'muffling' (silence) is writable by _APIinstruction only if the alarm is ON and auto returns to false at the alarm end.

Step 4: mirror device design

general criteria

  • The general objective is a minimal but complete implementation: all the functionality of a device must be reachable ASAP, with a minimum of custom code.
  • The use of 'Tuya IoT Platform' is only cognitive, without changing anything: the default for Smartlife is the standard instruction set mode, and this will not be modified.
  • In the case of mirror devices, keep in mind the advantages and limitations of the available tools:
    • The use of core_TRIGGER + Tuya smart scenes (automation) can cover the majority of cases:
      • available only for DPs present in the 'condition' or the 'action' columns
      • signal a device event, not numerical (condition value = xx)
      • signal a numerical event (condition value > | = | < xx )
      • start an action or SET a value (action value = xx), using a automation.
    • The use of core_OPENAPI must be very limited, only in truly necessary cases, to keep your dependence on Tuya Cloud low:
      • it is executed by tuya_DAEMON, in polling mode: therefore you cannot use it as a signal for asynchronous device events.
      • it is essential for GETting numerical codes.
      • it is useful when a code is in 'status' (i.e. it is readable), but it is NOT in SmartLife 'automation' 'conditions' (TRIGGER not available).
      • it is useful when a code is in 'instruction' (i.e. it is writable), but it is NOT in SmartLife 'tap-to-run' 'actions' (TRIGGER not available).

note: it is clear that OpenAPI cannot, on its own, satisfy all needs, lacking a simple event-driven mechanism.

Tuya_DAEMON 'smoke detector' design

Considering the limits highlighted in the DP and the behavior of the device, the minimum functionalities for tuya_DAEMON are:

  • we use native DPs (1..101) as 'base properties', updated via API or via 'trigger'.

Status (GET): Input triggers (from Tuya Cloud)

These Tuya_triggers are generated by automation fired when some conditions are met. As a result, the 'native DPs' are updated ASAP by the device, in an event-driven strategy.

DPtrigger name condition logging more actions
30030 TRGalarmON 'smoke status' = 'smoke alarm' DP_1 => 1 -
30100 TRGalarmOFF 'smoke status' = 'smoke alarm release' - API SCHEMA updates status
30170 TRGbattLOW 'battery state' = 'low' DP_14 => 'low' -

notes:

  • _'TRGalarmOFF' uses 'APIstatus' to refresh all native DPs on the occasion of an alarmOFF event.
  • Only the 'TRGbattLOW' trigger is useful to update the 'battery state' ASAP, i.e. after a 'test alarm' (after a 'smoke alarm' the refresh is done by 'TRGalarmOFF')
  • In the 'online' property (see API 'Query Device Details') of this device I always find 'true', even if I have disconnected the battery for more than 48 hours! The msg 'online' is sent by the device at startup and never reset. I would say, as a first solution, to always define 'connected' as true also in TuyaDAEMON.
  • Since this device is always online, it is appropriate for the startup to use the 'API SCHEMA updates status' to initialize the data in the tuyastatus.
  1. solution: In the 'trigge_devices' flow, the incoming trigger produces only a message for the 'smoke alarm.DPtrigger' properties. The 'base properties' logging and 'more actions' are then implemented via share (more verbose, but puts all logic in the global.alldevices structure under strict user control)
  2. solution In the 'trigge_devices' flow, the incoming trigger generates, using a custom node-red flow, the 'base properties' logging and 'more actions' required; the 'DPtriggers' are not required (more simple, the logic is wired in the 'mirror_devices' flow, preferred).

Instruction (SET): Output triggers (to Tuya Cloud)

These Tuya_triggers are generated by Tuya_DAEMON to fire an 'automation' in Tuya Cloud or to do some more action. Implemented as 'SKIP' extra DPs, are accessible by the user as SET.

DP name trigger automation action more
_silenceTRG silence 50050 SMOKE50050 silence = ON trigger=0
_statsTRG (optional) status - - - API SCHEMA updates status

notes:

  • In the log, the code muffling is called Silence and exposes 2 values: OFF|Open Mute.
  • The capability to do an 'API SCHEMA updates status' under user control is useful, e.g. after a manual 'Test', to refresh the 'battery state'. Since this feature is always available using the _APIstatus pseudoDP, a dedicated DP (_statsTRG) is optional.

solution: standard: the Trigger or the required action is implemented via share (no alternatives: '_silenceTRG' DP and share are mandatory).


implementation

This implementation of the "smoke detector" mirror device is minimal: the device exports only a SET for _silenceTRG and no GETs: the data is updated in the tuyastatus structure automatically and ASAP. For the APPs, nothing changes: the updates of all the DPs are disseminated via MQTT, while all data can be read via REST.
If for some reason you wanted to implement GET() for all DPs (I don't see the reason, but you never know) it's simple: you need a subflow that is a replacement for tuya-smart-device node, but that reads the values from global.tuyastatus (see as a model the 'interface' node in the CORE flow).
Now for TRIGGERs, GETs are implemented as solution 2, and for SETs, solution 1.

Some fragments about the "smoke detector" mirror device:

a) Automation required in Tuya Cloud (you use SmartLife to build them):

SMOKE30030: If "smoke":alarm:on,  Then "tuya_bridge":countdown_1:30030

SMOKE30100: If "smoke":alarm:off, Then "tuya_bridge":countdown_1:30100

SMOKE30170: If "smoke":battery:low,  Then "tuya_bridge":countdown_1:30170

SMOKE50050: If "tuya_bridge":countdown_1 = 50050,  Then "tuya_bridge":countdown_1:0 + "smoke":silence:ON

b) Share added in global.alldevices, 'smoke detector': to call the SMOKE50050 automation via TRIGGER:

                {
                    "dp": "_silenceTRG",
                    "capability": "SKIP",
                    "share": [
                        {
                            "action": [
                                {
                                    "device": "tuya_bridge",
                                    "property": "_trigger",
                                    "value": "50050"
                                }
                            ]
                        }
                    ]
                }

c) This is the implementation of a "smoke detector" mirror device in the 'mirror_devices' flow. This implementation is quite simple: there is only one function node, tuyastatus startup for mirror_smoke, but it is stereotyped, always similar to itself. The others are nodes or subflows with parameters to set. The three triggers, coming from the device, are selected by a selector, and then 3 sub-flows prepare the data for logging or to launch an API request. ACKs are handled automatically by core_TRIGGER. The trigger to the device is generated directly by a share.


Now we can implement the various parts and test the device. The tests in the mirror_devices flow simulate the trigger reception or send a SET silence ON. Expected results (in the debug pad):

  • test Alarm ON
 [ "RX: smoke detector/smoke_senso…", "1" ]   // the updated value in DP 1
 [ "RX: tuya_bridge/_trigger", 0 ]            // ACK from TuyaDEAMON, clears the trigger
  • test Alarm OFF
[ "TX SET: device smoke detector/…", "any" ]  // the request _APIstatus
[ "RX: tuya_bridge/_trigger", 0 ]             // ACK from TuyaDEAMON, , clears the trigger
[ "RX: openapi/_APIstatus", object ]          // _APIstatus response as an object, plus:
[ "RX: smoke detector/smoke_senso…", "2" ]    // the updated value in DP 1
[ "RX: smoke detector/smoke_senso…", 0.2 ]    // the updated value in DP 2
[ "RX: smoke detector/fault", 0 ]             // the updated value in DP 11
... more ...
  • test Battery Low
[ "RX: smoke detector/battery_sta…", "low" ]  // The updated value in DP 14
[ "RX: tuya_bridge/_trigger", 0 ]             // ACK from TuyaDEAMON, clears the trigger
  • set_silence ON
[ "RX: smoke detector/_silenceTRG", "ON" ]    // Local response to SET
[ "RX: tuya_bridge/_trigger", 50050 ]         // Trigger sent to Tuya
[ "RX: tuya_bridge/_trigger", 0 ]             // ACK from Tuya Cloud, verifying automation execution

note:

  • This is the full echo on the debug pad. Users can hide unnecessary messages.
  • If you use tuyadaemon.toolkit, don't forget to update the database and produce the updated 'datasheet' for your devices. If you also use tuyadaemon.thing, use it to quickly create the required 'shares' and to keep global.alldevices up to date.

Conclusions

The use of 'core_OPENAPI' is optional, but, as can be seen in the example reported here, it brings substantial benefits both in the analysis and in the implementation of devices for Tuya_DAEMON. The strategy used is conservative, with limited access to Tuya Cloud, but this completes the range of tools available to Tuya_DAEMON users.

It is interesting to compare this version of the 'smoke detector' device with the previous version, without the use of 'core_OPENAPI'.

Clone this wiki locally