Closed brianyulke closed 2 years ago
I think that the most important thing here is not what I want, we are here to work for the community, this buttons are cheap and very versatile so in my opinion the best way to handle this devices is to have realtime notification when a button is clicked, so you can do almost anything when a button is clicked. What do you think?
Code in device.py should work for the listener almost out of the box. If you run
devices = broadlink.discover()
And while you are discovering press a button could you please print the response. Could you try doing it a couple of times using different buttons
def scan(
timeout: int = DEFAULT_TIMEOUT,
local_ip_address: str = None,
discover_ip_address: str = DEFAULT_BCAST_ADDR,
discover_ip_port: int = DEFAULT_PORT,
) -> t.Generator[HelloResponse, None, None]:
"""Broadcast a hello message and yield responses."""
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)
if local_ip_address:
conn.bind((local_ip_address, 0))
port = conn.getsockname()[1]
else:
local_ip_address = "0.0.0.0"
port = 0
packet = bytearray(0x30)
packet[0x08:0x14] = Datetime.pack(Datetime.now())
packet[0x18:0x1C] = socket.inet_aton(local_ip_address)[::-1]
packet[0x1C:0x1E] = port.to_bytes(2, "little")
packet[0x26] = 6
checksum = sum(packet, 0xBEAF) & 0xFFFF
packet[0x20:0x22] = checksum.to_bytes(2, "little")
start_time = time.time()
discovered = []
try:
while (time.time() - start_time) < timeout:
time_left = timeout - (time.time() - start_time)
conn.settimeout(min(DEFAULT_RETRY_INTVL, time_left))
conn.sendto(packet, (discover_ip_address, discover_ip_port))
while True:
try:
resp, host = conn.recvfrom(1024)
except socket.timeout:
break
devtype = resp[0x34] | resp[0x35] << 8
mac = resp[0x3A:0x40][::-1]
if (host, mac, devtype) in discovered:
continue
discovered.append((host, mac, devtype))
name = resp[0x40:].split(b"\x00")[0].decode()
is_locked = bool(resp[0x7F])
yield devtype, host, mac, name, is_locked
finally:
conn.close()
It will print that it's found the S3 hub but it's the payload that is interesting - In the case of the response you received above 34 32 35 37 33
which is decoded into the variable name above perhaps you could print that out because the code discards duplicate responses that finds based on host, device type and MAC address
Ok, i changed scan function and i tried your code:
import broadlink
buttonsDid = "00000000000000000000ec0bae371ee1"
def main():
print("Hello World!")
devices = broadlink.discover()
print(devices)
if __name__ == "__main__":
main()
this is what it print:
[broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False)]
Did you allow multiple entries based on name as well? Did you press a couple of buttons during discovery? Could you also please print packet.tohex() before it is sent
what you mean with "Did you allow multiple entries based on name as well?" Yes i clicked all buttons, and when i clicked the button 1 i received the notification on the phone.
this is packet.hex()
000000000000000001000000e607281316050b0200000000000000000000000006c00000000006000000000000000000
this is packet
bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xe6\x07)\x13\x16\x05\x0b\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00')
if (host, mac, devtype) in discovered:
continue
discovered.append((host, mac, devtype))
Will discard multiple entries even if the name is different.
I think we might need to take a step back. Could you try the packet capture again and see if there is a difference in the response when a different button is pressed
Look at the packet.hex and compare it to the request from your number one capture above, close but not quite the same..
We know how to send the packet to request a button press but we don't know how to fully populate the packet; notice how datetime and checksums are added to the packet and so that will be some of the differences but it doesn't account for all of it.
It would be great if you could copy and paste the hex request responses rather than using an image
oh sure, this is the number 1 image export file: https://we.tl/t-VQyDta74lu
Something went wrong, you can just copy and paste the hex strings using normal copy and paste,
if (host, mac, devtype) in discovered: continue discovered.append((host, mac, devtype))
Will discard multiple entries even if the name is different.
I think we might need to take a step back. Could you try the packet capture again and see if there is a difference in the response when a different button is pressed
Wait, wait, wait, i removed the code that skip if a device with the same name already exist and boom:
[broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False), broadlink.hub.s3(('192.168.1.74', 80), mac=b'\xec\x0b\xae8\xfc\x83', devtype=42573, timeout=10, name='42573', model='S3', manufacturer='Broadlink', is_locked=False)]
I know i clicked too many times lol
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 28 16 Z..UZ..U ......(
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
ef c4 00 00 00 00 06 00 00 00 00 00 00 00 00 00 ........ ........
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 28 16 Z..UZ..U ......(
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
05 cd 00 00 00 00 07 00 00 00 00 00 00 00 00 00 ........ ........
4d 59 0e 58 4d a6 4a 01 a8 c0 83 fc 38 ae 0b ec MY.XM.J. ....8...
34 32 35 37 33 00 00 00 00 00 00 00 00 00 00 00 42573... ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 ........ ........
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 29 16 Z..UZ..U ......)
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
f0 c4 00 00 00 00 06 00 00 00 00 00 00 00 00 00 ........ ........
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 29 16 Z..UZ..U ......)
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
06 cd 00 00 00 00 07 00 00 00 00 00 00 00 00 00 ........ ........
4d 59 0e 58 4d a6 4a 01 a8 c0 83 fc 38 ae 0b ec MY.XM.J. ....8...
34 32 35 37 33 00 00 00 00 00 00 00 00 00 00 00 42573... ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 ........ ........
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 2a 16 Z..UZ..U ......*
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
f1 c4 00 00 00 00 06 00 00 00 00 00 00 00 00 00 ........ ........
5a a5 aa 55 5a a5 aa 55 01 00 00 00 e6 07 2a 16 Z..UZ..U ......*
14 04 0a 02 00 00 00 00 01 08 00 0a 3a a1 00 00 ....... ....:...
07 cd 00 00 00 00 07 00 00 00 00 00 00 00 00 00 ........ ........
4d 59 0e 58 4d a6 4a 01 a8 c0 83 fc 38 ae 0b ec MY.XM.J. ....8...
34 32 35 37 33 00 00 00 00 00 00 00 00 00 00 00 42573... ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 ........ ........
No, I cheered for nothing, tried to run again the program, this time without click a button and the response is the same :( So is the same packet
Yes, can you go back to capturing packets and seeing if you can see a difference between one button press or another, you may need to go back to Wireshark although I think you should try the phone packet capture first
date-time local-ip cs 6
0000000000000000 01000000e6072813 16050b0200000000 0000000000000000 06c0000000000600 0000000000000000
5aa5aa555aa5aa55 01000000e6072816 14040a0200000000 0108000a3aa10000 efc4000000000600 0000000000000000
packet = bytearray(0x30)
packet[0x08:0x14] = Datetime.pack(Datetime.now())
packet[0x18:0x1C] = socket.inet_aton(local_ip_address)[::-1]
packet[0x1C:0x1E] = port.to_bytes(2, "little")
packet[0x26] = 6
checksum = sum(packet, 0xBEAF) & 0xFFFF
packet[0x20:0x22] = checksum.to_bytes(2, "little")
Think you captured a discovery packet on the first attempt
Is the payload in the response: 0x3432353733
- is that cheeky Chinese character doing there
sorry for the late reply, I am continuing to test reading with packet capture, but I am unable to bind a package to the click of a button. Very good job @stevendodd, so what does it mean? LOL
It's the name of the S3 hub you printed above in ASCII
I can't reproduce the same thing when a button is clicked, how can i export to another format?
If the button click is not going to the phone/app then the only way we'll be to route traffic from the button via somewhere where you can intercept it to the hub. You might try listening for udp on wireshark but I'm not sure it's going to be broadcast
The button uses a Broadlink BL-3358-P that is a simple wifi card, but i don't find any info online. With wireshark i can't intercept any message to and from the ip of the hub, i don't know why
The button uses a Broadlink BL-3358-P that is a simple wifi card, but i don't find any info online. With wireshark i can't intercept any message to and from the ip of the hub, i don't know why
How did you find out about the Wi-Fi card is the documentation somewhere - even just stating that it uses this chip?
The fact that the button uses the smart hub means that there is some communication between them, perhaps it's just setting up the Wi-Fi connection? Perhaps the button communicates directly to the Broadlink servers. I'm wondering if my smart light switches also have the same Wi-Fi card
https://usermanual.wiki/Hangzhou-BroadLink-Technology/BL3358-P supports WEP/WPA so certainly has the ability to communicate directly with the servers
If there is no internal traffic between the hub and the smart button then you may have to resort to https://ifttt.com
i just opened the SR3-4KEY and inside i saw this chip. the chip is a simple 2.4ghz wireless but i think that it send and receive commands from the hub via a channel different from simple wifi, so the hub is needed to comunicate to the server. I saw this user manual but i don't know how to change fw in the button
You smart light will turn on from the app even when there is not an active internet connection right?
I suspect the hub when you pair it it will set up the Wi-Fi and then do nothing else unless you can capture a packet, we are stuck
but if the button were connected directly to the home wifi I think I would see it in my router, among the connected devices
That's true; you need to intercept the packet between the hub and the button
i can confirm that the device is not connected directly with my wifi, i don't know how to intercept something that is not standard wifi LOL
You smart light will turn on from the app even when there is not an active internet connection right?
please reply
Yes, just needs Wi-Fi
i think we have to stick with a server that poll every x ms the hub and have a copy of the previous state of the buttons. then it can say if the last state was 0 and now is 1, the button was clicked. i don't like this type of solutions but...
i didn't want to give up so i restart again! So i spin up a router and connected only my phone and the hub, started wireshark to sniff the packet and i think that this time i was able to mark the message to the button click, so this is the pcap file from wireshark, in the comments of the packetyou will find something like "Starting button1" and "finish button1" this is to mark when i clicked the buttons https://we.tl/t-VQ9onb1xsX
Those downloads don't work they seem to be just zero byte files
Do you have wireshark installed? because i tried downloading the file and it works try this https://we.tl/t-2rutsoqyml
That works what is the IP address of the hub and button please
This is the ip address of the hub: 192.168.137.219 like i said before the button DID NOT connect to the standard WiFi protocol, directly to the router, so it doesn't have an ip address
just to be more clear it think that the button send a msg to the hub via a non standard 2.4ghz protocol, than the hub broadcast a msg to the local network
how did you mark the before/finish packets
Before Button# means that this is the last capture BEFORE i clicked the button, so you see Before -> go to the next nsg and you will see the msg from when i clicked the button Unlike Before, Finish means that this is the last packet captured AFTER the button was clicked but before some time so this is the last msg in the ButtonClick msgs I hope I explained myself
How do you know did you put a comment on a live feed press the button and then comment the live feed after the press, was your phone 192.168.137.64
yes, my phone is .64. this file is the record of this actions:
Please start with looking at packet 1 and 4. there is 3 packets for each button press
If I remove your phone from the traffic then there is only this UDP traffic in between where you have marked the button presses. The packets don't seem to conform to the protocol that is documented in this repository but I'm not 100% sure about that yet. I still don't understand how you can be sure that you marked the before/after correctly
i'm 99% sure because for some time no packets show up in this record, than i clicked the button and these 3 packet show up. i know this is not a scientific test
The packets seem to start with the Mac addresses of each of your devices can you please confirm Mac address of the button it should be in your app as the DID
this is the did of the button: 00000000000000000000ec0bae371ee1
rather than trying to decode it why don't you just try sending this packet to your hub via the Python libraries - It may have a timestamp inclued it so it might not work because of it:
ec0bae38fc8306ea56f0fcb70800450000b867f8400030117328037a2117c0a889db0714400200a478a6d27fdf0201009c00868f13a7392170aaf3d990fd30d313a7878f13a50555681886ac47fc653a48c372132d0e50e4c343429702b5592f01830f4ba79abf120492fdd8dc585fba6d5a7135e7d6df9c5132c297845d91cdbb4d6ec51601dbc6b291f048a9faa5009ada12cf98e468d3dc20a4faca36bf5a9b544487fad24aa88a1688a9d1a4454601914d9524994bd344f85253174b0fae64a7868c972f
sorry, i don't understand you, my goal is to be able to write a program that can listen to these messages and understand which button was pressed, what do i get by sending this message to the hub?
trying to simulate a button press
Ok so i created a new function in device.py that send a packet as argument to the function:
def send_fake(self, packet: bytes):
with self.lock and socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as conn:
timeout = self.timeout
start_time = time.time()
while True:
time_left = timeout - (time.time() - start_time)
conn.settimeout(min(DEFAULT_RETRY_INTVL, time_left))
conn.sendto(packet, self.host)
try:
resp = conn.recvfrom(2048)[0]
break
except socket.timeout as err:
if (time.time() - start_time) > timeout:
raise e.NetworkTimeoutError(
-4000,
"Network timeout",
f"No response received within {timeout}s",
) from err
if len(resp) < 0x30:
raise e.DataValidationError(
-4007,
"Received data packet length error",
f"Expected at least 48 bytes and received {len(resp)}",
)
nom_checksum = int.from_bytes(resp[0x20:0x22], "little")
real_checksum = sum(resp, 0xBEAF) - sum(resp[0x20:0x22]) & 0xFFFF
if nom_checksum != real_checksum:
raise e.DataValidationError(
-4008,
"Received data packet check error",
f"Expected a checksum of {nom_checksum} and received {real_checksum}",
)
return resp
and them a script to execute:
import broadlink
buttonsDid = "00000000000000000000ec0bae371ee1"
def main():
print("Hello World!")
device = broadlink.hello('192.168.137.219')
device.auth()
subD = device.get_subdevices()
print(device.send_fake(bytes.fromhex("ec0bae38fc8306ea56f0fcb70800450000b867f8400030117328037a2117c0a889db0714400200a478a6d27fdf0201009c00868f13a7392170aaf3d990fd30d313a7878f13a50555681886ac47fc653a48c372132d0e50e4c343429702b5592f01830f4ba79abf120492fdd8dc585fba6d5a7135e7d6df9c5132c297845d91cdbb4d6ec51601dbc6b291f048a9faa5009ada12cf98e468d3dc20a4faca36bf5a9b544487fad24aa88a1688a9d1a4454601914d9524994bd344f85253174b0fae64a7868c972f")))
if __name__ == "__main__":
main()
this is the last print output:
b'\xec\x0b\xae8\xfc\x83\x06\xeaV\xf0\xfc\xb7\x08\x00E\x00\x00\xb8g\xf8@\x000\x11s(\x03z!\x17\xc0\xa8\x0c\xd5\xf9\xff@\x02\x84\xa7x\xa6\xd2\x7f\xdf\x02\x01\x00\x9c\x00\x86\x8f\x13\xa79!'
Please add support for the S3 hub - 0xa59c. This hub accompanies the Smart Light Switch TC3 (in my case, the TC3-US-1).