jasonacox / tinytuya

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

Cannot connect to Smart Switch #507

Open measwel opened 3 weeks ago

measwel commented 3 weeks ago

Good day,

The device shows up in the scan. The local key has been fetched with the wizard.

enlarger Product ID = keyjup78v54myhan [Valid Broadcast]: Address = 192.168.51.246 Device ID = bfadddf28e43521d4c7man (len:22) Local Key = xxxxxxxxx Version = 3.3 Type = default, MAC = 10:5a:17:dc:fd:32 Status: {'1': True, '9': 0, '18': 0, '19': 0, '20': 2346, '21': 1, '22': 610, '23': 30696, '24': 17327, '25': 2410, '26': 0, '38': 'memory', '39': False, '40': 'relay', '41': False, '42': '', '43': '', '44': ''}

I read through all the issues concerning 901 connection errors, but not found a solution.

DEBUG:ERROR Network Error: Unable to Connect - 901 - payload: null DEBUG:status() received data={'Error': 'Network Error: Unable to Connect', 'Err': '901', 'Payload': None}

I have 2 clues.

1 : the core.py throws an error:

File "...\AppData\Local\Programs\Python\Python312\Lib\site-packages\tinytuya\core.py", line 1009, in _get_socket if self.version >= 3.4: ^^^^^^^^^^^^^^^^^^^ TypeError: '>=' not supported between instances of 'str' and 'float'

2 : maybe other active connections are blocking access to the device?

I have a smart life app on my phone which shows the device. Disconnecting the device in the app does not work. When I disconnect without wiping data, the switch still loses its wifi settings. I have to repair it in the app to get it to show on the wifi network again.

Just closing the smart life app on the phone also does not help. Could it be, because other home members also have that app on their phone and are possibly making a connection? If so, how should I solve that? Should I remove the device from all the smart life apps?

Thank you.

So I do not know how to make sure no other active connections are present.

uzlonewolf commented 3 weeks ago

Hi @measwel,

Can you post your initialization code? It looks like version is being set as a string ("3.3") instead of a float (3.3) like it should.

You do not need to disconnect the device in the SmartLife app, simply closing the app is fine. As for multiple connections, this is going to depend on what the device supports; some devices can handle 2-4 simultaneous connections while others only allow 1.

measwel commented 3 weeks ago

I read device from device.json based on stored uuids of devices I am interested in.

def get_device_handle(device, type):
    device_id = device["id"]
    device_ip = device["ip"]
    device_key = device["key"]
    device_version = device["version"]

    if type=='outlet' : dev_handle = tinytuya.OutletDevice(device_id, device_ip, device_key)
    elif type=='bulb' : dev_handle = tinytuya.BulbDevice(device_id, device_ip, device_key)

    dev_handle.set_version(device_version) <-- YES THIS IS PROBABLY COMING IN AS STR FROM THE JSON
    return dev_handle

I will try to float it.

measwel commented 3 weeks ago

It works 👍 Thank you.

dev_handle.set_version(float(device_version))

A few words about what I am building. I am trying to make an app for darkroom work. Tkinter inteface. Red icons on black background, so it should be safe. I figured I can set smart bulbs to a red light wavelength that will not influence photographic paper. I can time the enlarger. And if I manage to integrate a light intensity sensor, then I can also let the app auto calculate the optimal exposure time based on the amount of light that is projected onto the photographic paper. :)

I plan to make the app open source as it should be quite useful for people who like to develop their own black and white photos in the darkroom.

jasonacox commented 3 weeks ago

Nice project @measwel ! Keep us posted. 😁

Thanks @uzlonewolf somewhere in my "don't have time" TODO list I had planned to explore how to type-safe our parameters. There's never enough time but we could use this issue to flag that.

uzlonewolf commented 3 weeks ago

I thought we were casting the version to a float, but looking at the code it seems that's only done when passed into Device(..., version=N), d.set_version(N) doesn't do it. Should be a simple change to add a float() to the set_version() function.

measwel commented 3 weeks ago

Would indeed be helpful for those who read the device params from devices.json. From a different barrel: what would be a good way to generically test if a device is online and ready to be used? The best I came up with is passing in a device handle and:

def  test_device(d):
    ok=True
    s = d.status()
    for a in s:
        if a=="Error": 
            msg = "Device " + d.id + " at " + d.address + " : " + s["Error"]
            message_to_user(msg)
            ok=False
    return ok

PS Would be nice to include the device name in the message too, but it's not part of the handle object. I would probably have to dig into devices.json to fetch it.

measwel commented 2 weeks ago

@jasonacox

3 sleepless nights later I have a prototype to show. :)

https://github.com/measwel/darkroom

Would very much appreciate your feedback.

jasonacox commented 2 weeks ago

Nice job @measwel !

measwel commented 2 weeks ago

@jasonacox

I can't get error handling quite right.

How to check if a device is online? Best I came up with is to d.status() and check for Error message. But getting the status takes a second and freezes up the interface.

The less I freeze up the interface, the better. Getting the exposure time right, means the devices must respond swiftly. So I currently execute the action (like switching on the enlarger) and afterwards check the device status.

Why do I check it? So in case a device went offline, I can tell the user, throw an exception and try to reconnect.

It would be better if I could check the device status in a non blocking way. Either with a callback or in a thread. As far as I know .status() does not have a callback one could use, so I will try to run it in a thread.

measwel commented 2 weeks ago

@jasonacox

So I made this threaded routine to check the status of a device after using it. It seems to work, but I do not really know if this makes sense or not :)

def check_device_status(d):
    ok = True
    status = None

    def device_status(d):
        return d.status()

    with concurrent.futures.ThreadPoolExecutor() as executor:
        future = executor.submit(device_status, d)
        status = future.result()

    for a in status:
        if a=="Error": 
            msg = f'{device_name(d)} : {status["Error"]}'
            message_to_user(msg)
            ok = False

    if not ok: 
        raise Exception("Device error")
    else:
        return status
jasonacox commented 2 weeks ago

Here is a script to scan through a list of devices and detect which are online or offline. Notice the additional connection settings in the constructor (OutletDevice):


import json
import os
import tinytuya

# Load all device details from devices.json
print('\nLoad devices.json')
devices = []
if os.path.exists('devices.json'):
    with open('devices.json') as json_file:
        devices = json.load(json_file)
else:
    print('devices.json not found')

# Or optionally, Set your own device details here:
devices = [
    {
        "name": "Switch",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.3"
    },
    {
        "name": "Light",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.1"
    },
    {
        "name": "Fan",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.3"
    },
]

# Loop through devices and check to see which are online
print('\nCheck Devices')
for device in devices:
    if not device['id'] or not device['ip'] or not device['key']:
        continue
    # Connect to device
    d = tinytuya.OutletDevice(dev_id=device['id'],
                              address=device['ip'],
                              local_key=device['key'],
                              version=device['version'],
                              connection_timeout=1,
                              connection_retry_limit=1,
                              connection_retry_delay=1,
                              )
    # Check status of device
    data = d.status()
    if "Error" in data:
        print(f"{device['name']} - OFFLINE")
    else:
        print(f"{device['name']} - ONLINE ({data})")
measwel commented 2 weeks ago

Interesting. It is doing the same thing I am currently doing with 2 differences.

  1. Smarter way to check for error : if "Error" in data: I will use that.
  2. It does not check the status in a thread. Using a thread might be better, so the user interface does not freeze up during the check.
measwel commented 2 weeks ago

I would propose:

import json
import os
import tinytuya
import concurrent.futures

# Load all device details from devices.json
print('\nLoad devices.json')
devices = []
if os.path.exists('devices.json'):
    with open('devices.json') as json_file:
        devices = json.load(json_file)
else:
    print('devices.json not found')

# Or optionally, Set your own device details here:
devices = [
    {
        "name": "Switch",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.3"
    },
    {
        "name": "Light",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.1"
    },
    {
        "name": "Fan",
        "id": "xxxxxxxx",
        "ip": "x.x.x.x",
        "key": "xxxxxxxxxxxxxxxx",
        "version": "3.3"
    },
]

# Loop through devices and check to see which are online
print('\nCheck Devices')
for device in devices:
    if not device['id'] or not device['ip'] or not device['key']:
        continue
    # Connect to device
    d = tinytuya.OutletDevice(dev_id=device['id'],
                              address=device['ip'],
                              local_key=device['key'],
                              version=device['version'],
                              connection_timeout=1,
                              connection_retry_limit=1,
                              connection_retry_delay=1,
                              )
    # Check status of device

    def device_status(d):
        return d.status()

    with concurrent.futures.ThreadPoolExecutor() as executor:
        future = executor.submit(device_status, d)
        data = future.result()

    if "Error" in data:
        print(f"{device['name']} - OFFLINE")
    else:
        print(f"{device['name']} - ONLINE ({data})")

The benefit is, that checking the status runs in its own thread this way and does not block the main program. When the thread finishes, data can be read out normally.

measwel commented 2 weeks ago

I only do this when checking the status. For other functions I do want the tuya code to run synchronous, so the events happen in the correct sequence.

Imagine I have 10 darkroom lamps. I send commands to switch them all off then switch on the enlarger. I do not want checking the status of those 10 lamps to block the program and interfere with switching the enlarger off again at the right time.

measwel commented 2 weeks ago

I always first run some function on the device, then check the status of that device. Why? Because some functions will return an Error when executed, but others will not. For instance :

d.set_status(False) <-- Turn off the smart outlet of the enlarger. This does return an error when the outlet is offline.

l.turn_off() <-- Turn off rgb led lamp. This does not return an Error when the lamp is offline.

turning the lamp off, does not seem to return an error code, even when the device is offline. So I need to get the status to find out if a lamp has malfunctioned.

jasonacox commented 2 weeks ago

Well done @measwel

One note, I think you could replace d.status() with d.turn_off() but would need to capture the response similar to status:

data = d.turn_off()
if "Error" in data:
    print(f"{device['name']} - OFFLINE ({data})")
else:
    print(f"{device['name']} - ONLINE ({data})")

Also, as a note, without adding the connnection* parameters to the OutletDevice, the defaults will mean that TinyTuya will take up to 45s to determine the device is offline based on this:

Total timeout = (connection_timeout * connection_retry_limit) + 
        (connection_retry_delay * (connection_retry_limit - 1))
        Defaults: (5 * 5) + (5 * (5 - 1)) = 45 seconds

As you mention, this is less of an issue if you are threading the calls and not blocking the main loop.

measwel commented 2 weeks ago

Hi @jasonacox, thanks for your suggestions.

I think I will stick with using .status() as that works every time. Yes, I set other connection settings to speed things up.

I cannot finish the part that would do the automatic exposure time calculation as I do not have a light intensity meter yet. Perhaps you know someone who does and could maybe help out?

jasonacox commented 2 weeks ago

I do not have a light intensity meter yet. Perhaps you know someone who does and could maybe help out?

Something like this? I haven't used this but there appear to be several options out there. I don't know if this would work for your use case.

measwel commented 2 weeks ago

Thank you @jasonacox. That is exactly the model I was looking at. I ordered it. It has usb power and a sensitivity scale from 0 to 1000 lumen, which should give enough resolution I hope.

What I do not know, is whether the scale is linear. Will 2 times as much light produce a two times bigger sensor value? I guess I will find out. It should be easy to test, as each F-stop on my enlarger lens increases / decreases the light output by 2.

measwel commented 1 week ago

@jasonacox

I bought 5 new RGB LED bulbs and a new smart outlet switch. I paired the devices. They work in the smartlife app. I pulled their data from the cloud with the wizard. But for some reason the scan does not find the new devices on the network.

Polling local devices... [Dark 1 ] Error: No IP found [Dark 2 ] Error: No IP found [Dark 3 ] Error: No IP found [Dark 4 ] Error: No IP found [Dark 5 ] Error: No IP found [Dark Switch ] Error: No IP found

What could be wrong? Could it be that the new devices connect over a different port? I would gladly help testing / debugging to get this problem resolved.

Maybe the new devices use yet another version of the API to communicate?

jasonacox commented 1 week ago

Hi @measwel , sure that is all possible. Can you share a link to thee bulbs so we can get some to test?

Also, can you provide the debug dump? tinytuya.set_debug(True) in your code or if you are doing scanner: python3 -m tinytuya scan -d

measwel commented 1 week ago

Debug scan output:

DEBUG:Listening for Tuya devices on UDP ports 6666, 6667 and 7000 DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} enlarger Product ID = keyjup78v54myhan [Valid Broadcast]: Address = 192.168.51.246 Device ID = bfadddf28e43521d4c7man (len:22) Local Key = U0-7k~0$=!'yM-z5 Version = 3.3 Type = default, MAC = 10:5a:17:dc:fd:32 Polling 192.168.51.246 Failed: DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} Scan completed in 18.0334 seconds

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

It seems to find just 1 device, which is my older smart plug switch.

measwel commented 1 week ago

Here are the links:

https://www.aliexpress.com/item/1005005810965042.html

https://de.aliexpress.com/item/1005005665258417.html

jasonacox commented 1 week ago

Interesting, both of those inks are 404 for me, but may be region locked. What is the exact name of the product I can search for?

As to the devices, please confirm they are just WiFi and not using a gateway (e.g. Zigbee)? Also, try to have the scanner do a force scan python3 -m tinytuya scan -f -d and if it can't find them, see if your router is able to detect what DHCP IP was assigned to them.

measwel commented 1 week ago

Updated the links. Will do the forced scan now.

jasonacox commented 1 week ago

Sadly, link still show:

image
measwel commented 1 week ago

Parsed args: Namespace(debug=False, command='scan', debug2=True, max_time=None, force=[[]], no_broadcasts=False, nocolor=False, yes=False, device_file='devices.json', snapshot_file='snapshot.json') DEBUG:TinyTuya [1.14.1]

DEBUG:Python 3.12.4 (tags/v3.12.4:8e8a4ba, Jun 6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)] on win32 DEBUG:Using pyca/cryptography 42.0.8 for crypto, GCM is supported DEBUG:loaded=devices.json [19 devices]

TinyTuya (Tuya device scanner) [1.14.1]

[Loaded devices.json - 19 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 Option: Network force scanning requested.

Running Scan...

DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} DEBUG:final payload_dict for 'bfadddf28e43521d4c7man' ('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":"bfadddf28e43521d4c7man","devId":"bfadddf28e43521d4c7man","uid":"bfadddf28e43521d4c7man","t":"1719260865"}' DEBUG:payload encrypted=b'000055aa000000010000000a00000088e0dde68977b3b8bf5d0f7d1512e0cf11917f13e6ebf2cc3253fce84e58712b0cfe03926d050ede078f9e3b27d31b9eb74771ba33d99c4be5bd1908e66477f53ae63d2da1c17239f6ee9c53e11928f979917f13e6ebf2cc3253fce84e58712b0ce1a1a862e02dbc599b7a4186bed57fdc17483fc9828cb6d0fb2cc1e02d473b2f0a287bf70000aa55' DEBUG:PollDevice: raw unpacked message = TuyaMessage(seqno=1, cmd=10, retcode=0, payload=b'm k\x9b\xec\xfb\xc1F+\n\x19\xf0\xd9\x01O\x02\x90\x04--\xaf\xb5ntt\xcd&\xe5\x8dsm\x0e;\x8b\x94\r\xd4u\x1a\xc6\xfd\x1c\x0b\x18\xd2\xcaL,T\xd6$ \x0c\xe8\xe7\x0f\'\xc6^\x8a1:\xbaX\x8a}\xbb\xdd\xf8\xe4\xd1\x8e\xaa\x16\xd4\xaa8\xf9\x8e"K\xfd\x8a\x7f\x18\xb2\x9ct\xaf-\xb1[\xe4}3\xa7\xc7F\xa9\x06\x0b\xd0\x00\x10\xd1\xb2~\xc3\x9a\xd3\xec\xdd\x8e\xd9\xb4\xa6\xa36\x0c/\x0c\xd3\x1c\xe4\x01j\xa3aS\xc8^\x06R\xb6\xa6Gm\xb6\xdb\x87\xe2\x99o\xac8Co\xd8\x03\xe1\x00.\xce\xb30}\xcc\xa2\x0e,V\xe7\x94:\xf5@\x9b\x00h\xecp\xf84\x96\x14O\x12\x88\xc7\x17\x9a[\xebi~.\x05\x9a\xa5R\xe7', crc=1430753997, crc_good=True, prefix=21930, iv=None) DEBUG:decode payload=b'm k\x9b\xec\xfb\xc1F+\n\x19\xf0\xd9\x01O\x02\x90\x04--\xaf\xb5ntt\xcd&\xe5\x8dsm\x0e;\x8b\x94\r\xd4u\x1a\xc6\xfd\x1c\x0b\x18\xd2\xcaL,T\xd6$ \x0c\xe8\xe7\x0f\'\xc6^\x8a1:\xbaX\x8a}\xbb\xdd\xf8\xe4\xd1\x8e\xaa\x16\xd4\xaa8\xf9\x8e"K\xfd\x8a\x7f\x18\xb2\x9ct\xaf-\xb1[\xe4}3\xa7\xc7F\xa9\x06\x0b\xd0\x00\x10\xd1\xb2~\xc3\x9a\xd3\xec\xdd\x8e\xd9\xb4\xa6\xa36\x0c/\x0c\xd3\x1c\xe4\x01j\xa3aS\xc8^\x06R\xb6\xa6Gm\xb6\xdb\x87\xe2\x99o\xac8Co\xd8\x03\xe1\x00.\xce\xb30}\xcc\xa2\x0e,V\xe7\x94:\xf5@\x9b\x00h\xecp\xf84\x96\x14O\x12\x88\xc7\x17\x9a[\xebi~.\x05\x9a\xa5R\xe7' DEBUG:decrypting=b'm k\x9b\xec\xfb\xc1F+\n\x19\xf0\xd9\x01O\x02\x90\x04--\xaf\xb5ntt\xcd&\xe5\x8dsm\x0e;\x8b\x94\r\xd4u\x1a\xc6\xfd\x1c\x0b\x18\xd2\xcaL,T\xd6$ \x0c\xe8\xe7\x0f\'\xc6^\x8a1:\xbaX\x8a}\xbb\xdd\xf8\xe4\xd1\x8e\xaa\x16\xd4\xaa8\xf9\x8e"K\xfd\x8a\x7f\x18\xb2\x9ct\xaf-\xb1[\xe4}3\xa7\xc7F\xa9\x06\x0b\xd0\x00\x10\xd1\xb2~\xc3\x9a\xd3\xec\xdd\x8e\xd9\xb4\xa6\xa36\x0c/\x0c\xd3\x1c\xe4*\x01j\xa3aS\xc8^\x06R\xb6\xa6Gm\xb6\xdb\x87\xe2\x99o\xac8Co\xd8\x03\xe1\x00.\xce\xb30}\xcc\xa2\x0e,V\xe7\x94:\xf5@\x9b\x00h\xecp\xf84\x96\x14O\x12\x88\xc7\x17\x9a[\xebi~.\x05\x9a\xa5R\xe7' DEBUG:decrypted 3.x payload='{"dps":{"1":false,"9":0,"18":0,"19":0,"20":2337,"21":1,"22":610,"23":30696,"24":17327,"25":2410,"26":0,"38":"memory","39":false,"40":"relay","41":false,"42":"","43":"","44":""}}' DEBUG:payload type = <class 'str'> DEBUG:decoded results='{"dps":{"1":false,"9":0,"18":0,"19":0,"20":2337,"21":1,"22":610,"23":30696,"24":17327,"25":2410,"26":0,"38":"memory","39":false,"40":"relay","41":false,"42":"","43":"","44":""}}' enlarger Product ID = keyjup78v54myhan [Valid Broadcast]: Address = 192.168.51.246 Device ID = bfadddf28e43521d4c7man (len:22) Local Key = U0-7k~0$=!'yM-z5 Version = 3.3 Type = default, MAC = 10:5a:17:dc:fd:32 Status: {'1': False, '9': 0, '18': 0, '19': 0, '20': 2337, '21': 1, '22': 610, '23': 30696, '24': 17327, '25': 2410, '26': 0, '38': 'memory', '39': False, '40': 'relay', '41': False, '42': '', '43': '', '44': ''} DEBUG:Unable to get network for [], ignoring ERROR: Unable to get network for [], ignoring. Traceback (most recent call last): File "C:\Users\marek\AppData\Local\Programs\Python\Python312\Lib\site-packages\tinytuya\scanner.py", line 902, in _generate_ip network = ipaddress.ip_network(netblock) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\marek\AppData\Local\Programs\Python\Python312\Lib\ipaddress.py", line 83, in ip_network raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 network') ValueError: [] does not appear to be an IPv4 or IPv6 network

DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} DEBUG:Received valid UDP packet: {'ip': '192.168.51.246', 'gwId': 'bfadddf28e43521d4c7man', 'active': 2, 'ablilty': 0, 'encrypt': True, 'productKey': 'keyjup78v54myhan', 'version': '3.3'} Scan completed in 18.0453 seconds

Scan Complete! Found 1 devices. Broadcasted: 1 Force-Scanned: 0 - Matched GWID: 0 Matched Key: 0 Unmatched: 0 Versions: 3.3: 1

Saving device snapshot data to snapshot.json

DEBUG:Scan complete with 1 devices found

jasonacox commented 1 week ago

The device does not appear on the same network as your host. Do you have multiple WiFi networks set up?

measwel commented 1 week ago

switch

RGB Led

measwel commented 1 week ago

Maybe the screenshots will help to find them.

Yes, I have 2 wifi networks. What is ment by the "host" ?

measwel commented 1 week ago

My main router is giving out IPs via DHCP and provides WiFi downstairs. Then I have a WiFi capable switch upstairs which just acts as a switch, providing WiFi upstairs. It has DHCP switched off.

The two smart outlets (one called 'enlarger', the other 'Dark Switch') are currently both connected to the WiFi upstairs. But for some reason, the scan finds one of them and not the other.

measwel commented 1 week ago

Maybe this will help too.

Old switch: "model": "BSD-BPEQ16" (works) New switch: "model": "\u767e\u89c6\u76fe\u8ba1\u91cf"

Old RGB LED: "model": "50-LM-025", "product_name": "E27 RGB CCT" New RGB LED: "model": "\u5bbd\u538b\u82af\u7247\u4e0a\u677f\u7403\u6ce1", "product_name": "LED BULB W5K"

jasonacox commented 1 week ago

Yes, I have 2 wifi networks. What is ment by the "host" ?

The computer you are running tinytuya scan is the host. It must be on the same network as the device. Can you tell what what IP address your router assigns to these new Tuya devices? If so, are you able to ping them from your host computer?

measwel commented 1 week ago

xx

They are on the network and I can ping them from the host.

jasonacox commented 1 week ago

Ok, so in the debug only 192.168.51.246 is sending broadcast message (I assume that was another one an you are only showing 4 new ones). That does seem very odd.

I would try: python3 -m tinytuya -f 192.168.51.0/24

And script with each IP (you mentioned you did get the ID and Key for these via Wizard):

d = tinytuya.OutletDevice(
      dev_id=DEVICEID,
      address="192.168.51.198,
      local_key=DEVICEKEY,
      version=3.3)

data = d.status()
print(data)
measwel commented 1 week ago

192.168.51.246 is the old outlet switch. The new outlet and the 5 new RGB Leds do not show up in the scan.

jasonacox commented 1 week ago

I would try direct access to see what happens:

d = tinytuya.OutletDevice(
      dev_id=DEVICEID,
      address="192.168.51.198,
      local_key=DEVICEKEY,
      version=3.3)

data = d.status()
print(data)
measwel commented 1 week ago

I will try. In the mean time, maybe this link works?

https://www.aliexpress.com/snapshot/0.html?spm=a311a.7996332.0.0.29636b31JpXwAa&orderId=3036988657497962&productId=1005005810965042

https://www.aliexpress.com/snapshot/0.html?spm=a311a.7996332.0.0.29636b31JpXwAa&orderId=3036988657497962&productId=1005005810965042

measwel commented 1 week ago

I tried both with BulbDevice and OutletDevice. I am not sure if I got the IP / deviceID / key combo right. I tried for all IPs. It all ended in:

DEBUG:status() received data={'Error': 'Check device key or version', 'Err': '914', 'Payload': None}

jasonacox commented 1 week ago

What version did you see from the wizard? Try changing the version in the script from 3.3 to 3.4 and 3.5.

measwel commented 1 week ago

@jasonacox, are you US or Europe based? I am thinking about reordering as one of the RGB LEDs I got is broken (only the blue led works). Ali tells me to send the whole order back for a refund which is nonsense.

I get a super deal on these devices and for 9 bucks I can get 2 more LEDs and 1 more switch. What if I let them be shipped to an address designated by you? Then you could keep one LED and switch, try them out and send one LED to me, so I can have 5 working ones?

measwel commented 1 week ago

I just checked with a bogus address in the UK. It seems they will ship to anywhere in EU for about 10 bucks.

xx2

measwel commented 1 week ago

@jasonacox

Update. I am able to connect to one of the new RGB LEDs using version 3.5 :

db = tinytuya.BulbDevice(dev_id=device_id, address=i, local_key=device_key, version=3.5, connection_timeout=1, connection_retry_limit=1, connection_retry_delay=0)

{'dps': {'20': True, '21': 'colour', '22': 10, '23': 269, '24': '006803e803e8', '25': '000e0d0000000000000000c80000', '26': 0, '34': False}}

I am also able to connect to the new smart plug, also by setting version to 3.5 and manually setting IP and key.

{'dps': {'1': True, '9': 0, '18': 0, '19': 0, '20': 2383, '21': 1, '22': 572, '23': 29257, '24': 15834, '25': 2630, '26': 0, '38': 'memory', '39': 'relay', '40': False, '41': '', '42': '', '43': '', '51': False}}

The scan does not find the devices however. Maybe these new devices broadcast on a different port?

I did notice this error when doing a forced scan:

def _generate_ip(networks, verbose, term):
    for netblock in networks:
        if tinytuya.IS_PY2 and type(netblock) == str:
            netblock = netblock.decode('latin1')
        try:
            network = ipaddress.ip_network(netblock)
            log.debug("Starting brute force network scan %s", network)
        except:
            log.debug("Unable to get network for %r, ignoring", netblock)
            if verbose:
                print(term.alert +
                    'ERROR: Unable to get network for %r, ignoring.' % netblock + term.normal)
                print(traceback.format_exc())
            continue

File "C:\Users\marek\AppData\Local\Programs\Python\Python312\Lib\site-packages\tinytuya\scanner.py", line 902, in _generate_ip
    network = ipaddress.ip_network(netblock)

    raise ValueError(f'{address!r} does not appear to be an IPv4 or IPv6 network')
ValueError: [] does not appear to be an IPv4 or IPv6 network
measwel commented 1 week ago

I received the light sensor today. The scan finds it and I am able to get a reading from it. But the new bulbs and switch are not found by the scan. Here are direct links to the devices which are not found by the scanner. I hope these links work?

https://www.aliexpress.us/item/3256805811828814.html

https://www.aliexpress.us/item/3256805544389239.html

For the time being, I checked the new IPs on my network and made a small script to find working combinations of IP and device key. Then I had the script add the IP and Version in devices.json. This way all my devices work. The only problem is, that I was not able to find them with the tinytuya scanner.

jasonacox commented 1 week ago

Thanks @measwel - those links worked! I was able to order. I'll let you know what happens.

measwel commented 1 week ago

That's great. I am sure we will figure it out.

uzlonewolf commented 1 week ago

The force scan was broken in the last update, #511 fixes it.

You are the 2nd person to report a v3.5 device not broadcasting. I hope this is not a new trend...

measwel commented 1 week ago

I played with the light intensity sensor today. Some suprising results.

Good: it works :smile:.

Strange: with a opal light bulb the measurement is linear. Changing 1 stop on the lens halves the measured lux value. I also tried a LED bulb. In this case the measured values did not quite get halved, but it was close enough to be practical I guess.

Interesting: a cold white light gives more measured lux than a warm white light at the same brightness setting.

Bad: the sensitivity of the sensor is on the low side. The scale goes from 0 to 1000. With an average setting on my enlarger (20 x 20 cm print, F5.6) I am getting a reading of 16 lux. Making a big print or closing down the lens to F22 will bring the measured value to 0 lux.

Conclusion; it is sensible to use the strongest bulb feasible in the enlarger head. I will try with the strongest LED bulb I can find.

This should do it: https://www.ebay.de/itm/155860485532

4000K to get good contrast, opal white and a whopping 3500 lumen.

measwel commented 1 week ago

The force scan was broken in the last update, #511 fixes it.

You are the 2nd person to report a v3.5 device not broadcasting. I hope this is not a new trend...

An alternative - but maybe stupid - scanning procedure would be:

Find existing IPs in devices.json. Ping every other IP in the network : xxx.xxx.xxx.1 to xxx.xxx.xxx.255 (based on the found existing IPs). Exclude the already found IPs from the ping. Store the IPs which return the ping. (the v3.5 devices which I have do ping back). Try every combination of the pinged IPs and local device keys. If a device connects, we write its IP and version back into devices.json.

This is basically how I figured out the IP and version of the v3.5 devices which did not show up on the scan. The only difference being, that I did not ping to find the new IPs. I just exported them from my router.

Come to think of it... Probably something very similar is done by the forced scan. :)

measwel commented 1 week ago

This is the code I used:

import tinytuya
import json

tinytuya.set_debug(False)

IPs = ["192.168.51.20","192.168.51.21","192.168.51.112","192.168.51.147","192.168.51.163","192.168.51.198","192.168.51.207"] 

with open('devices.json', 'r', encoding='utf-8') as f:  dev = json.load(f)

for d in dev:
    device_id = d["id"]
    device_key = d["key"]
    v = "3.5"

    if not d["name"][:4] == "Dark":
        continue

    for i in IPs:

        do = tinytuya.OutletDevice(dev_id=device_id, address=i, local_key=device_key, version=v, connection_timeout=1, connection_retry_limit=1, connection_retry_delay=0)
        data = do.status()

        if data and not "Error" in data: 
            d["version"] = v
            d["ip"] = i
            print(d["name"], i, v)
            break

with open('devices.json', 'w', encoding='utf-8') as f: json.dump(dev, f, ensure_ascii=False, indent=2) 

As said, the list of new IPs to try I got from my router. But I think one could try to find them with a ping on the same subnet. As all my device names ment for the Darkroom start with "Dark" I can skip the others. Also, I noticed it made no difference whether I used outlet or bulb for the connection test.