Summary
setup() creates a UDP socket and sends the provisioning payload without ever binding it to a local address:
https://github.com/mjg59/python-broadlink/blob/master/broadlink/__init__.py#L298
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(payload, (ip_address, DEFAULT_PORT))
On a host with more than one active interface, an unbound socket sending to 255.255.255.255 leaves via the default route, not via the interface attached to the device's BroadlinkProv AP. The call returns successfully, nothing is raised, and the device is simply never provisioned — a silent failure that is very hard to attribute.
This is inconsistent with discover() / xdiscover(), which already expose local_ip_address for exactly this reason.
How it shows up in practice
Provisioning a device from a laptop that is simultaneously on the device AP (Wi-Fi) and on a USB/Ethernet uplink is a completely ordinary setup — you need the second interface to keep working while the Wi-Fi is occupied by the AP. In that configuration setup() never reaches the device.
Diagnosed on macOS with Wi-Fi on the device AP (192.168.10.2) and an active USB tether holding the default route. setup() reported success; the device never associated. Binding the socket to the Wi-Fi address and sending to the subnet broadcast made it work on the first try.
Steps to reproduce
- Host with two active interfaces, default route on interface B.
- Put a Broadlink device in AP mode; connect interface A to
BroadlinkProv (host gets 192.168.10.x).
- Call
broadlink.setup(ssid, password, 3).
- Packet is transmitted on interface B. Device is never configured, no error raised.
Suggested fix
Accept an optional local address and bind, mirroring discover():
def setup(
ssid: str,
password: str,
security_mode: int,
ip_address: str = DEFAULT_BCAST_ADDR,
local_ip_address: Optional[str] = None,
) -> None:
...
if local_ip_address:
sock.bind((local_ip_address, 0))
sock.sendto(payload, (ip_address, DEFAULT_PORT))
Sending to the subnet broadcast in addition to 255.255.255.255 also helps on hosts that drop limited broadcasts.
Happy to open a PR if the approach looks right.
Summary
setup()creates a UDP socket and sends the provisioning payload without ever binding it to a local address:https://github.com/mjg59/python-broadlink/blob/master/broadlink/__init__.py#L298
On a host with more than one active interface, an unbound socket sending to
255.255.255.255leaves via the default route, not via the interface attached to the device'sBroadlinkProvAP. The call returns successfully, nothing is raised, and the device is simply never provisioned — a silent failure that is very hard to attribute.This is inconsistent with
discover()/xdiscover(), which already exposelocal_ip_addressfor exactly this reason.How it shows up in practice
Provisioning a device from a laptop that is simultaneously on the device AP (Wi-Fi) and on a USB/Ethernet uplink is a completely ordinary setup — you need the second interface to keep working while the Wi-Fi is occupied by the AP. In that configuration
setup()never reaches the device.Diagnosed on macOS with Wi-Fi on the device AP (
192.168.10.2) and an active USB tether holding the default route.setup()reported success; the device never associated. Binding the socket to the Wi-Fi address and sending to the subnet broadcast made it work on the first try.Steps to reproduce
BroadlinkProv(host gets192.168.10.x).broadlink.setup(ssid, password, 3).Suggested fix
Accept an optional local address and bind, mirroring
discover():Sending to the subnet broadcast in addition to
255.255.255.255also helps on hosts that drop limited broadcasts.Happy to open a PR if the approach looks right.