home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
71.78k stars 30.03k forks source link

Broadlink SP4M-US Integration #63853

Closed spencerthayer closed 11 months ago

spencerthayer commented 2 years ago

The problem

The problem is that the Broadlink SP4M-US use FastCon™ Bridging which is just a proprietary mesh network for Broadlink devices. According to Broadlink's documentation FastCon works as follows.

BroadLink 4th gen smart plugs support FastCon™ Bridging which allows devices building their own sub networks with one of them acting as master and others as clients to minimize the connection load of Wi-Fi router. When the device was added in the network. It will look for better network routes in 1-2 minutes and may join a sub network.

This causes issues with the existing Homeassistant integration since we cannot access the switches via their IP address. The following forum posts go into greater detail.

Is it, or could it be possible, to access and control the SP4M-US over the FastCon "sub network" via the device MAC address? It is worth noting that for all users at least one SP4M-US, functioning as the hub for FastCon, does connect to Homeassistant.

What version of Home Assistant Core has the issue?

core-2021.11.5

What was the last working version of Home Assistant Core?

core-2021.11.5

What type of installation are you running?

Home Assistant OS

Integration causing the issue

Broadlink

Link to integration documentation on our website

https://www.home-assistant.io/integrations/broadlink#switch

Additional information

The advised hack were users disconnect the each SP4M-US from the internet via the router to prevent FastCon does not work for the latest SP4M-US firmware and as of this posting there is no existing work around.

probot-home-assistant[bot] commented 2 years ago

broadlink documentation broadlink source (message by IssueLinks)

probot-home-assistant[bot] commented 2 years ago

Hey there @danielhiversen, @felipediel, @l-i-am, mind taking a look at this issue as it has been labeled with an integration (broadlink) you are listed as a code owner for? Thanks! (message by CodeOwnersMention)

spencerthayer commented 2 years ago

Anything y'all?

OMVMMG commented 2 years ago

Has anyone managed to get the current and power sensors working? Plugs are active using different accounts to avoid the mesh issue but neither provide data for sensors even though the sensors are recognised by Home Assistant.

felipediel commented 2 years ago

Hi. We are adding support for the FastCon technology: https://github.com/mjg59/python-broadlink/pull/669.

Could you help with tests? I don't have the device.

spencerthayer commented 2 years ago

@felipediel absolutely I'll help. @OMVMMG if you can contribute too that would be awesome.

OMVMMG commented 2 years ago

@spencerthayer Happy to help. Please let me know.

felipediel commented 2 years ago

Awesome, thanks! Instructions: https://github.com/mjg59/python-broadlink/pull/669#issuecomment-1073127017

megabitus98 commented 2 years ago

Hi. Any update here?

spencerthayer commented 2 years ago

@megabitus98 they are working on it https://github.com/mjg59/python-broadlink/pull/669#issuecomment-1090579519

github-actions[bot] commented 2 years ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

spencerthayer commented 2 years ago

Unless I've missed it, this issue still isn't resolved. Please keep it open.

issue-triage-workflows[bot] commented 1 year ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

felipediel commented 1 year ago

Let's keep it open, please. We still need to find a way to discover the subdevices. I don't have time to dedicate to this right now, any help would be appreciated.

felipediel commented 1 year ago

/still valid

TanTrungNguyen commented 1 year ago

felipediel,

Are you using the DID (device unique identifier) with the SDK dnaControl after AddDevice? https://docs.ibroadlink.com/public/appsdk_en/appsdk_05/#api10 See section 2.7.5

felipediel commented 1 year ago

We don't use the sdk, but we have something similar. Our struggle is one step before addDevice, the question is how to discover the subdevices?

They possibly come in the end of the payload when we discover the master, I just don't have any FastCon device to confirm.

TanTrungNguyen commented 1 year ago

I have 3 SP4M-US that I can control from the Broadlink App. None of them have sub device identified from the Broadlink App.

Trying the code

import broadlink as blk device = blk.hello("192.168.0.16") # Example IP address device.auth() subdevices = device.get_subdevices() did = subdevices[0].get("did") device.get_state(did) device.set_state(did, pwr=1) device.set_state(did, pwr=0) But it fails at device.get_subdevices() saying get_subdevices does not exist

Since I know the DID for each of my device from the Broadlink App Could you use the DID directly in the get_state and set_state?

{"Device own info":{"DID":"00000000000000000000a043b0d5c527","Data Cloud":"American server","Device IP":"192.168.2.110@80","Firmware":"v57218","IoT Cloud":"American server","MAC":"a0:43:b0:d5:c5:27","PID":"0000000000000000000000008b640000","Plug-in version":"1.5.6","Routine traits":"Trigger and action","S/N code":"","SDK":"2.16.35"}}

Kinetic69 commented 1 year ago

We don't use the sdk, but we have something similar. Our struggle is one step before addDevice, the question is how to discover the subdevices?

They possibly come in the end of the payload when we discover the master, I just don't have any FastCon device to confirm. @felipediel Mate I'll send you some devices.

felipediel commented 1 year ago

@Kinetic69 Thanks mate, I appreciate your support, but I am not sure they make these plugs to my country's specifications, we have a very specific plug unfortunately. Could you run some code with your devices instead? They need to be FastCon enabled devices configured as subdevices of a master device.

These are 3 hypotheses I have based on how other Broadlink devices work:

Test 1 - Public discovery

import socket

def discover(ip_addr):
    conn = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    conn.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    conn.settimeout(10)
    packet = bytearray(0x30)
    packet[0x26] = 6
    checksum = sum(packet, 0xBEAF) & 0xFFFF
    packet[0x20:0x22] = checksum.to_bytes(2, "little")
    conn.sendto(packet, (ip_addr, 80))
    resp = conn.recvfrom(1024)
    conn.close()
    return resp

resp = discover("192.168.0.16")  # Example master device IP address
print(resp)

Test 2 - S2K-like discovery

import broadlink as blk
from broadlink import exceptions as e

def get_subdevices(device) -> dict:
    packet = bytearray(16)
    packet[0] = 0x06
    response = device.send_packet(0x6A, packet)
    e.check_error(response[0x22:0x24])
    payload = device.decrypt(response[0x38:])
    count = payload[0x4]
    subdevice_data = payload[0x6:]
    subdevices = [
        bytearray(subdevice_data[i * 83 : (i + 1) * 83])
        for i in range(len(subdevice_data) // 83)
    ]
    return {
        "count": count,
        "subdevices": [
            {
                "status": subdevice[0],
                "name": subdevice[4:26].decode().strip("\x00"),
                "type": subdevice[3],
                "order": subdevice[1],
                "serial": subdevice[26:30].hex(),
            }
            for subdevice in subdevices
            if any(subdevice[26:30])
        ],
    }

device = blk.hello("192.168.0.16")  # Example master device IP address
device.auth()
resp = get_subdevices(device)
print(resp)

Test 3 - S3-like discovery

import broadlink as blk
from broadlink import exceptions as e

def get_subdevices(device):
    state = {"count": 5, "index": 0}
    packet = device._encode(14, state)
    resp = device.send_packet(0x6A, packet)
    e.check_error(resp[0x22:0x24])
    return device._decode(resp)

device = blk.hello("192.168.0.16")  # Example master device IP address
device.auth()
resp = get_subdevices(device)
print(resp)
TanTrungNguyen commented 1 year ago

Test 1 - Public discovery response (b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb2\xd0\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8bdl\x02\xa8\xc0e\xb1\xd5\xb0C\xa025739\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01E\x9a\x9aE\x02\x00\x00\x00\x1c\xf1[E\xdf\x0b\x00\x00\x02\x00\xa0C\xb0\xb7iL\xa0C\xb0\xd5\xc5'", ('192.168.2.108', 80))

felipediel commented 1 year ago

Awesome, thanks a lot! We found something, possibily a subdevice in the end of the payload:

>>> r = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb2\xd0\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8bdl\x02\xa8\xc0e\xb1\xd5\xb0C\xa025739\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01E\x9a\x9aE\x02\x00\x00\x00\x1c\xf1[E\xdf\x0b\x00\x00\x02\x00\xa0C\xb0\xb7iL\xa0C\xb0\xd5\xc5'"
>>> r[0x80:].hex()
'459a9a45020000001cf15b45df0b00000200a043b0b7694ca043b0d5c527'

How many subdevices that you have? Do you recognize any mac address or product id in this string?

TanTrungNguyen commented 1 year ago

I have 3 devices with the following MAC a0:43:b0:b7:69:4c a0:43:b0:d5:c5:27 a0:43:b0:d5:b1:65 All 3 devices have as Device IP 192.168.2.108@80 but none has any sub device listed

felipediel commented 1 year ago

Making some progress... the third device is the master. The MAC addresses of the first two devices start from byte 18.

>>> r = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb2\xd0\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8bdl\x02\xa8\xc0e\xb1\xd5\xb0C\xa025739\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01E\x9a\x9aE\x02\x00\x00\x00\x1c\xf1[E\xdf\x0b\x00\x00\x02\x00\xa0C\xb0\xb7iL\xa0C\xb0\xd5\xc5'"
>>> b = r[0x80:]

>>> m1 = bytes.fromhex("a043b0b7694c")
>>> m2 = bytes.fromhex("a043b0d5c527")
>>> m3 = bytes.fromhex("a043b0d5b165")
>>> b.find(m1)
18
>>> b.find(m2)
24
>>> b.find(m3)
-1  # The third device is the master

>>> len(b)
30

# MAC address 1
>>> b[18:24].hex()
'a043b0d5c527'

# MAC address 2
>>> b[24:30].hex()
'a043b0d5c527'

# Unknown bytes
>>> b[:18].hex()
'459a9a45020000001cf15b45df0b00000200'

We still need to decode the first 18 bytes (unknown bytes). The first 4 bytes are the header:

>>> b[:4].hex()
'459a9a45'

Byte 4 to 8 is the number of devices in little-endian:

>>> int.from_bytes(b[4:8], 'little')
2

We still need to figure this out:

>>> b[8:18].hex()
'1cf15b45df0b00000200'

Do you recognize anything in this string? Do you know your product ids (devtype)? You can check them in the device info in the official app.

TanTrungNguyen commented 1 year ago

Don't recognize byte 8:18 Here is an export of the device info and the printscreen has the product ID

{"Device own info":{"DID":"00000000000000000000a043b0d5b165","Data Cloud":"American server","Device IP":"192.168.2.114@80","Firmware":"v57218","IoT Cloud":"American server","MAC":"a0:43:b0:d5:b1:65","PID":"0000000000000000000000008b640000","Plug-in version":"1.5.6","Routine traits":"Trigger and action","S/N code":"","SDK":"2.16.35"}}

Screenshot_20230502-110244

Kinetic69 commented 1 year ago

Sorry I don't have any WiFi enabled BL globes, only FastCon BLE enabled which I'm trying to get working.

You don't have E27 or Bayonet globe sockets in Brazil?

ovionlogis commented 1 year ago

@felipediel I tried running the code on my environment and got similar results

>>> r = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\xd2\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x13%:\x8ba\x0bX\xa8\xc0\x915\x00\xb0C\xa0Smart Plug 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00E\x9a\x9aE\x02\x00\x00\x00\xca\xd46RR\x0b\x00\x00\x02\x00\xa0C\xb0/\xf8\x19\xa0C\xb0\x00'\xfe"
>>> b = r[0x80:]

>>> b.hex()
'459a9a4502000000cad43652520b00000200a043b02ff819a043b00027fe'

MAC address 1

>>> b[18:24].hex()
'a043b02ff819'

MAC address 2

>>> b[24:30].hex()
'a043b00027fe'

Unknown bytes

>>> b[:18].hex()
'459a9a4502000000cad43652520b00000200'

This part is updated on every request

>>> b[8:12].hex()
'cad43652'
ovionlogis commented 1 year ago

@felipediel

Byte 16 to 18 is the number of devices in little-endian:

>>> int.from_bytes(b[16:18], 'little')
2
felipediel commented 1 year ago

@ovionlogis Thanks!

b[8:12] is probably a checksum. I tried adler32 and crc32, no one matched. b[12] possibly some kind of flag and b[13] marks the end of the metadata.

I think it's possible to ignore the metadata and go straight to the mac addresses. I wish we could retrieve the device type though, I think they send another packet after this to get the type.

@Kinetic69 Yes, but they are e27 110v. I think Broadlink only makes e27 220v or e26 110v.

Tdubs3 commented 1 year ago

Any chance I could assist in implementing this same approach via BLE? I have a GW4C hub that will control devices via the cloud but I really want all of this to live locally. I’m not super savvy in decoding but have the skills and hardware to hopefully push this integration into the BLE side of things too.

spencerthayer commented 1 year ago

I've put my Broadlink plugs in a box and forgot about them until one day I realized I threw them away. I am glad to see this issue getting some attention but at this point I think it is best for the Home Assistant enthusiast to stay away from Broad-link devices.

issue-triage-workflows[bot] commented 11 months ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.