Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ history below starts at that fork point.

### Changed

- **The library is asynchronous.** Every method that talks to a device is
now a coroutine: `await device.auth()`, `await device.send_data(...)`,
`await device.check_sensors()`, and so on. Discovery is
`await broadlink.discover(...)`, `broadlink.hello(...)` and `setup(...)`
are coroutines, and `xdiscover(...)` is an async generator. The packet
helpers (`pulses_to_data`, `data_to_pulses`), CRC and datetime helpers
stay synchronous. There is no synchronous compatibility layer: a call
without `await` returns a coroutine and does nothing.
- Each device keeps one UDP endpoint for its lifetime (the previous
version opened a socket per call) and serializes requests on it with an
`asyncio.Lock`. The old code declared a lock but never acquired it.
`async with device:` or `await device.aclose()` releases the endpoint;
it reopens on the next call.
- When a device reports that the session key has expired, the library
re-authenticates once and repeats the request. Callers no longer need
their own re-auth loop.
- Retry and timeout behaviour is unchanged: a request is repeated every
second until `timeout` elapses, then `NetworkTimeoutError` is raised.
- `dooya.set_percentage_and_wait` sleeps with `asyncio.sleep`.
- The CLI tools run their body under `asyncio.run`.
- Packaging moved to `pyproject.toml`; `setup.py` and the stale
`requirements.txt` pin are gone. The distribution name is now
`python-broadlink`; the import name stays `broadlink`. Python 3.13 or
Expand Down
106 changes: 65 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ A Python module and CLI for controlling Broadlink devices locally.
> RM Max and RM5 Plus. Version 1.0 will be asynchronous; see `CHANGELOG.md`.
> Upstream's credit and MIT license are preserved.

## Version 1.0 is asynchronous

Every call that reaches a device is a coroutine and must be awaited. This
is the whole change from the original library's API; method names,
arguments and return values are the same.

```python
import asyncio
import broadlink

async def main():
devices = await broadlink.discover(timeout=5)
device = devices[0]
await device.auth()
print(await device.check_sensors())

asyncio.run(main())
```

Calling a device method without `await` returns a coroutine object and
sends nothing; Python prints a `RuntimeWarning: coroutine ... was never
awaited` when it is garbage collected. If you need the old synchronous
behaviour, pin the original distribution (`broadlink==0.19.0`) instead.

The following devices are supported:

- **Universal remotes**: RM home, RM mini 3, RM plus, RM pro, RM pro+, RM4 mini, RM4 pro, RM4C mini, RM4S, RM4 TV mate
Expand Down Expand Up @@ -42,11 +66,11 @@ environment, remove it first (`pip3 uninstall broadlink`); both provide the

## Basic functions

First, open Python 3 and import this module.
The examples below are written as they would appear inside an `async def`
function run with `asyncio.run(...)`, as in the snippet above. To try them
interactively, start Python with `python3 -m asyncio`, which gives you a
prompt where `await` works at the top level.

```
python3
```
```python3
import broadlink
```
Expand All @@ -63,7 +87,7 @@ In order to control the device, you need to connect it to your local network. If
- Manually connect to the WiFi SSID named BroadlinkProv.
2. Connect the device to your local network with the setup function.
```python3
broadlink.setup('myssid', 'mynetworkpass', 3)
await broadlink.setup('myssid', 'mynetworkpass', 3)
```

Security mode options are (0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
Expand All @@ -72,45 +96,45 @@ Security mode options are (0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)

You may need to specify a broadcast address if setup is not working.
```python3
broadlink.setup('myssid', 'mynetworkpass', 3, ip_address='192.168.0.255')
await broadlink.setup('myssid', 'mynetworkpass', 3, ip_address='192.168.0.255')
```

### Discovery

Use this function to discover devices:

```python3
devices = broadlink.discover()
devices = await broadlink.discover()
```

#### Advanced options
You may need to specify `local_ip_address` or `discover_ip_address` if discovery does not return any devices.

Using the IP address of your local machine:
```python3
devices = broadlink.discover(local_ip_address='192.168.0.100')
devices = await broadlink.discover(local_ip_address='192.168.0.100')
```

Using the broadcast address of your subnet:
```python3
devices = broadlink.discover(discover_ip_address='192.168.0.255')
devices = await broadlink.discover(discover_ip_address='192.168.0.255')
```

If the device is locked, it may not be discoverable with broadcast. In such cases, you can use the unicast version `broadlink.hello()` for direct discovery:
```python3
device = broadlink.hello('192.168.0.16')
device = await broadlink.hello('192.168.0.16')
```

If you are a perfomance freak, use `broadlink.xdiscover()` to create devices instantly:
```python3
for device in broadlink.xdiscover():
async for device in broadlink.xdiscover():
print(device) # Example action. Do whatever you want here.
```

### Authentication
After discovering the device, call the `auth()` method to obtain the authentication key required for further communication:
```python3
device.auth()
await device.auth()
```

The next steps depend on the type of device you want to control.
Expand All @@ -123,12 +147,12 @@ Learning IR codes takes place in three steps.

1. Enter learning mode:
```python3
device.enter_learning()
await device.enter_learning()
```
2. When the LED blinks, point the remote at the Broadlink device and press the button you want to learn.
3. Get the IR packet.
```python3
packet = device.check_data()
packet = await device.check_data()
```

### Learning RF codes
Expand All @@ -137,7 +161,7 @@ Learning RF codes takes place in six steps.

1. Sweep the frequency:
```python3
device.sweep_frequency()
await device.sweep_frequency()
```
2. When the LED blinks, point the remote at the Broadlink device for the first time and long press the button you want to learn.
3. Check if the frequency was successfully identified:
Expand All @@ -148,12 +172,12 @@ if ok:
```
4. Enter learning mode:
```python3
device.find_rf_packet()
await device.find_rf_packet()
```
5. When the LED blinks, point the remote at the Broadlink device for the second time and short press the button you want to learn.
6. Get the RF packet:
```python3
packet = device.check_data()
packet = await device.check_data()
```

#### Notes
Expand All @@ -164,25 +188,25 @@ Universal remotes with product id 0x2712 use the same method for learning IR and

You can exit the learning mode in the middle of the process by calling this method:
```python3
device.cancel_sweep_frequency()
await device.cancel_sweep_frequency()
```

### Sending IR/RF packets
```python3
device.send_data(packet)
await device.send_data(packet)
```

### Fetching sensor data
```python3
data = device.check_sensors()
data = await device.check_sensors()
```

## Switches

### Setting power state
```python3
device.set_power(True)
device.set_power(False)
await device.set_power(True)
await device.set_power(False)
```

### Checking power state
Expand All @@ -199,8 +223,8 @@ state = device.get_energy()

### Setting power state
```python3
device.set_power(1, True) # Example socket. It could be 2 or 3.
device.set_power(1, False)
await device.set_power(1, True) # Example socket. It could be 2 or 3.
await device.set_power(1, False)
```

### Checking power state
Expand All @@ -217,49 +241,49 @@ state = device.get_state()

### Setting state attributes
```python3
devices[0].set_state(pwr=0)
devices[0].set_state(pwr=1)
devices[0].set_state(brightness=75)
devices[0].set_state(bulb_colormode=0)
devices[0].set_state(blue=255)
devices[0].set_state(red=0)
devices[0].set_state(green=128)
devices[0].set_state(bulb_colormode=1)
await devices[0].set_state(pwr=0)
await devices[0].set_state(pwr=1)
await devices[0].set_state(brightness=75)
await devices[0].set_state(bulb_colormode=0)
await devices[0].set_state(blue=255)
await devices[0].set_state(red=0)
await devices[0].set_state(green=128)
await devices[0].set_state(bulb_colormode=1)
```

## Environment sensors

### Fetching sensor data
```python3
data = device.check_sensors()
data = await device.check_sensors()
```

## Hubs

### Discovering subdevices
```python3
device.get_subdevices()
await device.get_subdevices()
```

### Fetching data
Use the DID obtained from get_subdevices() for the input parameter to query specific sub-device.

```python3
device.get_state(did="00000000000000000000a043b0d06963")
await device.get_state(did="00000000000000000000a043b0d06963")
```

### Setting state attributes
The parameters depend on the type of subdevice that is being controlled. In this example, we are controlling LC-1 switches:

#### Turn on
```python3
device.set_state(did="00000000000000000000a043b0d0783a", pwr=1)
device.set_state(did="00000000000000000000a043b0d0783a", pwr1=1)
device.set_state(did="00000000000000000000a043b0d0783a", pwr2=1)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr=1)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr1=1)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr2=1)
```
#### Turn off
```python3
device.set_state(did="00000000000000000000a043b0d0783a", pwr=0)
device.set_state(did="00000000000000000000a043b0d0783a", pwr1=0)
device.set_state(did="00000000000000000000a043b0d0783a", pwr2=0)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr=0)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr1=0)
await device.set_state(did="00000000000000000000a043b0d0783a", pwr2=0)
```
Loading
Loading