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

Cannot connect to Smart Switch #507

Open
measwel opened this issue Jun 11, 2024 · 59 comments
Open

Cannot connect to Smart Switch #507

measwel opened this issue Jun 11, 2024 · 59 comments
Labels
enhancement New feature or request

Comments

@measwel
Copy link

measwel commented Jun 11, 2024

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
Copy link
Collaborator

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
Copy link
Author

measwel commented Jun 11, 2024

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
Copy link
Author

measwel commented Jun 11, 2024

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
Copy link
Owner

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
Copy link
Collaborator

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
Copy link
Author

measwel commented Jun 12, 2024

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
Copy link
Author

measwel commented Jun 15, 2024

@jasonacox

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

https://github.com/measwel/darkroom

Would very much appreciate your feedback.

@jasonacox
Copy link
Owner

Nice job @measwel !

@measwel
Copy link
Author

measwel commented Jun 16, 2024

@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
Copy link
Author

measwel commented Jun 16, 2024

@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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 16, 2024

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
Copy link
Author

measwel commented Jun 16, 2024

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
Copy link
Author

measwel commented Jun 16, 2024

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
Copy link
Author

measwel commented Jun 16, 2024

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
Copy link
Owner

jasonacox commented Jun 16, 2024

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
Copy link
Author

measwel commented Jun 16, 2024

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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 17, 2024

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
Copy link
Author

measwel commented Jun 24, 2024

@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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Author

measwel commented Jun 24, 2024

@jasonacox
Copy link
Owner

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
Copy link
Author

measwel commented Jun 24, 2024

Updated the links. Will do the forced scan now.

@jasonacox
Copy link
Owner

Sadly, link still show:
image

@measwel
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Owner

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

@measwel
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 24, 2024

xx

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

@jasonacox
Copy link
Owner

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
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 24, 2024

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
Copy link
Owner

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
Copy link
Author

measwel commented Jun 25, 2024

@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
Copy link
Author

measwel commented Jun 25, 2024

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
Copy link
Author

measwel commented Jun 25, 2024

@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
Copy link
Author

measwel commented Jun 25, 2024

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
Copy link
Owner

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

@measwel
Copy link
Author

measwel commented Jun 26, 2024

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

@uzlonewolf
Copy link
Collaborator

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
Copy link
Author

measwel commented Jun 26, 2024

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

Good: it works 😄.

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
Copy link
Author

measwel commented Jun 26, 2024

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
Copy link
Author

measwel commented Jun 26, 2024

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.

@uzlonewolf
Copy link
Collaborator

Yes, the force-scan does something similar: it waits a few seconds to look for broadcast packets, and then starts trying every IP address it did not hear a broadcast from. It goes one step further and uses non-blocking sockets and select() to interrogate 10 devices in parallel every main loop tick (shakes out to around 100 simultaneous requests). Once a connection is established it starts trying every device version and local key combination (minus the keys it already found) until it gets a match.

@uzlonewolf
Copy link
Collaborator

uzlonewolf commented Jun 27, 2024

I noticed it made no difference whether I used outlet or bulb for the connection test.

OutletDevice is going to be a little quicker as the BulbDevice makes a connection and calls status() to try and figure out what bulb type it is as soon as the BulbDevice() object is created.

Personally I prefer to use tinytuya.Device() when the device type is not known as I feel it makes it a bit more obvious when reading the code that the device type is not known yet.

@uzlonewolf
Copy link
Collaborator

uzlonewolf commented Jun 28, 2024

I'm not too hopeful, but, if you run this in one terminal:

import tinytuya
import socket
import time

bcast = b'{"from":"app","ip":"192.168.51.1"}'


bcast_port = 7000
bcast_to = '255.255.255.255'
bcast_idx = 0
bcast_cmd = 0x25
bcast_retcode = None
bcast_msg = tinytuya.TuyaMessage( bcast_idx, bcast_cmd, bcast_retcode, bcast, 0, True, tinytuya.PREFIX_6699_VALUE, True )
bcast_packet = tinytuya.pack_message( bcast_msg, hmac_key=tinytuya.udpkey )

bsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
bsock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

while True:
    print("broadcast encrypted=%r" % bcast_packet.hex() )
    bsock.sendto( bcast_packet, (bcast_to, bcast_port) )
    bsock.sendto( bcast_packet, (bcast_to, bcast_port) )
    time.sleep(5)

Does python3 -m tinytuya scan in another then pick up those new bulbs?

@measwel
Copy link
Author

measwel commented Jun 28, 2024

Okay, the Terminal was firing broadcast messages such as:
broadcast encrypted='xxxxxxx'

In the other Terminal I did a scan:

Unknown v3.4 Device Product ID = tgkr1bwom0jcn3cx [Valid Broadcast]:
Address = 192.168.51.15 Device ID = bf01e82420703431551cwm (len:22) Local Key = Version = 3.4 Type = default, MAC =
No Stats for 192.168.51.15: DEVICE KEY required to poll for status
New Broadcast from App at 192.168.51.4 - {'from': 'app', 'ip': '192.168.51.1'}
Scan completed in 18.0288 seconds

Scan Complete! Found 1 devices.
Broadcasted: 1
Versions: 3.4: 1
Unknown Devices: 1

The bulbs and the outlet switch were not found. What it did find, (192.168.51.15) is the light intensity sensor. But that thing was also found by a regular scan before.

@uzlonewolf
Copy link
Collaborator

Yeah, I figured that wasn't going to work, but thought it was worth a shot. I ordered those AliExpress devices and they should get here today so hopefully I'll be able to figure out what's going on.

@uzlonewolf
Copy link
Collaborator

My devices came in and I made some progress. The problem with these new devices is 2-fold: they do not broadcast at all unless they receive a "from: app" broadcast on port 7000, and they broadcast to port 7000. Receiving their broadcasts is a minor change to the scanner as it already listens on port 7000 for apps, but sending a broadcast to get the devices to start broadcasting is turning out to be a bit trickier than I thought.

@measwel
Copy link
Author

measwel commented Jul 5, 2024

Hey @uzlonewolf, interesting results so far. I hold my thumbs for you to find a good way to get them to broadcast. Maybe a useful hint, maybe not: I noticed they do return a ping, so maybe one could use that to filter what IPs to try on the network.

@uzlonewolf
Copy link
Collaborator

uzlonewolf commented Jul 5, 2024

There's no point to pinging them, just attempting a TCP connection with a short time-out does the same thing while also making it so the 2nd step is ready to go should the connection succeed. It currently creates parallel connections to about 50 IP addresses per second, so attempting connections to an entire /24 only takes ~8 seconds (5.12 seconds to open connections plus 3 seconds for the time-out if there's no response from the last IP).

I'll probably be going out of town early next week so I may or may not be able to get sending broadcasts implemented this weekend. It's pretty easy to do on machines which only have 1 IP address, but there's no good way to get a list of IPs in python on multi-interface machines.

@measwel
Copy link
Author

measwel commented Jul 5, 2024

Yeah, got that. Enjoy your time outside of town.

@jasonacox jasonacox added the enhancement New feature or request label Jul 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants