logo elektroda
logo elektroda
X
logo elektroda

Tuya 5.8G "Radar" presence sensor with Unknown Implementation of TuyaMCU Protocol (command

vandenelsenkoen 963 4
ADVERTISEMENT
  • #1 21356988
    vandenelsenkoen
    Level 2  
    I'm working on getting all my devices away from cloud software and towards local operation.

    One of my sensors is a 5.8G "MmWave Radar" human presence sensor, see image below:

    5.8G MmWave Radar human presence sensor with technical specifications.

    Though externally it appeared similar to the device used in this topic:

    https://www.elektroda.com/rtvforum/topic3987582.html

    It is actually completely different on the inside as can be seen in the images below. Note that in these images the TuyaMCU chip is temporarily taken off the board to provide a clearer overview.

    The image shows a circuit board of a device with an integrated circuit visible in the upper left corner and an empty space where a central chip was removed. PCB with CB3S Beken module. Top view of a sensor board with visible pin labels. 5.8G MmWave Radar sensor PCB

    The sensor uses a CB3S Beken MCU for wifi communication, which is connected to a "more sense" board which has a MCU on it labeled 58h32.

    Reading the post by p.kaczmarek2 provided me with the tools to debug and investigate the communication between the Wifi MCU and the TuyaMCU, which appears to use the TuyaMCU protocol at 9600 baud.

    The Smart Life app provides the user with a few control options:
    - Motion sensitivity, value between 0 and 10
    - Micro-Motion sensitivity, value between 0 and 10
    - Motion detection range lower limit, value between 0 and 600cm
    - Motion detection range upper limit, value between 0 and 600cm
    - Micro-Motion detection range lower limit, value between 0 and 600cm
    - Micro-Motion detection range upper limit, value between 0 and 600cm
    - "Nobody-Time" after what time of no motion/micro-motion should the sensor indicate no presence, value of 30, 60, 90 or 120

    Investigating the communication in a bidirectional way with the tool from https://www.elektroda.com/rtvforum/topic3970199.html gave the following results:

    Setting motion sensitivity:
    
    Sent by WiFi module:
    55 AA	00	06		00 08	6902000400000006	82	
    HEADER	VER=00	SetDP		LEN	dpId=105 Val V=6		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	69 02 00 04 00000006 		86	
    HEADER	VER=03	State		LEN	dpId=105 Val V=6	CHK	
    


    Setting micro motion sensitivity:
    
    Sent by WiFi module:
    55 AA	00	06		00 08	6B02000400000004	82	
    HEADER	VER=00	SetDP		LEN	dpId=107 Val V=4		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	6B 02 00 04 00000004 		86	
    HEADER	VER=03	State		LEN	dpId=107 Val V=4	CHK
    


    Setting Motion detection range, changing either the minimum or maximum value always sends both values to the TuyaMCU:
    
    Received by WiFi module:
    55 AA	03	07		00 08	6D 02 00 04 00000186 		0B	
    HEADER	VER=03	State		LEN	dpId=109 Val V=390	CHK	
    
    Sent by WiFi module:
    55 AA	00	06		00 08	6D02000400000186	07	
    HEADER	VER=00	SetDP		LEN	dpId=109 Val V=390		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	6E 02 00 04 00000000 		85	
    HEADER	VER=03	State		LEN	dpId=110 Val V=0	CHK	
    
    Sent by WiFi module:
    55 AA	00	06		00 08	6E02000400000000	81	
    HEADER	VER=00	SetDP		LEN	dpId=110 Val V=0		CHK	
    

    Where dpid 109 is the upper limit and dpid 110 is the lower limit.

    Setting Micro-Motion detection range, changing either the minimum or maximum value always sends both values to the TuyaMCU:
    
    Sent by WiFi module:
    55 AA	00	06		00 08	6F020004000000FA	7C	
    HEADER	VER=00	SetDP		LEN	dpId=111 Val V=250		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	6F 02 00 04 000000FA 		80	
    HEADER	VER=03	State		LEN	dpId=111 Val V=250	CHK	
    
    Sent by WiFi module:
    55 AA	00	06		00 08	7002000400000000	83	
    HEADER	VER=00	SetDP		LEN	dpId=112 Val V=0		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	70 02 00 04 00000000 		87	
    HEADER	VER=03	State		LEN	dpId=112 Val V=0	CHK		
    

    Where dpid 111 is the upper limit and dpid 112 is the lower limit.

    Setting Nobody Time:
    
    Sent by WiFi module:
    55 AA	00	06		00 08	680200040000003C	B7	
    HEADER	VER=00	SetDP		LEN	dpId=104 Val V=60		CHK	
    
    Received by WiFi module:
    55 AA	03	07		00 08	68 02 00 04 0000003C 		BB	
    HEADER	VER=03	State		LEN	dpId=104 Val V=60	CHK	
    


    Luminance update from TuyaMCU:
    
    Received by WiFi module:
    55 AA	03	07		00 08	67 02 00 04 00000012 		90	
    HEADER	VER=03	State		LEN	dpId=103 Val V=18	CHK	
    


    All the above seems logical and the values are matching with my settings in the smart life app. As can be seen, the Tuya MCU transmits back all data received, I'd assume this is for verification.

    However, as one may have noticed by now, this does not yet include the actual receiving of the sensor's main value: presence.
    When presence is detected or not found anymore, this is sent from the TuyaMCU to the Wifi MCU, however, this message does not seem to adhere to protocol.

    Presence detected:
    
    Received by WiFi module:
    55 AA	03	22		00 05	6504000100	93	
    HEADER	VER=03	Unk		LEN	6504000100	CHK	
    
    Sent by WiFi module:
    55 AA	00	23		00 01	01	24	
    HEADER	VER=00	Unk		LEN	01	CHK	
    


    No presence detected:
    
    Received by WiFi module:
    55 AA	03	22		00 05	6504000101	94	
    HEADER	VER=03	Unk		LEN	6504000101	CHK	
    
    Sent by WiFi module:
    55 AA	00	23		00 01	01	24	
    HEADER	VER=00	Unk		LEN	01	CHK
    


    It seems like it is a "normal" TuyaMCU DPID set command operation, with some bytes left out.
    The command #22 is not recognized by the TuyaMCU analyzer tool.
    The last value does however seem to match with the expected values of 0 and 1 (though inverted).

    Just for verification purposes, I looked up the dpid that the presence state should use in the Tuya Developer Cloud environment, which can be seen in the image below:
    Presence sensor event log showing status and communication details.
    This image shows that the "code" or dpid should be 101, which matches with the "65" in the packet sent by the TuyaMCU.

    This is where I'm currently stuck as far as trying to implement this sensor in openbeken goes, since I do not know how I can implement/work with this incorrect/unknown TuyaMCU packet.

    Perhaps there is someone who can assist me?
  • ADVERTISEMENT
  • ADVERTISEMENT
  • Helpful post
    #3 21358291
    divadiow
    Level 35  
    cool. thanks for posting the backup.

    Here's some other bits that may be a repeat of the info you've already posted

    Registers as 001HPS01_5.8G when paired with Tuya

    present in binary after pairing:

    Code: JSON
    Log in, to see the code



    dpIDs

    Code: JSON
    Log in, to see the code
  • ADVERTISEMENT
  • #4 21359011
    vandenelsenkoen
    Level 2  
    >>21358291

    Wow, very cool that you are able to get all this info from that bin file!
    It seems to mostly match the info I got from the UART communication, which is nice to know.
    Interesting to see that the "nobody time" or "presence delay" actually can be any value between 0 and 180, instead of just the values that can be selected in the smart life app (30, 60, 90, 120).

    Very interesting that dpid 101 for presence shows up as a "regular" dpid. It looks like both the original bin file and the Tuya Cloud Developer environment show it as a normal dpid then, only the uart communication didn't.

    I might try to set up the openbeken software assuming that all dpid's are regular, just to see what it does/doesn't do.
  • #5 21360585
    vandenelsenkoen
    Level 2  
    Good news!

    The presence value does seem to get picked up by the TuyaMCU driver within OpenBeken, and its value is updated correctly.

    For reference, this is the autoexec.bat file that I'm now using/testing with:

    
    startDriver TuyaMCU
    
    // always report paired
    tuyaMcu_defWiFiState 4
    // baud
    tuyaMcu_setBaudRate 9600
    
    // 101 is presence alarm
    setChannelType 1 ReadOnly
    setChannelLabel 1 Presence
    linkTuyaMCUOutputToChannel 101 bool 1
    
    // 103 is light sensor
    setChannelType 2 ReadOnly
    setChannelLabel 2 Lux
    linkTuyaMCUOutputToChannel 103 val 2 
    
    // 105 is dp_motion_sensitivity
    setChannelType 3 TextField
    setChannelLabel 3 Motion_Sensitivity 
    linkTuyaMCUOutputToChannel 105 val 3
    
    // 110 is dp_motion_range_lower
    setChannelType 4 TextField
    setChannelLabel 4 Motion_Min_Centimeters
    linkTuyaMCUOutputToChannel 110 val 4
    
    // 109 is dp_motion_range_upper
    setChannelType 5 TextField
    setChannelLabel 5 Motion_Max_Centimeters
    linkTuyaMCUOutputToChannel 109 val 5
    
    //104 is presence delay
    setChannelType 6 TextField
    setChannelLabel 6 Presence_Delay
    linkTuyaMCUOutputToChannel 104 val 6
    
    // 107 is dp_micro_sensitivity
    setChannelType 7 TextField
    setChannelLabel 7 Micro_Sensitivity 
    linkTuyaMCUOutputToChannel 107 val 7
    
    // 112 is dp_micro_range_lower
    setChannelType 8 TextField
    setChannelLabel 8 Micro_Min_Centimeters
    linkTuyaMCUOutputToChannel 112 val 8
    
    // 111 is dp_micro_range_upper
    setChannelType 9 TextField
    setChannelLabel 9 Micro_Max_Centimeters
    linkTuyaMCUOutputToChannel 111 val 9
    


    Still no clue why the dpid 101 is for some reason sent using different command types and message formatting, but the TuyaMCU driver seems to be able to handle it without any problems thus far...!

    Below two images that depict the working state of the sensor, do note that the presence value is inverted (as expected), so 0 means someone has been detected. (this can also be seen in the lower luminance value, since I placed my hand over the sensor to detect presence, resulting in a lower light value detected by the sensor)

    Screenshot showing a sensor configuration page with various adjustable settings. Screenshot of the presence sensor configuration page with channel settings.

    Added after 2 [hours] 3 [minutes]:

    Update:

    Based on this post by P.Kaczmarek2:
    topic3987582
    I updated the autoexec.bat file to now contain a button reference to a config page and implemented that config page.
    I'll add both below.

    Autoexec:
    
    startDriver TuyaMCU
    startDriver httpButtons
    
    setButtonEnabled 0 1
    setButtonLabel 0 "Open Sensor Config"
    setButtonCommand 0 "*/api/lfs/cfg.html"
    setButtonColor 0 "#FF0000"
    
    // always report paired
    tuyaMcu_defWiFiState 4
    // baud
    tuyaMcu_setBaudRate 9600
    
    // 101 is presence alarm
    setChannelType 1 Motion_n
    linkTuyaMCUOutputToChannel 101 bool 1
    
    // 103 is light sensor
    setChannelType 2 Illuminance
    linkTuyaMCUOutputToChannel 103 val 2 
    
    // 105 is dp_motion_sensitivity
    setChannelType 3 TextField
    setChannelLabel 3 Motion_Sensitivity 
    linkTuyaMCUOutputToChannel 105 val 3
    
    // 110 is dp_motion_range_lower
    setChannelType 4 TextField
    setChannelLabel 4 Motion_Min_Centimeters
    linkTuyaMCUOutputToChannel 110 val 4
    
    // 109 is dp_motion_range_upper
    setChannelType 5 TextField
    setChannelLabel 5 Motion_Max_Centimeters
    linkTuyaMCUOutputToChannel 109 val 5
    
    //104 is presence delay
    setChannelType 6 TextField
    setChannelLabel 6 Presence_Delay
    linkTuyaMCUOutputToChannel 104 val 6
    
    // 107 is dp_micro_sensitivity
    setChannelType 7 TextField
    setChannelLabel 7 Micro_Sensitivity 
    linkTuyaMCUOutputToChannel 107 val 7
    
    // 112 is dp_micro_range_lower
    setChannelType 8 TextField
    setChannelLabel 8 Micro_Min_Centimeters
    linkTuyaMCUOutputToChannel 112 val 8
    
    // 111 is dp_micro_range_upper
    setChannelType 9 TextField
    setChannelLabel 9 Micro_Max_Centimeters
    linkTuyaMCUOutputToChannel 111 val 9
    


    Configuration page html (cfg.html):
    
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Radar Control</title>
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" />
    </head>
    
    <body>
        <div class="container mt-5 text-center">
            <h1 class="mb-3">Presence Sensor Controller</h1>
            <div class="row">
                <div class="col-md-4 offset-md-4">
                    <div class="card">
                        <div class="card-body">
                            <h2 class="card-title mb-4">Sensor Results</h2>
                            <div class="mt-5">
                                <label for="presence">Presence: <strong id="presence">?</strong></label>
                            </div>
                            <div class="mt-3">
                                <label for="lux">Illuminance: <strong id="lux">?</strong></label>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
    
            <div class="row">
                <div class="col-md-4 offset-md-4">
                    <div class="card">
                        <div class="card-body">
                            <h2 class="card-title mb-4">Configure Sensor</h2>
                            <div class="mt-5">
                                <p>
                                    <label for="motion-sensitivity">Motion Sensitivity (0-10):</label>
                                </p>
                                <input type="range" class="form-range" id="motion-sensitivity" min="0" max="10" value="5"
                                    step="1" list="sensitivity-values" onchange="onChange_Motion_Sens(this.value)">
                            </div>
                            <div class="mt-3">
                                <p>
                                    <label for="motion-min-distance">Motion Minimum Distance (0-600):</label>
                                </p>
                                <input type="range" class="form-range" id="motion-min-distance" min="0" max="600" value="0"
                                    step="10" list="distance-values" onchange="onChange_Motion_Min(this.value)">
                            </div>
                            <div class="mt-3">
                                <p>
                                    <label for="motion-max-distance">Motion Maximum Distance (0-600):</label>
                                </p>
                                <input type="range" class="form-range" id="motion-max-distance" min="0" max="600" value="30"
                                    step="10" list="distance-values" onchange="onChange_Motion_Max(this.value)">
                            </div>
                            <div class="mt-5">
                                <p>
                                    <label for="micro-sensitivity">Micro Sensitivity (0-10):</label>
                                </p>
                                <input type="range" class="form-range" id="micro-sensitivity" min="0" max="10" value="5"
                                    step="1" list="sensitivity-values" onchange="onChange_Micro_Sens(this.value)">
                            </div>
                            <div class="mt-3">
                                <p>
                                    <label for="micro-min-distance">Micro Minimum Distance (0-600):</label>
                                </p>
                                <input type="range" class="form-range" id="micro-min-distance" min="0" max="600" value="0"
                                    step="10" list="distance-values" onchange="onChange_Micro_Min(this.value)">
                            </div>
                            <div class="mt-3">
                                <p>
                                    <label for="micro-max-distance">Micro Maximum Distance (0-600):</label>
                                </p>
                                <input type="range" class="form-range" id="micro-max-distance" min="0" max="600" value="30"
                                    step="10" list="distance-values" onchange="onChange_Micro_Max(this.value)">
                            </div>
                            <div class="mt-5">
                                <p>
                                    <label for="presence-delay">Presence Delay (0-180):</label>
                                </p>
                                <input type="range" class="form-range" id="presence-delay" min="0" max="180" value="10"
                                    step="1" onchange="onChange_Pres_Delay(this.value)">
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="container mt-4 text-center">
            <a href="/">Back to Main Page</a>
        </div>
    
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
        <script>
            // http://192.168.xxx.xxx/api/lfs/cfg.html
            let baseURL = "";
            let channel_presence = 1;
            let channel_lux = 2;
            let channel_motion_dist_min = 4;
            let channel_motion_dist_max = 5;
            let channel_motion_sens = 3;
            let channel_micro_dist_min = 8;
            let channel_micro_dist_max = 9;
            let channel_micro_sens = 7;
            let channel_presence_delay = 6;
    
            // Function to update the motion minimum distance value
            function onChange_Motion_Min(val) {
                if (val > document.getElementById('motion-max-distance').value) {
                    document.getElementById('motion-max-distance').value = val;
                    setChannel(channel_motion_dist_max, val);
                }
                setChannel(channel_motion_dist_min, val);
            }
    
            // Function to update the motion maximum distance value
            function onChange_Motion_Max(val) {
                if (val < document.getElementById('motion-min-distance').value) {
                    document.getElementById('motion-min-distance').value = val;
                    setChannel(channel_motion_dist_min, val);
                }
                setChannel(channel_motion_dist_max, val);
            }
    
            // Function to update the motion sensitivity value
            function onChange_Motion_Sens(val) {
                setChannel(channel_motion_sens, val);
            }
    
            // Function to update the micro minimum distance value
            function onChange_Micro_Min(val) {
                if (val > document.getElementById('micro-max-distance').value) {
                    document.getElementById('micro-max-distance').value = val;
                    setChannel(channel_micro_dist_max, val);
                }
                setChannel(channel_micro_dist_min, val);
            }
    
            // Function to update the micro maximum distance value
            function onChange_Micro_Max(val) {
                if (val < document.getElementById('micro-min-distance').value) {
                    document.getElementById('micro-min-distance').value = val;
                    setChannel(channel_micro_dist_min, val);
                }
                setChannel(channel_micro_dist_max, val);
            }
    
            // Function to update the micro sensitivity value
            function onChange_Micro_Sens(val) {
                setChannel(channel_micro_sens, val);
            }
    
            // Function to update the presence delay value
            function onChange_Pres_Delay(val) {
                setChannel(channel_presence_delay, val);
            }
    
            // Function to set a channel value through a REST API call
            function setChannel(ch, val) {
                console.log("Sending new value to OBK API, changes channel " + ch + " to " + val);
                fetch(baseURL + '/cm?cmnd=SetChannel ' + ch + ' ' + val, {
                    method: 'GET'
                })
                    .then(response => {
                        if (response.ok) {
                            response.text().then(data => {
                                console.log("OK");
                                // Handle successful response
                            });
                        } else {
                            throw new Error('Network response was not ok');
                        }
                    })
                    .catch(error => {
                        // Handle fetch error or error during response processing
                        console.error('Error:', error);
                    });
            }
    
            // Function to retrieve channel values from the REST API
            function getChannels() {
                fetch(baseURL + '/cm?cmnd=Ch', {
                    method: 'GET'
                })
                    .then(response => {
                        response.json().then(data => {
                            console.log(data);
                            const motion_dist_min = data['Ch' + channel_motion_dist_min];
                            const motion_dist_max = data['Ch' + channel_motion_dist_max];
                            const motion_sens = data['Ch' + channel_motion_sens];
                            const micro_dist_min = data['Ch' + channel_micro_dist_min];
                            const micro_dist_max = data['Ch' + channel_micro_dist_max];
                            const micro_sens = data['Ch' + channel_micro_sens];
                            const presence_delay = data['Ch' + channel_presence_delay];
                            console.log("Cur values received correctly!");
                            document.getElementById('motion-min-distance').value = motion_dist_min;
                            document.getElementById('motion-max-distance').value = motion_dist_max;
                            document.getElementById('motion-sensitivity').value = motion_sens;
                            document.getElementById('micro-min-distance').value = micro_dist_min;
                            document.getElementById('micro-max-distance').value = micro_dist_max;
                            document.getElementById('micro-sensitivity').value = micro_sens;
                            document.getElementById('presence-delay').value = presence_delay;
                            const presence = data['Ch' + channel_presence];
                            const lux = data['Ch' + channel_lux];
                            document.getElementById('presence').innerText = presence;
                            document.getElementById('lux').innerText = lux;
                        });
                    });
            }
    
            // Start the countdown timer when the page loads
            window.onload = function () {
                getChannels();
                setInterval(getChannels, 1000);
            };
        </script>
    </body>
    
    <datalist id="sensitivity-values">
        <option value="0" label="0"></option>
        <option value="2" label="2"></option>
        <option value="4" label="4"></option>
        <option value="6" label="6"></option>
        <option value="8" label="8"></option>
        <option value="10" label="10"></option>
    </datalist>
    
    <datalist id="distance-values">
        <option value="0" label="0"></option>
        <option value="100" label="100"></option>
        <option value="200" label="200"></option>
        <option value="300" label="300"></option>
        <option value="400" label="400"></option>
        <option value="500" label="500"></option>
        <option value="600" label="600"></option>
    </datalist>
    
    </html>
    


    This all results in the configuration page that can be seen in the image below:
    Presence sensor control panel with results and configuration options
    Visually it could definitely be improved, but functionally it is working properly.
ADVERTISEMENT