jasonacox / tinytuya

Python API for Tuya WiFi smart devices using a direct local area network (LAN) connection or the cloud (TuyaCloud API).
MIT License
939 stars 167 forks source link

Help with device #94

Open Electrik-rich546456 opened 2 years ago

Electrik-rich546456 commented 2 years ago

Hi I need a bit of help with a new thermostat device. I cannot get any dps data from it as it seems to not have an ip address according to the debug info

DEBUG:socket unable to connect - retry 1/5
DEBUG:socket unable to connect - retry 2/5
DEBUG:socket unable to connect - retry 3/5
DEBUG:socket unable to connect - retry 4/5
DEBUG:socket unable to connect - retry 5/5
DEBUG:ERROR Network Error: Device Unreachable - 905 - payload: null

or

Status {'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}

I've tried with device22 option.

I think this device is faulty as even the smart life app cannot do a "check device network"

I can see its mac address on smart life app and have found it on my router. the 'tuya-raw.json' file reports data like ...

 "model": "RSH-TH03",
            "name": "Thermostat",
            "online": true,
            "owner_id": "secret",
            "product_id": "secret",
            "product_name": "\secret",
            "status": [
                {
                    "code": "va_temperature",
                    "value": 203
                },
                {
                    "code": "va_humidity",
                    "value": 49
                },
                {
                    "code": "battery_percentage",
                    "value": 100
                }

Any ways as always I look forward to fixing it :-) P.s Happy holidays @jasonacox

jasonacox commented 2 years ago

Hi @Electrik-rich546456 ! Happy Holidays to you too... I have enjoyed some time off which has afforded me some time to invest in TinyTuya. I added more TuyaCloud API functions which may actually help. It won't find the local IP but it will help us discover the DPS ID numbers that you can use to control the device.

import tinytuya

# Turn on Debug Mode (optional)
# tinytuya.set_debug(True)

# Connect to Tuya Cloud
# c = tinytuya.Cloud() # This will use the tinytuya.json file created by the wizard or you can specify:
c = tinytuya.Cloud(
        apiRegion="us", 
        apiKey="xxxxxxxxxxxxxxxxxxxx", 
        apiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 
        apiDeviceID="xxxxxxxxxxxxxxxxxxID")

# Display list of devices
devices = c.getdevices()
print("Device List: %r" % devices)

# Select a Device ID to Test
id = "xxxxxxxxxxxxxxxxxxID"

# Display Properties of Device
result = c.getproperties(id)
print("Properties of device:\n", result)

# Display Functions of Device
result = c.getfunctions(id)
print("Functions of device:\n", result)

# Display DPS IDs of Device
result = c.getdps(id)
print("DPS IDs of device:\n", result)

# Display Status of Device
result = c.getstatus(id)
print("Status of device:\n", result)

# Send Command - This example assumes a basic switch
commands = {
    'commands': [{
        'code': 'switch_1',
        'value': True
    }, {
        'code': 'countdown_1',
        'value': 0
    }]
}
print("Sending command...")
result = c.sendcommand(id,commands)
print("Results:\n", result)
Electrik-rich546456 commented 2 years ago

Hi there I tried the code using c = tinytuya.Cloud() on ln 9 but got this error.

Traceback (most recent call last):
  File "./thermostat.py", line 9, in <module>
    c = tinytuya.Cloud() # This will use the tinytuya.json file created by the wizard or you can specify:
AttributeError: module 'tinytuya' has no attribute 'Cloud'
Electrik-rich546456 commented 2 years ago

Oh dear my bad how is the pip3 install version different to the github version ? When does it get updated ?

Electrik-rich546456 commented 2 years ago

This is the output of my thermostat.

Properties of device:
 {'result': {'category': 'wsdcg', 'status': [{'code': 'va_temperature', 'name': '当前温度', 'type': 'Integer', 'values': '{"unit":"℃","min":-200,"max":600,"scale":1,"step":1}'}, {'code': 'va_humidity', 'name': '湿度数值', 'type': 'Integer', 'values': '{"unit":"%","min":0,"max":100,"scale":0,"step":1}'}, {'code': 'battery_percentage', 'name': '电池电量', 'type': 'Integer', 'values': '{"unit":"%","min":0,"max":100,"scale":0,"step":1}'}]}, 'success': True, 't': 1640959507201}
Functions of device:
 {'result': {'category': 'wsdcg', 'functions': []}, 'success': True, 't': 1640959507334}
DPS IDs of device:
 {'result': {'category': 'wsdcg', 'functions': [], 'status': [{'code': 'va_temperature', 'dp_id': 1, 'type': 'Integer', 'values': '{"unit":"℃","min":-200,"max":600,"scale":1,"step":1}'}, {'code': 'va_humidity', 'dp_id': 2, 'type': 'Integer', 'values': '{"unit":"%","min":0,"max":100,"scale":0,"step":1}'}, {'code': 'battery_percentage', 'dp_id': 4, 'type': 'Integer', 'values': '{"unit":"%","min":0,"max":100,"scale":0,"step":1}'}]}, 'success': True, 't': 1640959507466}
Status of device:
 {'result': [{'code': 'va_temperature', 'value': 231}, {'code': 'va_humidity', 'value': 56}, {'code': 'battery_percentage', 'value': 100}], 'success': True, 't': 1640959507649}

How do just get the temperature as a human readable data (eg 22 `C ) ?

Also seeing as we are getting the info via the tuya api, Can we make the api request data from device?

It has one button on it that seems to have two functions short press makes the thing update the api with current values. Long press resets it so the smartlife app needs to re find it.

I got myself this unit as I thought it would let me get data from it via serial but all i got was "S2" and S3" as an output, tried lots of different ways of connecting to it to the extent I thought I had broken it at one point but its still working It doesn't seem to want to update itself when i start the smart life app so was thinking of flashing something else like 'tasmota' but have no idea as cant find any data about the hardware and also wanted to find a way to take a backup of existing firmware just in case i broke it but have not had any success.

jasonacox commented 2 years ago

Hi @Electrik-rich546456, you are right, pip updates can take a while. I find python3 -m pip install --upgrade tinytuya works best to pull latest but technically pip3 should do the same.

Human Readable Data?

Here is what you would do to process the data into a human readable form (based on the units and scale listed in your output):

# Grab Status of Device
response = c.getstatus(id)

# Parse Response
status = {}
for item in response['result']:
    status[item['code']] = item['value']

# Print Values
print("Temperature = %0.2f °C" % (status['va_temperature']/10))
print("Humidity = %d %%" % status['va_humidity'])
print("Battery = %d %%" % status['battery_percentage'])

Force Update via API?

The function list that you pulled back from TuyaCloud indicates that it doesn't have any command functions we can call to update it:

{'result': {'category': 'wsdcg', 'functions': []}, 'success': True, 't': 1640959507334}

Here is what I believe is happening: Most battery powered Tuya devices do not stay online. They "wake up" every so often, connect to the WiFi and update the TuyaCloud with their data. Your "button press" probably forces this. This is also why you do not see the device on the network when you do a python3 -m tinytuya scan to find it or when you try to connect to it directly using an IP address. It is asleep most of the time so that it saves the battery life. If you managed to force it to stay online, it would likely drain the battery in a matter of hours. Your best path would be to use the above method to get the data that the device delivers to the cloud.

Flash Device to Tasmota?

That's an interesting option. I have never done this with any device, but I suspect it will be much harder to do with a battery powered device for the reasons I stated above. If you do manage to get to work, you will want an external power source for it or you will drain the batteries quickly.

Electrik-rich546456 commented 2 years ago

Happy new year @jasonacox in advance :-)

I made these changes to your original code


#!/usr/bin/env python3
import time

import tinytuya

# Turn on Debug Mode (optional)
# tinytuya.set_debug(True)

# Connect to Tuya Cloud
c = tinytuya.Cloud() # This will use the tinytuya.json file created by the wizard or you can specify:

# Display list of devices
#devices = c.getdevices()
#print("Device List: %r" % devices)

# Select a Device ID to Test
id = "bf2c558dce75d7c817yz9y"

## Display Properties of Device
#result = c.getproperties(id)
#print("Properties of device:\n", result)
#
## Display Functions of Device
#result = c.getfunctions(id)
#print("Functions of device:\n", result)

# Display DPS IDs of Device
#result = c.getdps(id)
#
#print("DPS IDs of device:\n", result)
#i = 1
while True:

    # Display Status of Device
    result = c.getstatus(id)
    #print(result["result"])
    for item in result["result"]:
    #    print(item)
        if item["code"] == 'va_temperature':
            #unit = (item['value']/10)
            unit = (str(item['value']))
            print("tempature" , unit)
            if unit >= '200':
                print("hot")
            elif unit <= '190':
                print("cold")
    time.sleep(120)
#    i += 1

it works nicely my output is this so i thought but then it did this error


tempature 208
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 203
hot
tempature 198
tempature 198
tempature 198
Traceback (most recent call last):
  File "./thermostat.py", line 42, in <module>
    for item in result["result"]:
KeyError: 'result'

I wonder how i can add your suggestion into my code ? But it may be updating enough in the loop but I think the error i got was due to the api kicking me off.

I was also looking at different ttinyuya functions that could possibly be of use like tinytuya.HEART_BEAT or tinytuya.UPDATEDPS what do they do can they help ? :-)

jasonacox commented 2 years ago

The example code I gave doesn't have any error handling. Can you add this and try again?

tinytuya.set_debug(True)

Without seeing the debug info, I don't know what the Cloud API sent back, but there is the possibility that it will sometimes deliver a bad response (the devices do this too) or it may indicate that the token has expired. You can also add some basic error handling:

while True:
    try:
        result = c.getstatus(id)
        if "result" in result:
            for item in result["result"]:
                if item["code"] == 'va_temperature':
                    unit = (item['value']/10)  # tuya stores temps as an integer (10x the real value)
                    print("temperature: %0.1f" % unit)
                    if unit >= 20.0:
                        print("hot")
                    elif unit <= 19.0:
                        print("cold")
    except:
        print("error - ignoring")

    time.sleep(120)

The tinytuya.HEART_BEAT and tinytuya.UPDATEDPS commands are for local control (you would need the device to be on WiFi and pingable on your local network). Even if it was available via the Cloud API, your device would need to stay on your WiFi full time which would drain the battery in less than a day which is usually the reason it disconnects and only updates the cloud periodically.

Electrik-rich546456 commented 2 years ago

Happy new year !!:-)@ I've updated the code and it seems to be running nicely. How do add the snapshot name function so cant useturn_on() device name

Is there a way to work out how often the Cloud API gets updated from local device without the button being pressed. Maybe a timer to lo look for device update ?

jasonacox commented 2 years ago

Do you know what IP address it uses when it comes online to send the update? If so, set up a constant ping and see how often it responds. If you don't have the IP, you can look at your router (if it has the ability to show DHCP assignments) or you could run python3 -m tinytuya scan and push the button to see if it reports in. ;)

Happy 2022!! :)

Electrik-rich546456 commented 2 years ago

Hi, Yes I found the IP address by cross referencing with the mac address found on smartlife app. I made this to time when its online.

#!/usr/bin/env python3
import datetime
import time

import os
hostname = "192.168.1.153" #example
#response = os.system("arping -c 1 " + hostname)

while True:
    exit_code = os.system(f"arping -c 1 {hostname} > /dev/null 2>&1")
    if exit_code == 0:
        print(datetime.datetime.now().time(), hostname, exit_code == 0)
    elif exit_code == 1:
        print("down")
    time.sleep(1)

But its very strange as the output is this ....

19:27:17.867410 192.168.1.153 True
19:27:19.895136 192.168.1.153 True

    19:27:21.915193 192.168.1.153 True
    21:33:04.763419 192.168.1.153 True  2 hours, 5 minutes, and 43 seconds

  21:33:08.795348 192.168.1.153 True
  22:14:35.379331 192.168.1.153 True  0 hour, 41 minutes, and 27 seconds

22:14:37.399439 192.168.1.153 True
22:14:39.419444 192.168.1.153 True
22:15:46.087434 192.168.1.153 True
22:15:48.107233 192.168.1.153 True
22:15:50.127444 192.168.1.153 True
22:16:54.759447 192.168.1.153 True
22:16:56.779370 192.168.1.153 True
22:16:58.795390 192.168.1.153 True
22:19:14.079294 192.168.1.153 True
22:19:16.099459 192.168.1.153 True
22:19:18.119388 192.168.1.153 True
22:21:33.459374 192.168.1.153 True
22:21:35.479396 192.168.1.153 True
22:21:37.499417 192.168.1.153 True
22:25:03.527448 192.168.1.153 True
22:25:05.547350 192.168.1.153 True
22:25:07.567417 192.168.1.153 True
22:29:46.295447 192.168.1.153 True
22:29:48.315304 192.168.1.153 True
22:29:50.335316 192.168.1.153 True
22:33:16.375462 192.168.1.153 True
22:33:18.395445 192.168.1.153 True
22:33:20.415362 192.168.1.153 True
22:35:37.747437 192.168.1.153 True
22:35:39.767455 192.168.1.153 True
22:35:41.791218 192.168.1.153 True
22:37:57.115403 192.168.1.153 True
22:37:59.135440 192.168.1.153 True
22:38:01.151363 192.168.1.153 True
22:41:27.223154 192.168.1.153 True
22:41:29.243426 192.168.1.153 True
22:41:31.263356 192.168.1.153 True
22:47:16.663211 192.168.1.153 True
22:47:18.683364 192.168.1.153 True
22:47:20.703401 192.168.1.153 True
22:55:23.507419 192.168.1.153 True
22:55:25.519464 192.168.1.153 True
22:55:27.535433 192.168.1.153 True
00:02:28.211286 192.168.1.153 True
**00:02:30.235454 192.168.1.153 True It just stopped after that point** 

I'm not sure why at 10pm ish it starts to be so active unless it comes online every time a change is detected by the thermostat. What do you think?

Happy 2022!! :)

Electrik-rich546456 commented 2 years ago

I made these changes I'm going to leave it running over night to see the results. Unless you have any improvements in mined?

#!/usr/bin/env python3
import time
import datetime
import os
import tinytuya
# Turn on Debug Mode (optional)
tinytuya.set_debug(True)
# Connect to Tuya Cloud
c = tinytuya.Cloud()
# Select a Device ID to Test
id = "bf2c558dce75d7c817yz9y"
counter = 0
def checker():
    global counter
    try:
        result = c.getstatus(id)
        if "result" in result:
            for item in result["result"]:
                if item["code"] == 'va_temperature':
                    unit = (item['value']/10)  # tuya stores temps as an integer (10x the real value)
                    print("temperature: %0.1f" % unit)
                    if unit >= 22.0:
                        print("hot")
                    elif unit <= 19.5:
                        print("cold")
    except:
        print("error - ignoring")

#    time.sleep(120)
    counter += 1

hostname = "192.168.1.153" #example
#response = os.system("arping -c 1 " + hostname)

while True:
    exit_code = os.system(f"arping -c 1 {hostname} > /dev/null 2>&1")
    if exit_code == 0:
        if counter == 0:
            print(datetime.datetime.now().time(), hostname, exit_code == 0)
            checker()
        else:
            counter = 0

    time.sleep(1)
jasonacox commented 2 years ago

Looks good! It will be interesting to see what happens. I suspect it is sending updates when the temperature values change (at least some %).

Electrik-rich546456 commented 2 years ago

This is the output :-(

DEBUG:TinyTuya [1.3.0]

DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/token?grant_type=1 HTTP/1.1" 200 194
01:21:25.927217 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 23.0
hot
01:21:30.195091 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 22.5
hot
01:28:24.551356 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 22.0
hot
01:37:34.083147 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 22.0
hot
01:37:38.339391 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 21.5
01:52:35.103191 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
temperature: 21.0
03:10:19.095384 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:Error from Tuya Cloud: 'token invalid'
03:10:23.299353 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:Error from Tuya Cloud: 'token invalid'
04:35:56.711424 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:Error from Tuya Cloud: 'token invalid'
09:07:12.455435 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:Error from Tuya Cloud: 'token invalid'

Why is my token invalid, its fine if i start the script again?

jasonacox commented 2 years ago

The token expired. I need to add some code into the library to detect that and refresh the token. Good catch.

jasonacox commented 2 years ago

I just published v1.3.1 - see if this works. I don't how long the tokens last so I created an test by artificially changing the token to get the 'invalid token' error from Tuya. Hopefully this will capture the real expiration as well.

Thanks for helping test these new cloud functions.

python3 -m pip install --upgrade tinytuya
Electrik-rich546456 commented 2 years ago

Would putting something like except "token invalid"in my checker function be any good ?

Electrik-rich546456 commented 2 years ago

Also would putting c = tinytuya.Cloud() and id=blahhhhh into the checker function be any good? So it wold be restarting every time that function is run ?

jasonacox commented 2 years ago

Yes, you could but I added code to the library to re-authenticate with TuyaCloud when the token dies. Woud you be able to test it for me?

python3 -m pip install --upgrade tinytuya

After upgrading to v 1.3.1, re-run your script to see if that fixes the issue.

Electrik-rich546456 commented 2 years ago

I updated timytuya and left the code running all night this is the output

started at 
02:18:00.930574 192.168.1.153 True

***--------------lots more working output snipped--------------
03:48:33.538553 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":192},{"code":"va_humidity","value":55},{"code":"battery_percentage","value":100}],"success":true,"t":1641181713768} token=7eeb46250f5a9e38590df5305deb52de
temperature: 19.2
cold
04:16:56.082574 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:GET: response code=200 text={"code":1010,"msg":"token invalid","success":false,"t":1641183416246} token=7eeb46250f5a9e38590df5305deb52de
DEBUG:Token Expired - Try to renew
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/token?grant_type=1 HTTP/1.1" 200 194
DEBUG:GET: response code=200 text={"result":{"access_token":"1b1d42cb6fbee5f91b07671e10fc48da","expire_time":7200,"refresh_token":"d3cba512868293a1dcf6583aeba0ee1e","uid":"bay1589200703093rRcs"},"success":true,"t":1641183416400} token=None
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":192},{"code":"va_humidity","value":55},{"code":"battery_percentage","value":100}],"success":true,"t":1641183416588} token=1b1d42cb6fbee5f91b07671e10fc48da
temperature: 19.2
cold
04:17:00.646278 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":187},{"code":"va_humidity","value":56},{"code":"battery_percentage","value":100}],"success":true,"t":1641183420806} token=1b1d42cb6fbee5f91b07671e10fc48da
temperature: 18.7
cold
05:32:56.558546 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":182},{"code":"va_humidity","value":58},{"code":"battery_percentage","value":100}],"success":true,"t":1641187976793} token=1b1d42cb6fbee5f91b07671e10fc48da
temperature: 18.2
cold
06:49:55.578530 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:GET: response code=200 text={"code":1010,"msg":"token invalid","success":false,"t":1641192595714} token=1b1d42cb6fbee5f91b07671e10fc48da
DEBUG:Token Expired - Try to renew
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/token?grant_type=1 HTTP/1.1" 200 194
DEBUG:GET: response code=200 text={"result":{"access_token":"c0ebb1c2f423602b7673304583d41c67","expire_time":7200,"refresh_token":"9c2013e54477a2b9039253ca62959f19","uid":"bay1589200703093rRcs"},"success":true,"t":1641192595868} token=None
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":181},{"code":"va_humidity","value":58},{"code":"battery_percentage","value":100}],"success":true,"t":1641192596027} token=c0ebb1c2f423602b7673304583d41c67
temperature: 18.1
cold
08:25:01.174546 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":186},{"code":"va_humidity","value":59},{"code":"battery_percentage","value":100}],"success":true,"t":1641198301381} token=c0ebb1c2f423602b7673304583d41c67
temperature: 18.6
cold
11:11:49.774504 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 69
DEBUG:GET: response code=200 text={"code":1010,"msg":"token invalid","success":false,"t":1641208309933} token=c0ebb1c2f423602b7673304583d41c67
DEBUG:Token Expired - Try to renew
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/token?grant_type=1 HTTP/1.1" 200 194
DEBUG:GET: response code=200 text={"result":{"access_token":"07b88c1783e4e7146ff099d307b79ea0","expire_time":7200,"refresh_token":"1becd3be95aafc0131ad11d3f381024b","uid":"bay1589200703093rRcs"},"success":true,"t":1641208310087} token=None
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":186},{"code":"va_humidity","value":57},{"code":"battery_percentage","value":100}],"success":true,"t":1641208310276} token=07b88c1783e4e7146ff099d307b79ea0
temperature: 18.6
cold
11:11:54.322560 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":186},{"code":"va_humidity","value":57},{"code":"battery_percentage","value":100}],"success":true,"t":1641208314445} token=07b88c1783e4e7146ff099d307b79ea0
temperature: 18.6
cold
11:19:46.974513 192.168.1.153 True
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":186},{"code":"va_humidity","value":57},{"code":"battery_percentage","value":100}],"success":true,"t":1641208787145} token=07b88c1783e4e7146ff099d307b79ea0
temperature: 18.6
cold
jasonacox commented 2 years ago

This is great! There are 3 examples of the token expiring and tinytuya renewing it. Thanks for testing! Let me know if you see anything wrong.

DEBUG:Token Expired - Try to renew
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/token?grant_type=1 HTTP/1.1" 200 194
DEBUG:GET: response code=200 text={"result":{"access_token":"07b88c1783e4e7146ff099d307b79ea0","expire_time":7200,"refresh_token":"1becd3be95aafc0131ad11d3f381024b","uid":"bay1589200703093rRcs"},"success":true,"t":1641208310087} token=None
DEBUG:Starting new HTTPS connection (1): openapi.tuyaeu.com:443
DEBUG:https://openapi.tuyaeu.com:443 "GET /v1.0/iot-03/devices/bf2c558dce75d7c817yz9y/status HTTP/1.1" 200 159
DEBUG:GET: response code=200 text={"result":[{"code":"va_temperature","value":186},{"code":"va_humidity","value":57},{"code":"battery_percentage","value":100}],"success":true,"t":1641208310276} token=07b88c1783e4e7146ff099d307b79ea0
temperature: 18.6
cold
Electrik-rich546456 commented 2 years ago

I know its cool its working ! You are welcome I enjoyed testing. Got a few questions, Is the "expire_time":7200" the max time allowed by the api?

How do I pull in the use of a wifi plug by name to use instead of the print cold or hot statements?? eg d.turn_on("radiator")

jasonacox commented 2 years ago

Good questions. The "expire_time" value is set by the TuyaCloud (that is the response). In your example, that seems to hold true as it requires a new token every 2 hours. Tuya is setting that value so it likely isn't adjustable, but with the token renewal logic, it shouldn't matter.

If you want to take local action on your Tuya devices, you just add the OutletDevice code:

# At the start of your code
radiator = tinytuya.OutletDevice(DEVICEID, DEVICEIP, DEVICEKEY)
d.set_version(3.3)

# Add to section where you want to turn on/off
radiator.turn_off()
radiator.turn_on()
Electrik-rich546456 commented 2 years ago

Can't I use Device ID of the plug to turn on and off?

jasonacox commented 2 years ago

I think I misunderstood your request. If you want to turn it off via the Cloud API, you will need to do something like this instead:

# Send Command - Turn on switch
commands = {
    'commands': [{
        'code': 'switch_1',
        'value': True
    }, {
        'code': 'countdown_1',
        'value': 0
    }]
}
print("Sending turn on command...")
c.sendcommand(DeviceID,commands)
Electrik-rich546456 commented 2 years ago

Hi hows it going ? I may have discovered another error... If i run this on my pc its fine if i run it on the pi it fails with

TinyTuya [1.3.1]

GET: response code=200 text={"result":{"access_token":"xxxxxxxx","expire_time":4739,"refresh_token":"xxxxx","uid":"xxxxx"},"success":true,"t":1641383117344} token=None
11:45:16 AM Host is online
GET: response code=200 text={"result":[{"code":"va_temperature","value":224},{"code":"va_humidity","value":42},{"code":"battery_percentage","value":97}],"success":true,"t":1641383148528} token=xxxxxx
temperature: 22.4
status() entry (dev_type is device22)
building payload=b'{"devId":"xxxxxx","uid":"xxxxx","t":"1641383148","dps":{"1":null}}'
payload generated=b'\x00\x00U\xaa\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x873.3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd9 b\\\xdei\xa9&\x12\xc8\xe6\xaf\xf8<=\x9d\x04jy]\xdf\xe3D\xb1\x1bDQ\x1e:\x02"\xad\xfdx\\-\x9f\x0f&\x02\xe25\xd9\x89y\x19\x9c\xf1G\xd8\xe2i\xc31L>>\x9d:\xeb!\x07\xf1\xfe>\x92p\xda\xea-\'\xfdBag7\x945\xad\x86+\xe3\r\xcc\xf0(\xab1\x94\x10\x94\xd0\xe9\xd9?\xe3\xba\x1d\xa6\xee\x00\xe8\xc5\xfbd\x1ak\x93\xc2\x94\xf4\xeb\xb9\x99\xae"\x00\x00\xaaU'
socket unable to connect - retry 1/5
socket unable to connect - retry 2/5
socket unable to connect - retry 3/5
socket unable to connect - retry 4/5
socket unable to connect - retry 5/5

`ERROR Network Error: Device Unreachable - 905 - payload: null
status() received data={'Error': 'Network Error: Device Unreachable', 'Err': '905', 'Payload': None}
'dps'
Electrik-rich546456 commented 2 years ago

My code that running

#!/usr/bin/env python3
import time
import os
import json
import logging

import tinytuya

# Get the top-level logger object
log = logging.getLogger()

# make it print to the console.
console = logging.StreamHandler()
log.addHandler(console)

t = time.localtime()

with open('/home/pi/bin/phplights/snapshot.json') as json_file:
    jdata = json.load(json_file)

# Turn on Debug Mode (optional)
tinytuya.set_debug(True)
# Connect to Tuya Cloud
c = tinytuya.Cloud() # This will use the tinytuya.json file created by the wizard or you can specify:
# Select a Device ID to Test
id = "xxxx"
counter = 0

def pfow(name, *num):
    for item in jdata["devices"]:
        if item["name"] == name:
            break
    d = tinytuya.OutletDevice(item["id"], item["ip"], item["key"], 'device22')
    d.set_version(float(item["ver"]))
    d.set_dpsUsed({"1": None})
    data = d.status()
    for n in num:
        if n == 1:
            if(data['dps']['1'] == True):
                log.warning("Radiator is on Turning off")
                d.turn_off()
        if n ==2:
            if(data['dps']['1'] == False):
                log.warning("Radiator is off Turing on")
                d.turn_on()

def checker():
    global counter
    try:
        result = c.getstatus(id)
        if "result" in result:
            for item in result["result"]:
                if item["code"] == 'va_temperature':
                    unit = (item['value']/10)  # tuya stores temps as an integer (10x the real value)
                    log.warning("temperature: %0.1f" % unit)
                    if unit >= 22.0:
                        #log.warning("hot")
                        pfow("Bedroom Radiator",  1)
                    elif unit <= 19.5:
                        pfow("Bedroom Radiator",  2)
                        #log.warning("cold")
    except(RuntimeError, TypeError, NameError, Exception) as e:
        log.warning(f'{e}')

#    time.sleep(120)
    counter += 1

hostname = "192.168.1.153" #example
#response = os.system("arping -c 1 " + hostname)

while True:
    ret = os.system(f"ping -c 1 {hostname} > /dev/null 2>&1")
    if ret == 0:
        if counter == 0:
            current_time = time.strftime("%r", t)                    
            log.warning(f'{current_time} Host is online')            
#            log.warning(datetime.datetime.now().time())
#            log.warning("Host is online")
            #print(datetime.datetime.now().time(), hostname, exit_code == 0)
            checker()
        else:
            counter = 0

    time.sleep(1)
jasonacox commented 2 years ago

Are you able to ping the device from the PI? The error indicates it is not reachable from the PI:

ERROR Network Error: Device Unreachable - 905
Electrik-rich546456 commented 2 years ago

Well no it seems to be pot pingable but if i change to tinytuya.BulbDevice instead of tinytuya.OutletDevice it works !!

What's the difference ?

jasonacox commented 2 years ago

BulbDevice class is similar to OutletDevice (both use the root Device class) but adds bulb attributes (type, brightness, color) and related functions for controlling smart bulbs. I don't know how it would make a difference in you case. Very interesting.

Electrik-rich546456 commented 2 years ago

Hi i think it was just a strange ghost in the code... as it stopped working due to the it not having an ip address anymore so i just used the new cloud function instead. I just need your help with two more things. 1 how do i make the output from print("DPS IDs of device:\n", result) more human readable ? 2 how to I make an if statement to check power status ? using the cloud function

Electrik-rich546456 commented 2 years ago

Hi @jasonacox I seem to have a new problem.. This is my error im getting TypeError: __init__() should return None, not 'dict' but only if the script is running as www-data (33) user. If I switch to that user in the terminal it works and doesn't give any errors same if i run as standard user. Do you know how to make the script use all imported modules like soket and the like ?

Oh yeah and that ERROR Network Error: Device Unreachable - 905 is only when the smart life app is open so my bad :-)

jasonacox commented 2 years ago

I must be missing some context. Are you running the script from a web server?

Electrik-rich546456 commented 2 years ago

Hi @jasonacox Yeah it's running on my RPI 2b along side pi.hole. I've created a PHP page with a load of buttons on it each one runs a little Py script when clicked. They all work except the one using the new tuya.cloud function. I suspect it's some strange path of modules thing. 😞

On Thu, 20 Jan 2022, 2:30 am Jason Cox, @.***> wrote:

I must be missing some context. Are you running the script from a web server?

— Reply to this email directly, view it on GitHub https://github.com/jasonacox/tinytuya/issues/94#issuecomment-1017059003, or unsubscribe https://github.com/notifications/unsubscribe-auth/AN3GUW4SWVHZDASWKSQX7MDUW5XTFANCNFSM5KVX2BCA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

jasonacox commented 2 years ago

Ah, I see. If you are using this line: c = tinytuya.Cloud() in your code, that by default will try to load a local tinytuya.json file. PHP not like that or have a pwd available to use. You could fix that specifying the credentials as parameters:

# c = tinytuya.Cloud()  # uses tinytuya.json 
c = tinytuya.Cloud(
        apiRegion="us", 
        apiKey="xxxxxxxxxxxxxxxxxxxx", 
        apiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 
        apiDeviceID="xxxxxxxxxxxxxxxxxxID")