Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intermittant "905 - Network Device Unreachable" Errors #496

Closed
horatio-g opened this issue May 10, 2024 · 15 comments
Closed

Intermittant "905 - Network Device Unreachable" Errors #496

horatio-g opened this issue May 10, 2024 · 15 comments
Labels
documentation Improvements or additions to documentation tuya_device Support for specific Tuya Devices

Comments

@horatio-g
Copy link

Hi,

Wonder if you can help me with a problem I'm having with my EARU EAWCPT-J 63A (Tuya) Switch. This is used to measure power consumption of my ASHP system. I have installed tinytuya on a RPI system, to intercept the output from the switch and populate a database. I am using a python script to do so. I followed your comprehensive guide to install tinytuya and obtain my local key

I fine tuned the python script to ignore the frequent dps request replies that did not contain the data I required (Power and Voltage). Initially, for about 2 days, the script and tinytuya worked fine.

However, I then became aware that I was getting continuous "'905' Network Error - Device Unreachable" errors in the log. A ping to the Tuya switch's local IP also yielded no response. The Tuya switch is set up as a Static IP on the router. I have also set the lease expiry to 21 days on the network table. Eventually, I found that a simple power cycle on the Tuya switch would allow it to be immediately "be seen" again AND my script would start receiving data again.

I thought that this was maybe a "one-off glitch", but about a day later, the same thing occured. The frequency of this disconnect seems to be increasing, so that I now get about 1 or 2 hours of data before the 905 error. Funnily enough, even when this error is occuring, the Tuya Smart App and the Tuya console can still "see" and "update" the switch. (I am only using the app/portal AFTER the error occurs, so it seems that it is NOT me accessing the app/portal that is provoking these errors.)

Here is the python script (with my key and other sensitive info redacted as "****" ):

import datetime, time, tinytuya, mysql.connector

d = tinytuya.OutletDevice('bfd6923b26129897d0ejzv', '192.168.1.133', '********')
d.set_version(3.3)
d.set_socketPersistent(True)

mydb = mysql.connector.connect(
        host="****",
        user="****",
        password="****",
        database="*****"
)

while True:
        payload = d.generate_payload(tinytuya.HEART_BEAT)
        d.send(payload)

        payload = d.generate_payload(tinytuya.UPDATEDPS)
        d.send(payload)
        data = d.status()
        print('data: %s' % (data))

        if 'dps' in data.keys() and '19' in data['dps'].keys() and '20' in data['dps'].keys():
                power = round(data['dps']['19']/10,1)
                voltage = round(data['dps']['20'] / 10,1)
                timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
                print('Values: Timestamp = %s, Power = %d, Voltage = %d' % (timestamp, power,voltage))
                cursor = mydb.cursor()
                cursor.execute("REPLACE INTO reading (DateTime,Sensor,Rate,Watts) VALUES (%s,2,%s,%s)", (timestamp,voltage,power))
                mydb.commit()
                time.sleep(15)
        else:
                print('Invalid Data - Skipping')

and a section of the output log where this error starts:

Values: Timestamp = 2024-05-10 14:11:06, Power = 153, Voltage = 246
data: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715349041}
Invalid Data - Skipping
data: {'dps': {'1': True, '9': 0, '18': 864, '19': 1539, '20': 2462, '21': 2, '22': 0, '23': 0, '24': 0, '25': 0, '26': 0, '38': 'memory', '40': 'relay', '41': False, '42': ''}}
Values: Timestamp = 2024-05-10 14:11:21, Power = 153, Voltage = 246
data: {'dps': {'1': True, '9': 0, '18': 864, '19': 1539, '20': 2462, '21': 2, '22': 0, '23': 0, '24': 0, '25': 0, '26': 0, '38': 'memory', '40': 'relay', '41': False, '42': ''}}
Values: Timestamp = 2024-05-10 14:11:36, Power = 153, Voltage = 246
data: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715349041}
Invalid Data - Skipping
data: {'dps': {'20': 2479, '18': 867, '19': 1554}, 't': 1715349055}
Values: Timestamp = 2024-05-10 14:11:51, Power = 155, Voltage = 247
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}

Also, here is the output from "python -m tinytuya -nocolor -debug" (similarly redacted) :

DEBUG:TinyTuya [1.13.2]

DEBUG:Python 3.7.3 (default, Apr  3 2019, 05:39:12)
[GCC 8.2.0] on linux
DEBUG:Using PyCrypto 2.6.1.final.0 for crypto
DEBUG:Warning: Crypto library does not support AES-GCM, v3.5 devices will not work!
DEBUG:loaded=devices.json [1 devices]

TinyTuya (Tuya device scanner) [1.13.2]

[Loaded devices.json - 1 devices]

Scanning on UDP ports 6666 and 6667 and 7000 for devices for 18 seconds...

DEBUG:Listening for Tuya devices on UDP ports 6666, 6667 and 7000
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk*******', 'version': '3.3'}
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk******', 'version': '3.3'}
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk*****', 'version': '3.3'}
ASHP Breaker   Product ID = keyuh3jxk******j  [Valid Broadcast]:
    Address = 192.168.1.133   Device ID = bfd69******d0ejzv (len:22)  Local Key = ******  Version = 3.3  Type = default, MAC = d8:d6******3:68
    Polling 192.168.1.133 Failed:
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk******', 'version': '3.3'}
Scan completed in 18.0895 seconds

Scan Complete!  Found 1 devices.
Broadcasted: 1
Versions: 3.3: 1

>> Saving device snapshot data to snapshot.json

DEBUG:Scan complete with 1 devices found

I think the essential issue there is where it reads "Polling 192.168.1.133 Failed:"?
Trying to ping the switch's IP address yields the following:

ping -w 10 192.168.1.133
PING 192.168.1.133 (192.168.1.133) 56(84) bytes of data.

--- 192.168.1.133 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 381ms

As soon as I power cycle the Tuya switch (from the consumer board), the switch immediately reconnects again:

ping -w 10 192.168.1.133
PING 192.168.1.133 (192.168.1.133) 56(84) bytes of data.
64 bytes from 192.168.1.133: icmp_seq=1 ttl=255 time=45.5 ms
64 bytes from 192.168.1.133: icmp_seq=2 ttl=255 time=66.8 ms
64 bytes from 192.168.1.133: icmp_seq=3 ttl=255 time=89.4 ms
64 bytes from 192.168.1.133: icmp_seq=4 ttl=255 time=111 ms
64 bytes from 192.168.1.133: icmp_seq=5 ttl=255 time=27.8 ms
64 bytes from 192.168.1.133: icmp_seq=6 ttl=255 time=51.2 ms
64 bytes from 192.168.1.133: icmp_seq=7 ttl=255 time=74.5 ms
64 bytes from 192.168.1.133: icmp_seq=8 ttl=255 time=5.19 ms
64 bytes from 192.168.1.133: icmp_seq=9 ttl=255 time=13.4 ms
64 bytes from 192.168.1.133: icmp_seq=10 ttl=255 time=4.17 ms

--- 192.168.1.133 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 23ms
rtt min/avg/max/mdev = 4.171/48.951/111.491/34.874 ms

and

DEBUG:TinyTuya [1.13.2]

DEBUG:Python 3.7.3 (default, Apr  3 2019, 05:39:12)
[GCC 8.2.0] on linux
DEBUG:Using PyCrypto 2.6.1.final.0 for crypto
DEBUG:Warning: Crypto library does not support AES-GCM, v3.5 devices will not work!
DEBUG:loaded=devices.json [1 devices]

TinyTuya (Tuya device scanner) [1.13.2]

[Loaded devices.json - 1 devices]

Scanning on UDP ports 6666 and 6667 and 7000 for devices for 18 seconds...

DEBUG:Listening for Tuya devices on UDP ports 6666, 6667 and 7000
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk9******', 'version': '3.3'}
DEBUG:final payload_dict for 'bfd6923b26129897d0ejzv' ('v3.3'/'default'): {1: {'command': {'gwId': '', 'devId': '', 'uid': '', 't': ''}}, 7: {'command': {'devId': '', 'uid': '', 't': ''}}, 8: {'command': {'gwId': '', 'devId': ''}}, 9: {'command': {'gwId': '', 'devId': ''}}, 10: {'command': {'gwId': '', 'devId': '', 'uid': '', 't': ''}}, 13: {'command': {'devId': '', 'uid': '', 't': ''}}, 16: {'command': {'devId': '', 'uid': '', 't': ''}}, 18: {'command': {'dpId': [18, 19, 20]}}, 64: {'command': {'reqType': '', 'data': {}}}}
DEBUG:building command 10 payload=b'{"gwId":"bfd6923b26129897d0ejzv","devId":"bfd6923b26129897d0ejzv","uid":"bfd6923b26129897d0ejzv","t":"1715354020"}'
DEBUG:payload encrypted=b'000055aa000000010000000a000000888c6dd53c750cb0d2ce6881d2c8d5be13facbc82ac8e787d76052d2af07d3e6e9fe1e97e9ba4056cb07a715edfa2f5bbb162cc7acc8c0976ea36ec75fcc5e5a3b3025e82ae91f354b21be8559aa2c98eefacbc82ac8e787d76052d2af07d3e6e9a47d43c7383203c8ea40722c32674e154fce7932004d1f6a6d1eec626ba5b377229280600000aa55'
DEBUG:PollDevice: raw unpacked message = TuyaMessage(seqno=1, cmd=10, retcode=0, payload=b'\xa9\xf2O*^\x8e\x02\xe2\xc0\xb9g\xd6\x81\xa1\x0bohS^\xf5\xa4\x7f\xfd\xfb\x8d\x06\xae\x9b\xf3-\xba$\x80v,V\xf8\xa9\xb2\xddp\xb4\x05\xb2\xdb^!C\x0b9<;\xf4\xfe\x9d\x99e\x0b\xc7\xeeA\x0c\xa4ccl\xc9j\x1d\xe6c!i\xa2\xd2>\x14\x16\x0e\xb9\x7f\xde\xd7|\x84\xd7\xcb\xc8|@\x92j\xc9Fy\xc9\x16\x8b6\xc5\x1a\x18Z\x07\xa8\xbb=\x1b.\\\xc8%\xd6\xa9e\x93\x8f\xf47\xf1\xf4#\xdeG\xf7\x0e\x0c\xc8\xf2\x96\xeb\xcas\xe5\xa6\xa6\x88\x08\xaf\x17\xa5\xb8|\xb8', crc=4273021407, crc_good=True, prefix=21930, iv=None)
DEBUG:decode payload=b'\xa9\xf2O*^\x8e\x02\xe2\xc0\xb9g\xd6\x81\xa1\x0bohS^\xf5\xa4\x7f\xfd\xfb\x8d\x06\xae\x9b\xf3-\xba$\x80v,V\xf8\xa9\xb2\xddp\xb4\x05\xb2\xdb^!C\x0b9<;\xf4\xfe\x9d\x99e\x0b\xc7\xeeA\x0c\xa4ccl\xc9j\x1d\xe6c!i\xa2\xd2>\x14\x16\x0e\xb9\x7f\xde\xd7|\x84\xd7\xcb\xc8|@\x92j\xc9Fy\xc9\x16\x8b6\xc5\x1a\x18Z\x07\xa8\xbb=\x1b.\\\xc8%\xd6\xa9e\x93\x8f\xf47\xf1\xf4#\xdeG\xf7\x0e\x0c\xc8\xf2\x96\xeb\xcas\xe5\xa6\xa6\x88\x08\xaf\x17\xa5\xb8|\xb8'
DEBUG:decrypting=b'\xa9\xf2O*^\x8e\x02\xe2\xc0\xb9g\xd6\x81\xa1\x0bohS^\xf5\xa4\x7f\xfd\xfb\x8d\x06\xae\x9b\xf3-\xba$\x80v,V\xf8\xa9\xb2\xddp\xb4\x05\xb2\xdb^!C\x0b9<;\xf4\xfe\x9d\x99e\x0b\xc7\xeeA\x0c\xa4ccl\xc9j\x1d\xe6c!i\xa2\xd2>\x14\x16\x0e\xb9\x7f\xde\xd7|\x84\xd7\xcb\xc8|@\x92j\xc9Fy\xc9\x16\x8b6\xc5\x1a\x18Z\x07\xa8\xbb=\x1b.\\\xc8%\xd6\xa9e\x93\x8f\xf47\xf1\xf4#\xdeG\xf7\x0e\x0c\xc8\xf2\x96\xeb\xcas\xe5\xa6\xa6\x88\x08\xaf\x17\xa5\xb8|\xb8'
DEBUG:decrypted 3.x payload='{"dps":{"1":true,"9":0,"18":223,"19":78,"20":2464,"21":2,"22":0,"23":0,"24":0,"25":0,"26":0,"38":"memory","40":"relay","41":false,"42":""}}'
DEBUG:payload type = <class 'str'>
DEBUG:decoded results='{"dps":{"1":true,"9":0,"18":223,"19":78,"20":2464,"21":2,"22":0,"23":0,"24":0,"25":0,"26":0,"38":"memory","40":"relay","41":false,"42":""}}'
ASHP Breaker   Product ID = keyuh3jxk9******  [Valid Broadcast]:
    Address = 192.168.1.133   Device ID = bfd6923b26129897d0ejzv (len:22)  Local Key = ******  Version = 3.3  Type = default, MAC = d8:d******3:68
    Status: {'1': True, '9': 0, '18': 223, '19': 78, '20': 2464, '21': 2, '22': 0, '23': 0, '24': 0, '25': 0, '26': 0, '38': 'memory', '40': 'relay', '41': False, '42': ''}
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk9******', 'version': '3.3'}
DEBUG:Received valid UDP packet: {'ip': '192.168.1.133', 'gwId': 'bfd6923b26129897d0ejzv', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyuh3jxk9******', 'version': '3.3'}
Scan completed in 18.0903 seconds

Scan Complete!  Found 1 devices.
Broadcasted: 1
Versions: 3.3: 1

>> Saving device snapshot data to snapshot.json

DEBUG:Scan complete with 1 devices found

and

data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
Invalid Data - Skipping
data: {'dps': {'1': True, '9': 0, '18': 0, '19': 0, '20': 0, '21': 2, '22': 0, '23': 0, '24': 0, '25': 0, '26': 0, '38': 'memory', '40': 'relay', '41': False, '42': ''}}
Values: Timestamp = 2024-05-10 15:12:26, Power = 0, Voltage = 0
data: {'dps': {'20': 2463, '18': 241, '19': 144}, 't': 8}
Values: Timestamp = 2024-05-10 15:12:41, Power = 14, Voltage = 246
data: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 8}
Invalid Data - Skipping
data: {'dps': {'1': True, '9': 0, '18': 241, '19': 144, '20': 2463, '21': 2, '22': 0, '23': 0, '24': 0, '25': 0, '26': 0, '38': 'memory', '40': 'relay', '41': False, '42': ''}}
Values: Timestamp = 2024-05-10 15:12:56, Power = 14, Voltage = 246
data: {'dps': {'18': 239, '19': 130}, 't': 1715353961}
Invalid Data - Skipping
data: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715353961}
Invalid Data - Skipping
data: {'dps': {'18': 242, '19': 149}, 't': 1715353970}
Invalid Data - Skipping
data: {'dps': {'20': 2466, '18': 245, '19': 146}, 't': 1715353975}
Values: Timestamp = 2024-05-10 15:13:11, Power = 14, Voltage = 246
data: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715353975}
Invalid Data - Skipping

I know that "power cycling" is a workaround BUT I cannot do this away from the house; plus I am trying to get away from "manual intervention" but do require continuous data for my purposes. (A S-COP calculation.) Apologies for my amateur stab at a write-up on this issue but its my first foray into Home Automation. Have you any suggestions, ideas, etc as to why this is happening? If you require any info, please let me know and I'll endeavour to provide it.

Many Thanks & Regards,
Harry.

@jasonacox jasonacox added the tuya_device Support for specific Tuya Devices label May 11, 2024
@jasonacox
Copy link
Owner

jasonacox commented May 11, 2024

HI @horatio-g - I'm sad that your first foray into home automation is so problematic. But good job on troubleshooting!

the Tuya Smart App and the Tuya console can still "see" and "update" the switch

Tuya devices are designed to be cloud first. The local API we are using for TinyTuya local control is a secondary API that the SmartLife app uses for setup and local control if available. However, if it can't access the local API it will go to the cloud (assuming you are not on your local network). That's why it will always see the device.

I've had a few devices similar to yours and still have an old 3.1 version one that does the same. I wish we knew what cause them to become unstable. It may be due to a firmware memory leak that slowly eats the resources until it crashes. Anyway, I have at least one suggestion for you.

Your monitor script which I assume you did get from the README, is not intended for long term use. It sends way too many status and heartbeat pings, way more than is needed. If your device has a firmware bug, it could be tripping that resource limit fast. Instead, you should have the heartbeat only sent after a time limit. You could use something from the example here: https://github.com/jasonacox/tinytuya/blob/master/examples/monitor.py

STATUS_TIMER = 30
KEEPALIVE_TIMER = 12

heartbeat_time = time.time() + KEEPALIVE_TIMER
status_time =  None

# Uncomment if you want the monitor to constantly request status - otherwise you
# will only get updates when state changes
#status_time = time.time() + STATUS_TIMER

while(True):
    if status_time and time.time() >= status_time:
        # poll for status
        print(" > Send Request for Status < ")
        data = d.status()
        status_time = time.time() + STATUS_TIMER
        heartbeat_time = time.time() + KEEPALIVE_TIMER
    elif time.time() >= heartbeat_time:
        # send a keep-alive
        data = d.heartbeat(nowait=False)
        heartbeat_time = time.time() + KEEPALIVE_TIMER
    else:
        # no need to send anything, just listen for an asynchronous update
        data = d.receive()

    print('Received Payload: %r' % data)

    if data and 'Err' in data:
        print("Received error!")
        # rate limit retries so we don't hammer the device
        time.sleep(5)

I would also check to make sure your device doesn't have a firmware upgrade available.

(TODO: update README with better code)

@jasonacox jasonacox added the documentation Improvements or additions to documentation label May 11, 2024
@horatio-g
Copy link
Author

Hi Jason,

Many thanks for your reply with useful explanations and things to try. Ah! the old "memory leak" problem surfaces again - I remember back to my old DP days, as a junior programmer, in the late 80s, having loads of fun with those! Who would have thought, 40 odd years later and I would be hitting the same issue, this time in firmware!

I have re-written my monitor script based (loosely) on the example you provided in your reply above, It seems my switch will not yield any data without the full d.updatedps(['19','20'], nowait=True) being supplied first. On a side note, I keep getting the following being returned also:

``Received Payload: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715428608}`

The ratio of this type of response to the data that I am after is roughly (but not exactly) 50:50. (Mostly, the response seems to alternate between the above and a "valid" dataset. With above "key=49" type responses, the integer "t" seems to vary but the '49' data string remains constant (AQAAAAMAAAAEAAAA') Reading around this subject on the web, I found this key has something to do with "Alarm Set 2". Regardless, I have made the script check for this type of response and just cycle the d.receive() command until a "non-49" dataset is received. This seems to work. (Touch wood!)

With regard to the HEARTBEAT and STATUS functionality, I was finding it was giving me unreliable results and, tbh, the logic was tying me in knots! I thought I would try with the functionality for both removed. So, effectively, all my script does now is check the d.status() at the beginning of the run (BTW: the "version" key does report back that my switch is 3.3 compliant - if that what this means?) Then the script goes into the while loop and sends a d.updatedps() followed by one or more d.receive() commands until I get a non '49 key' response. (Either valid data or "something else"!) It then waits 15 seconds before the next cycle. I would really like to be getting 4 datasets per minute but, if necessary I can drop this down to once a minute, if the memory leak continues to cause problems.

At the moment the script has been running as a service for most of the day without the switch becoming unreachable. However, if I still am encountering problems, I was just wondering about the capabilities of tinytuya being able to instigate a "soft reset/reboot" on the switch? This may release the memory leak and give me another day+ of processing before I need to do it again. As I say, I would want to try and automate this workaround, if at all possible, as manually cycling the switch is not practical in my case.

WRT to your question about switch's firmware version, I used the SmartTuya app and found it to be 1.0.5 (if that makes sense?). Unfortunately, no updates are available:

Screenshot_20240511_173958_Tuya Smart

I am also a bit lairy about using the app or Tuya portal at the same time as my monitor script is running as I read (somewhere in your documentation or somewhere else?) that the Tuya kit will only allow one TCP connection at a time? And (I may be assuming incorrectly here), that the remote connection has precedence? However, it must be said that my accessing the app and/or portal do not seem to co-incide with any "upsets" in the script's processing.

Anyway, thanks for your time and effort - I hope I am now making progress :)

Regards,
Harry.

@jasonacox
Copy link
Owner

Received Payload: {'dps': {'49': 'AQAAAAMAAAAEAAAA'}, 't': 1715428608}

It sounds like you figured that out, but this is expected. We don't know the logic on the frequency, but the Tuya devices will send updates for data points (DPS), usually as they change, but sometimes on some random basis or cadence.

It seems my switch will not yield any data without the full d.updatedps(['19','20'], nowait=True)

Yes, that happens with some switches. I have 2 like that. My only advice there is to try to ensure the frequency of that updatedps() call isn't too high. These Tuya devices have small processors and, how should I say, sometimes questionable code, that doesn't handle a lot of load well. If you buy another switch from another manufacturer, you will have a different experience. I confess I had some that behaved like your and ended up trashing them and finding another. I'm not suggesting you can't make it work, but it is sometimes not worth the effort. 😉

I looked up your switch. Is this it here (link)? It occurs to me that these are likely installed inside metal boxers (breaker box?) and WiFi access could be a challenge. Since this is TCP, you wouldn't see that manifest as garbage, but it could translate to the Tuya switch having to do a lot of TCP retries, again adding to its load or exposing any resource leak. You might do some network sniffing to see if anything like that is happening and if so, see if you can adjust WiFi signal for less retry.

Good luck! Please keep us updated on what you discover and if you find a good solution!

@horatio-g
Copy link
Author

horatio-g commented May 11, 2024

Hi Jason,

Thanks again for your quick and helpful response.

Re that link to the switch; its VERY similar but mine is the EAWCPT-J and the pic in that link shows EAWCBT-J - not sure what the difference is :) As you can see from the price, one of the constraints of my project is "much cheapness!" ANd my Scottish-ness rebels at "trashing" anything until all hope is lost!

Yes - you are right - the switch is in a metal box. But its right next to the router. I'll defo have a look into "sniffing the network" and see if there are multiple re-tries going on. (Yet another skill to acquire ... what's that about "old dogs?")

Any feedback on my previously posed question as to whether there's any way I can use tinytuya to "re-boot" my switch?

So far, as of now, the script, which I kicked off this morning, is still happily trundling along - so a big improvement already! (My earlier version of the script was encountering problems less than 2 hours after re-booting the switch.)

Thanks again.

Regards,
Harry.

@uzlonewolf
Copy link
Collaborator

Unfortunately there is no way to reboot a device, either from the cloud or via the local API. I've spent many hours combing through the official documentation and have even reverse engineered a couple firmware images and it just doesn't support it. I've inadvertently caused reboots by sending IR blasters bad data causing them to lock up until the watchdog fires, but that's been it.

@jasonacox
Copy link
Owner

that link to the switch; its VERY similar but mine is the EAWCPT-J and the pic in that link shows EAWCBT-J

I did find a breakdown of the EARU EAWCPT-J here: https://www.elektroda.com/rtvforum/topic4018747.html
HA had a report of this device but seems like a cloud issue: home-assistant/core#102769

So far, as of now, the script, which I kicked off this morning, is still happily trundling along

Awesome! Would you be willing to share your code? If anyone else runs across this device or one like it, it may be helpful.

...

reverse engineered a couple firmware images and it just doesn't support it

Of course you did! @uzlonewolf you are amazing. ❤️

@horatio-g
Copy link
Author

horatio-g commented May 12, 2024

Script still running as a service, some 24+ hours later - looking promising!

Current incarnation of script as follows (Keys and other sensitive info redacted with "***"):

#
# Python script that uses tinytuya to extract data (Power & Voltage) from a Earu EAWCPT-J (Tuya) Smart Circuit Breaker and
# insert those values into a table ("reading") in a MySQL database
#
# Based on scripts by Jason Cox: https://github.com/jasonacox/tinytuya

import datetime, time, tinytuya, mysql.connector

# tinytuya.set_debug(True)

#
# Setting the address to 'Auto' or None will trigger a scan which will auto-detect both the address and version, but this can take up to 8 seconds
# d = tinytuya.OutletDevice('DEVICEID', 'Auto', 'DEVICEKEY', persist=True)
# If you know both the address and version then supplying them is a lot quicker
# d = tinytuya.OutletDevice('DEVICEID', 'DEVICEIP', 'DEVICEKEY', version=DEVICEVERSION, persist=True)

d = tinytuya.OutletDevice('bfd6923b26129897d0ejzv', '192.168.1.133', '********', version=3.3, persist=True)

mydb = mysql.connector.connect(
        host="********",
        user="********",
        password="********",
        database="*********"
)

print("\n\nStarting import_ashp.py -  Sending Request for Tuya Switch Status...")
data = d.status()
print('Version: %r, Initial Status: %r' % (d.version, data))

if data and 'Err' in data:
        print("Status request returned an error, is version %r and local key %r correct?" % (d.version, d.local_key))

print("\nStarting ASHP Power Data Retreival Loop")
while(True):
        print("Send DPS Update & Receive Data...")

        # payload = d.generate_payload(tinytuya.UPDATEDPS)
        payload = d.updatedps(['19','20'], nowait=True)
        d.send(payload)

        # Ignore spurious key = '49' data - just keep receiving data until data is what we want or otherwise invalid
        while data is not None and 'dps' in data.keys() and '49' in data['dps'].keys():
                data = d.receive()

        print('Received Payload: %s' % (data))

        if data is not None and 'dps' in data.keys() and '19' in data['dps'].keys() and '20' in data['dps'].keys():
                power = round(data['dps']['19']/10,1)
                voltage = round(data['dps']['20'] / 10,1)
                timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
                print('Values: Timestamp = %s, Power = %d, Voltage = %d' % (timestamp, power,voltage))
                cursor = mydb.cursor()
                cursor.execute("REPLACE INTO reading (DateTime,Sensor,Rate,Watts) VALUES (%s,2,%s,%s)", (timestamp,voltage,power))
                mydb.commit()
                time.sleep(15)

        else:
                print('Invalid Data - Skipping')
                time.sleep(5)

@jasonacox
Copy link
Owner

Thanks @horatio-g ! Looking at the logic here (I updated your post so it formats as python) in comparison to the first code snip you shared, it seems like the difference is related to the less aggressive approach. The first sends both the heartbeat and updatedps payloads and effectively doesn't wait, just pounds the device with those until you get the 19 & 20 DPS you want. The second only sends updatedps and patiently waits for the right response. In any case, I suspect the more gentle approach is making all the difference.

I hope this fixes your problem long term. Keep us updated.

@horatio-g
Copy link
Author

Hi Jason

I spoke to soon! The script was still running as at 30+ hours, but I wasn't paying too much attention to the data it was retreiving and inserting into my SQL table. However, this evening I was testing out some MySQL procedure trickery to generate views for "pretty graphs", from my ASHP data, when I noticed that my ASHP power demand had been remarkably static for the whole day ... in fact, far too static! Further investigation revealed that tinytuya was returning data via the data.receive() call BUT, it was returning the SAME dataset for the last 30 hours - Doh!

I've tried swapping in d.generate_payload(tinytuya.UPDATEDPS) for d.updatedps(['19','20'], nowait=True) but still no luck!

I've got to get ready for (real) work tomorrow, so will have to leave this for now, but I thought I had better let you (and other folk) know that this not (yet) the correct solution!

Will get back on it as soon as I can.
Regards,
Harry.

@jasonacox
Copy link
Owner

jasonacox commented May 12, 2024

You may need to put a status() request back in there. And you may need to put the heartbeat back in.

I would write the logic so that you are also draining the response before sending anything else. Something like:

flip = True
while(True):
    print("Send DPS Update & Receive Data...")
    flip = not flip

    if flip:
        payload = d.generate_payload(tinytuya.HEART_BEAT)
    else:
        payload = d.generate_payload(tinytuya.UPDATEDPS)
        
    d.send(payload, nowait=True)

    # Ignore spurious key = '49' data - just keep receiving data until data is what we want or otherwise invalid
    while data is not None and 'dps' in data.keys() and '49' in data['dps'].keys():
        data = d.receive()

@uzlonewolf
Copy link
Collaborator

There's no need for a heartbeat in this case. As long as you're sending something at least every ~20 seconds or so it's not needed, it's only used to keep an idle connection open.

@uzlonewolf
Copy link
Collaborator

uzlonewolf commented May 13, 2024

        payload = d.updatedps(['19','20'], nowait=True)
        d.send(payload)

updatedps() calls send() internally (hence the nowait arg), and when given nowait=True it is going to return None. This won't hurt anything as calling .send(None) will just immediately return None without sending anything, but this is probably not what you intended.

The issue you're having now is because you are not calling data = d.receive() in the main while loop - once data is valid .receive() is never called again. Instead, you can just use nowait=False to make updatedps() get it for you like:

        data = d.updatedps(['19','20'], nowait=False)

        # Ignore spurious key = '49' data - just keep receiving data until data is what we want or otherwise invalid
        while data is not None and 'dps' in data.keys() and '49' in data['dps'].keys():
               data = d.receive()

@horatio-g
Copy link
Author

Many thanks uzlonewolf & jason

Under the cosh with my dayjob a bit at the moment. But had another look at this issue this morning: its not so good news re the amendment I made, as described, last above; I got another memory leak hang in less than 24 hours. But I have implemented uzlonewolf's ideas and fired teh script off again (as a service) to see if that improves things. Thanks also to uzlonewolf for the detail on the updatedps() clause and itsnowait qualifier - their behaviour now makes a lot more sense to me :)

I'll let you know how it goes with the revised version of the script and, if successful, will re-publish here. Regards, Harry.

@horatio-g
Copy link
Author

Hiya uzlonewolf & jason,

Just to finish off this topic and express my thanks.

Here we are, some 2 weeks later, and the latest incarnation of the script continues to run without crashing. I have also added in an MQTT publish into the script, so, as well as populating my local dtatabase with the required information, I can now also pass it on to my Home Assistant implementation, running on a remote server.

Here is the final script, if anybody needs it in the future. Rathe rthan redacting senstive info this time, I have replaced it with an indication of what is required at that point encased in angle brackets (<>)

#
# Python script that uses tinytuya to extract data (Power & Voltage) from a Earu EAWCPT-J (Tuya) Smart Circuit Breaker and
# insert those values into a table ("reading") in a MySQL database
#
# Based on scripts by Jason Cox: https://github.com/jasonacox/tinytuya. Thanks also to "uzlonewolf" for explaining how some of these modules work.
#

import datetime, time, tinytuya, mysql.connector
import paho.mqtt.publish as publish
import json

host = "pi"

# tinytuya.set_debug(True)

# Setting the address to 'Auto' or None will trigger a scan which will auto-detect both the address and version, but this can take up to 8 seconds
# d = tinytuya.OutletDevice('DEVICEID', 'Auto', 'DEVICEKEY', persist=True)
# If you know both the address and version then supplying them is a lot quicker
# d = tinytuya.OutletDevice('DEVICEID', 'DEVICEIP', 'DEVICEKEY', version=DEVICEVERSION, persist=True)
d = tinytuya.OutletDevice('<deviceid>', '<local ip address>', '<tuya local key>', version=3.3, persist=True)

mydb = mysql.connector.connect(
        host="<host>",
        user="<user>",
        password="<password>",
        database="<datatbase>"
)

print("\n\nStarting import_ashp.py -  Sending Request for Tuya Switch Status...")
data = d.status()
print('Version: %r, Initial Status: %r' % (d.version, data))

if data and 'Err' in data:
        print("Status request returned an error, is version %r and local key %r correct?" % (d.version, d.local_key))

print("\nStarting ASHP Power Data Retreival Loop")
while(True):
        print("Polling For Data...")


        # d.updatedps(['19','20'], nowait=True)
        # payload = d.generate_payload(tinytuya.UPDATEDPS)
        # data = d.send(payload)
        #       data = d.receive()

        data = d.updatedps(['19','20'], nowait=False)

        # Ignore spurious key = '49' data - just keep receiving data until data is what we want or otherwise invalid
        while data is not None and 'dps' in data.keys() and '49' in data['dps'].keys():
                data = d.receive()

        print('Received Payload: %s' % (data))

        if 'dps' in data.keys() and '19' in data['dps'].keys() and '20' in data['dps'].keys():
                power = round(data['dps']['19']/10,1)
                voltage = round(data['dps']['20'] / 10,1)
                timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
                print('Values: Timestamp = %s, Power = %d, Voltage = %d' % (timestamp, power,voltage))
                cursor = mydb.cursor()
                cursor.execute("REPLACE INTO reading (DateTime,Sensor,Rate,Watts) VALUES (%s,2,%s,%s)", (timestamp,voltage,power))
                mydb.commit()
                MQTT_MSG=json.dumps({"voltage":voltage,"power":power});

                if __name__ == '__main__':
                        publish.single(topic="energy/ashp", payload=MQTT_MSG, hostname=host)

                time.sleep(15)

        else:
                print('Invalid Data - Skipping')
                time.sleep(5)

Thanks again,
Regards, Harry.

@jasonacox
Copy link
Owner

Thanks @horatio-g !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation tuya_device Support for specific Tuya Devices
Projects
None yet
Development

No branches or pull requests

3 participants