Skip to content

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

Marco Sillano edited this page Dec 24, 2023 · 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 is 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 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.

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.
  • 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).

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
  • 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
}}

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...
}}

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
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": "SKIP",
                    "name": "smoke_sensor_state"   // user name, used by TuyaDAEMON interfaces, here is the `code` 
                },
                {
                    "dp": 2,
                    "capability": "SKIP",
                    "typefield": "BYTESMALLFLOAT",  // to make the value human-readable
                    "name": "smoke_sensor_value"
                },
                {
                    "dp": 11,
                    "capability": "SKIP",
                    "name": "fault"
                },
                {
                    "dp": 14,
                    "capability": "SKIP",
                    "name": "battery_state"
                },
                {
                    "dp": 15,
                    "capability": "SKIP",
                    "name": "battery_percentage"
                },
                {
                    "dp": 16,
                    "capability": "SKIP",
                    "name": "muffling"
                },
                {
                    "dp": 101,
                    "capability": "SKIP",
                    "name": "test"
                }
            ]
        },

The "capability": "SKIP" must be used, 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 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.
  • 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 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.
  • The use of core_TRIGGER + Tuya smart scenes (automation and tap-to-run) must 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 'tap-to-run'.
  • The use of core_OPENAPI must be very limited, only in truly necessary cases:
    • 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 'instruction' (i.e. it is writable), but it is NOT in SmartLife 'tap-to-run' 'actions' (TRIGGER not available).
    • 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).

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:

  • In the log, the code muffling is called Silence and exposes 2 values: OFF|Open Mute.
  • _'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' response (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 set 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 tuyastatus.
  1. solution: In the 'trigge_devices' flow, the incoming trigger produces only a message for the 'smoke alarm.DPtrigger' property. The 'base properties' logging and 'more actions' are then implemented via share (more verbose, but puts all logic in global.alldevices 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 'DPtrigger' is not required (more simple, the logic is wired in the 'mirror_devices' flow).

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

These Tuya_triggers are generated by Tuya_DAEMON to fire a 'tap-to-run' 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 (opt) status - - - API SCHEMA updates status (optional)

notes:

  • 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 by default 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 equivalent to tuya-smart-device node, but that reads the values from global.tuyastatus.
For GETs is implemented 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 ]          // OpenAPI response as an object
[ "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