python-broadlink 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. python_broadlink-1.0.0/CHANGELOG.md +98 -0
  2. python_broadlink-1.0.0/LICENSE +22 -0
  3. python_broadlink-1.0.0/MANIFEST.in +5 -0
  4. python_broadlink-1.0.0/PKG-INFO +360 -0
  5. python_broadlink-1.0.0/README.md +329 -0
  6. python_broadlink-1.0.0/TROUBLESHOOTING.md +9 -0
  7. python_broadlink-1.0.0/broadlink/__init__.py +342 -0
  8. python_broadlink-1.0.0/broadlink/alarm.py +43 -0
  9. python_broadlink-1.0.0/broadlink/climate.py +474 -0
  10. python_broadlink-1.0.0/broadlink/const.py +5 -0
  11. python_broadlink-1.0.0/broadlink/cover.py +182 -0
  12. python_broadlink-1.0.0/broadlink/device.py +470 -0
  13. python_broadlink-1.0.0/broadlink/exceptions.py +161 -0
  14. python_broadlink-1.0.0/broadlink/helpers.py +43 -0
  15. python_broadlink-1.0.0/broadlink/hub.py +98 -0
  16. python_broadlink-1.0.0/broadlink/light.py +200 -0
  17. python_broadlink-1.0.0/broadlink/protocol.py +50 -0
  18. python_broadlink-1.0.0/broadlink/remote.py +521 -0
  19. python_broadlink-1.0.0/broadlink/sensor.py +90 -0
  20. python_broadlink-1.0.0/broadlink/switch.py +472 -0
  21. python_broadlink-1.0.0/cli/README.md +156 -0
  22. python_broadlink-1.0.0/cli/broadlink_cli +221 -0
  23. python_broadlink-1.0.0/cli/broadlink_discovery +37 -0
  24. python_broadlink-1.0.0/protocol.md +204 -0
  25. python_broadlink-1.0.0/pyproject.toml +66 -0
  26. python_broadlink-1.0.0/python_broadlink.egg-info/PKG-INFO +360 -0
  27. python_broadlink-1.0.0/python_broadlink.egg-info/SOURCES.txt +40 -0
  28. python_broadlink-1.0.0/python_broadlink.egg-info/dependency_links.txt +1 -0
  29. python_broadlink-1.0.0/python_broadlink.egg-info/requires.txt +6 -0
  30. python_broadlink-1.0.0/python_broadlink.egg-info/top_level.txt +1 -0
  31. python_broadlink-1.0.0/setup.cfg +4 -0
  32. python_broadlink-1.0.0/tests/__init__.py +0 -0
  33. python_broadlink-1.0.0/tests/oracle/__init__.py +1 -0
  34. python_broadlink-1.0.0/tests/oracle/cases.py +402 -0
  35. python_broadlink-1.0.0/tests/oracle/fixtures.json +4099 -0
  36. python_broadlink-1.0.0/tests/oracle/harness.py +158 -0
  37. python_broadlink-1.0.0/tests/oracle/record.py +38 -0
  38. python_broadlink-1.0.0/tests/test_capture.py +553 -0
  39. python_broadlink-1.0.0/tests/test_helpers.py +109 -0
  40. python_broadlink-1.0.0/tests/test_oracle.py +57 -0
  41. python_broadlink-1.0.0/tests/test_remote.py +63 -0
  42. python_broadlink-1.0.0/tests/test_transport.py +511 -0
@@ -0,0 +1,98 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are recorded here. The format follows
4
+ Keep a Changelog; versions follow Semantic Versioning.
5
+
6
+ ## 1.0.0 - 2026-09-05
7
+
8
+ This is the first release of `python-broadlink`, a maintained fork of
9
+ `mjg59/python-broadlink` (PyPI `broadlink`, last released as 0.19.0). The
10
+ history below starts at that fork point.
11
+
12
+ ### Changed
13
+
14
+ - **The library is asynchronous.** Every method that talks to a device is
15
+ now a coroutine: `await device.auth()`, `await device.send_data(...)`,
16
+ `await device.check_sensors()`, and so on. Discovery is
17
+ `await broadlink.discover(...)`, `broadlink.hello(...)` and `setup(...)`
18
+ are coroutines, and `xdiscover(...)` is an async generator. The packet
19
+ helpers (`pulses_to_data`, `data_to_pulses`), CRC and datetime helpers
20
+ stay synchronous. There is no synchronous compatibility layer: a call
21
+ without `await` returns a coroutine and does nothing.
22
+ - Each device keeps one UDP endpoint for its lifetime (the previous
23
+ version opened a socket per call) and serializes requests on it with an
24
+ `asyncio.Lock`. The old code declared a lock but never acquired it.
25
+ `async with device:` or `await device.aclose()` releases the endpoint;
26
+ it reopens on the next call.
27
+ - When a device reports that the session key has expired, the library
28
+ re-authenticates once and repeats the request. Callers no longer need
29
+ their own re-auth loop.
30
+ - Retry and timeout behaviour is unchanged: a request is repeated every
31
+ second until `timeout` elapses, then `NetworkTimeoutError` is raised.
32
+ - `dooya.set_percentage_and_wait` sleeps with `asyncio.sleep`.
33
+ - The CLI tools run their body under `asyncio.run`. `broadlink_cli
34
+ --learn` and `--rflearn` use `capture()` / `capture_rf()`, so a learning
35
+ session no longer goes deaf when the device times out partway through;
36
+ `--window` sets how long to listen, `--keep` prints every code heard, and
37
+ `--send --durations --repeat N` sets the repeat count. The CLI README's
38
+ `--rfscanlearn` was a typo for `--rflearn` (mjg59/python-broadlink#803,
39
+ #830).
40
+ - `pulses_to_data` returns `bytes` (it returned a `bytearray`, against its
41
+ own annotation).
42
+ - Packaging moved to `pyproject.toml`; `setup.py` and the stale
43
+ `requirements.txt` pin are gone. The distribution name is now
44
+ `python-broadlink`; the import name stays `broadlink`. Python 3.13 or
45
+ newer is required.
46
+ - Continuous integration now runs `ruff` and `pytest` on Python 3.13 and
47
+ 3.14, and builds the sdist and wheel on every pull request. Releases are
48
+ published to PyPI from version tags using trusted publishing.
49
+
50
+ ### Fixed
51
+
52
+ - The IR tick constant used by `pulses_to_data` and `data_to_pulses` is now
53
+ `TICK = 8192 / 269` (about 30.45 us), matching the device's 32768 Hz
54
+ timebase as documented in `protocol.md`. The previous value, 32.84, was
55
+ the inverse ratio applied the wrong way round and compressed IR codes
56
+ built from true microsecond timings by about 7 percent. Codes learned and
57
+ replayed through the same device were unaffected. Verified on an RM4 Pro
58
+ against an independent receiver in both directions.
59
+ (mjg59/python-broadlink#839, #841)
60
+ - `pulses_to_data` rounds each duration to the nearest tick instead of
61
+ truncating, which removes up to one tick of systematic shortening per
62
+ pulse.
63
+
64
+ ### Added
65
+
66
+ - `capture()` and `capture_rf()`, async generators that own the arm, poll,
67
+ timeout and re-arm loop of a learning session and yield each signal as a
68
+ `CapturedSignal` (device packet, decoded pulses at the correct tick,
69
+ kind, repeat count, and for RF the carrier frequency). They re-arm on a
70
+ timer, because the device leaves learning mode silently, and after any
71
+ `send_data`, because a transmission ends the session; both intervals and
72
+ the poll cadence were set from a bench on an RM4 Pro. Only one window can
73
+ be open per device. `capture_rf()` (Pro models only) takes the carrier
74
+ frequency directly and falls back to the on-device sweep when it is not
75
+ given.
76
+ - Packet helpers: `pulses_to_data` takes `kind` and `repeat`, `parse_packet`
77
+ is its inverse, and `SignalKind` names the IR, 433 MHz and 315 MHz bands.
78
+ A device's returned RF packet does not always use the canonical type byte
79
+ (an RM4 Pro answers a 433 MHz capture with 0xB1, not 0xB2), so the kind is
80
+ read by band and a capture is tagged from what it armed rather than the
81
+ byte.
82
+ - Devices, carried over from pull requests against the original repository
83
+ with their authors' commits intact: RM Max 0xAF8B (#838, Alexey Masolov);
84
+ RM5 plus 0x5224 with a new `rm5plus` class (#831, Anil Daoud); RM mini 3
85
+ OEM 0xA544 (#823, Bartłomiej Nogaś); RM mini 3 CMCC 0x27C8 (#802,
86
+ shuxin); LB26 R1 0xA517 (#812, techitapart); SP mini 3-AL 0x7D15 (#805,
87
+ bbcbbk); LEDVANCE SMART+ WIFI CEILING TW 24W 0x6498 (#799, Felipe Martins
88
+ Diel).
89
+ - Devices reported in issues against the original repository, added by
90
+ model name to the existing class for that family and not yet confirmed on
91
+ hardware: MP1-1K3S2U 0x4EDA (#816) and SP4 0xA57A (#758). Please open an
92
+ issue if either does not behave.
93
+ - `cryptography` 43 or newer is required, the first release with wheels for
94
+ Python 3.13 (supersedes mjg59/python-broadlink#749).
95
+ - A test suite. The `tests/oracle` package records the exact request bytes
96
+ every public method of every device class sends, and the results it
97
+ decodes from canned responses, so that later changes to the transport
98
+ can be checked byte for byte against the original behavior.
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Mike Ryan
4
+ Copyright (c) 2016 Matthew Garrett
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include LICENSE README.md CHANGELOG.md protocol.md TROUBLESHOOTING.md
2
+ include pyproject.toml
3
+ graft cli
4
+ graft tests
5
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -0,0 +1,360 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-broadlink
3
+ Version: 1.0.0
4
+ Summary: Python API for controlling Broadlink devices
5
+ Author: DAB-LABS
6
+ Author-email: Matthew Garrett <mjg59@srcf.ucam.org>
7
+ Maintainer: DAB-LABS
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/DAB-LABS/python-broadlink
10
+ Project-URL: Repository, https://github.com/DAB-LABS/python-broadlink
11
+ Project-URL: Issues, https://github.com/DAB-LABS/python-broadlink/issues
12
+ Project-URL: Changelog, https://github.com/DAB-LABS/python-broadlink/blob/master/CHANGELOG.md
13
+ Project-URL: Upstream, https://github.com/mjg59/python-broadlink
14
+ Keywords: broadlink,infrared,rf,home-assistant,rm4,rm-pro
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Home Automation
22
+ Requires-Python: >=3.13
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: cryptography>=43
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Requires-Dist: ruff>=0.5; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # python-broadlink
33
+
34
+ A Python module and CLI for controlling Broadlink devices locally.
35
+
36
+ > **About this fork.** This repository is a maintained fork of
37
+ > [mjg59/python-broadlink](https://github.com/mjg59/python-broadlink), which
38
+ > has not accepted changes since 2024. It exists so that Home Assistant's
39
+ > Broadlink integration has a library that can take fixes and new devices.
40
+ > The distribution on PyPI is `python-broadlink`; the import name stays
41
+ > `broadlink`. The first release corrects the IR timing constant reported in
42
+ > upstream [#839](https://github.com/mjg59/python-broadlink/issues/839)
43
+ > (fix in [#841](https://github.com/mjg59/python-broadlink/pull/841)) and
44
+ > adds the devices waiting in upstream's pull request queue, including the
45
+ > RM Max and RM5 Plus. Version 1.0 is asynchronous and adds `capture()`;
46
+ > see `CHANGELOG.md`. Upstream's credit and MIT license are preserved.
47
+
48
+ ## Version 1.0 is asynchronous
49
+
50
+ Every call that reaches a device is a coroutine and must be awaited. This
51
+ is the whole change from the original library's API; method names,
52
+ arguments and return values are the same.
53
+
54
+ ```python
55
+ import asyncio
56
+ import broadlink
57
+
58
+ async def main():
59
+ devices = await broadlink.discover(timeout=5)
60
+ device = devices[0]
61
+ await device.auth()
62
+ print(await device.check_sensors())
63
+
64
+ asyncio.run(main())
65
+ ```
66
+
67
+ Calling a device method without `await` returns a coroutine object and
68
+ sends nothing; Python prints a `RuntimeWarning: coroutine ... was never
69
+ awaited` when it is garbage collected. If you need the old synchronous
70
+ behaviour, pin the original distribution (`broadlink==0.19.0`) instead.
71
+
72
+ The following devices are supported:
73
+
74
+ - **Universal remotes**: RM home, RM mini 3, RM plus, RM pro, RM pro+, RM4 mini, RM4 pro, RM4C mini, RM4S, RM4 TV mate, RM Max, RM5 plus
75
+ - **Smart plugs**: SP mini, SP mini 3, SP mini+, SP1, SP2, SP2-BR, SP2-CL, SP2-IN, SP2-UK, SP3, SP3-EU, SP3S-EU, SP3S-US, SP4L-AU, SP4L-EU, SP4L-UK, SP4M, SP4M-US, SP mini 3-AL, Ankuoo NEO, Ankuoo NEO PRO, Efergy Ego, BG AHC/U-01
76
+ - **Switches**: MCB1, SC1, SCB1E, SCB2
77
+ - **Outlets**: BG 800, BG 900
78
+ - **Power strips**: MP1-1K3S2U, MP1-1K4S, MP2
79
+ - **Environment sensors**: A1
80
+ - **Alarm kits**: S1C, S2KIT
81
+ - **Light bulbs**: LB1, LB26 R1, LB27 R1, SB800TD, LEDVANCE SMART+ WIFI CEILING TW 24W
82
+ - **Curtain motors**: Dooya DT360E-45/20
83
+ - **Thermostats**: Hysen HY02B05H
84
+ - **Hubs**: S3
85
+
86
+ ## Installation
87
+
88
+ Use pip3 to install the latest version of this module.
89
+
90
+ ```
91
+ pip3 install python-broadlink
92
+ ```
93
+
94
+ If the original `broadlink` distribution is also installed in the same
95
+ environment, remove it first (`pip3 uninstall broadlink`); both provide the
96
+ `broadlink` package.
97
+
98
+ ## Basic functions
99
+
100
+ The examples below are written as they would appear inside an `async def`
101
+ function run with `asyncio.run(...)`, as in the snippet above. To try them
102
+ interactively, start Python with `python3 -m asyncio`, which gives you a
103
+ prompt where `await` works at the top level.
104
+
105
+ ```python3
106
+ import broadlink
107
+ ```
108
+
109
+ Now let's try some functions...
110
+
111
+ ### Setup
112
+
113
+ In order to control the device, you need to connect it to your local network. If you have already configured the device with the Broadlink app, this step is not necessary.
114
+
115
+ 1. Put the device into AP Mode.
116
+ - Long press the reset button until the blue LED is blinking quickly.
117
+ - Long press again until blue LED is blinking slowly.
118
+ - Manually connect to the WiFi SSID named BroadlinkProv.
119
+ 2. Connect the device to your local network with the setup function.
120
+ ```python3
121
+ await broadlink.setup('myssid', 'mynetworkpass', 3)
122
+ ```
123
+
124
+ Security mode options are (0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
125
+
126
+ #### Advanced options
127
+
128
+ You may need to specify a broadcast address if setup is not working.
129
+ ```python3
130
+ await broadlink.setup('myssid', 'mynetworkpass', 3, ip_address='192.168.0.255')
131
+ ```
132
+
133
+ ### Discovery
134
+
135
+ Use this function to discover devices:
136
+
137
+ ```python3
138
+ devices = await broadlink.discover()
139
+ ```
140
+
141
+ #### Advanced options
142
+ You may need to specify `local_ip_address` or `discover_ip_address` if discovery does not return any devices.
143
+
144
+ Using the IP address of your local machine:
145
+ ```python3
146
+ devices = await broadlink.discover(local_ip_address='192.168.0.100')
147
+ ```
148
+
149
+ Using the broadcast address of your subnet:
150
+ ```python3
151
+ devices = await broadlink.discover(discover_ip_address='192.168.0.255')
152
+ ```
153
+
154
+ 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:
155
+ ```python3
156
+ device = await broadlink.hello('192.168.0.16')
157
+ ```
158
+
159
+ If you are a perfomance freak, use `broadlink.xdiscover()` to create devices instantly:
160
+ ```python3
161
+ async for device in broadlink.xdiscover():
162
+ print(device) # Example action. Do whatever you want here.
163
+ ```
164
+
165
+ ### Authentication
166
+ After discovering the device, call the `auth()` method to obtain the authentication key required for further communication:
167
+ ```python3
168
+ await device.auth()
169
+ ```
170
+
171
+ The next steps depend on the type of device you want to control.
172
+
173
+ ## Universal remotes
174
+
175
+ ### Learning IR codes
176
+
177
+ Learning IR codes takes place in three steps.
178
+
179
+ 1. Enter learning mode:
180
+ ```python3
181
+ await device.enter_learning()
182
+ ```
183
+ 2. When the LED blinks, point the remote at the Broadlink device and press the button you want to learn.
184
+ 3. Get the IR packet.
185
+ ```python3
186
+ packet = await device.check_data()
187
+ ```
188
+
189
+ ### Learning RF codes
190
+
191
+ Learning RF codes takes place in six steps.
192
+
193
+ 1. Sweep the frequency:
194
+ ```python3
195
+ await device.sweep_frequency()
196
+ ```
197
+ 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.
198
+ 3. Check if the frequency was successfully identified:
199
+ ```python3
200
+ ok, frequency = await device.check_frequency()
201
+ if ok:
202
+ print(f'Frequency found: {frequency} MHz')
203
+ ```
204
+ 4. Enter learning mode:
205
+ ```python3
206
+ await device.find_rf_packet()
207
+ ```
208
+ 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.
209
+ 6. Get the RF packet:
210
+ ```python3
211
+ packet = await device.check_data()
212
+ ```
213
+
214
+ #### Notes
215
+
216
+ Universal remotes with product id 0x2712 use the same method for learning IR and RF codes. They don't need to sweep frequency. Just call `device.enter_learning()` and `device.check_data()`.
217
+
218
+ ### Canceling learning
219
+
220
+ You can exit the learning mode in the middle of the process by calling this method:
221
+ ```python3
222
+ await device.cancel_sweep_frequency()
223
+ ```
224
+
225
+ ### Capturing signals
226
+
227
+ `capture()` wraps the arm, poll, timeout and re-arm dance above into one
228
+ async generator that yields each signal it hears as a `CapturedSignal`:
229
+
230
+ ```python3
231
+ from contextlib import aclosing
232
+
233
+ async with aclosing(device.capture(window=30)) as signals:
234
+ async for signal in signals:
235
+ print(signal.kind, len(signal.pulses), "pulses")
236
+ await other_device.send_data(signal.packet)
237
+ ```
238
+
239
+ By default the window closes after the first signal. Pass
240
+ `stop_after_first=False` to keep it open for the whole `window` (in seconds;
241
+ `window=0` runs until the generator is closed), re-arming after each signal
242
+ because the device holds only one code per learning session. A universal
243
+ remote has a single receiver, so only one capture window can be open on a
244
+ device at a time.
245
+
246
+ `CapturedSignal` carries the device's own `packet` bytes (ready for
247
+ `send_data`), the decoded `pulses` in microseconds at the correct tick, the
248
+ `kind` (`SignalKind.IR`, `RF_433` or `RF_315`), the `repeat` count, and for
249
+ RF the `frequency_mhz` the packet itself does not record.
250
+
251
+ RF works the same way on the Pro models, with the carrier as the one extra
252
+ input:
253
+
254
+ ```python3
255
+ async with aclosing(device.capture_rf(window=30, frequency=433.92)) as signals:
256
+ async for signal in signals:
257
+ ...
258
+ ```
259
+
260
+ Pass `frequency` whenever you know it. Without it the device first sweeps
261
+ for the carrier while you hold a button down, then learns the code from a
262
+ fresh press; the sweep is unreliable on some firmware and can report a
263
+ carrier it never really locked, so the known-frequency path is preferred.
264
+
265
+ ### Sending IR/RF packets
266
+ ```python3
267
+ await device.send_data(packet)
268
+ ```
269
+
270
+ ### Fetching sensor data
271
+ ```python3
272
+ data = await device.check_sensors()
273
+ ```
274
+
275
+ ## Switches
276
+
277
+ ### Setting power state
278
+ ```python3
279
+ await device.set_power(True)
280
+ await device.set_power(False)
281
+ ```
282
+
283
+ ### Checking power state
284
+ ```python3
285
+ state = await device.check_power()
286
+ ```
287
+
288
+ ### Checking energy consumption
289
+ ```python3
290
+ state = await device.get_energy()
291
+ ```
292
+
293
+ ## Power strips
294
+
295
+ ### Setting power state
296
+ ```python3
297
+ await device.set_power(1, True) # Example socket. It could be 2 or 3.
298
+ await device.set_power(1, False)
299
+ ```
300
+
301
+ ### Checking power state
302
+ ```python3
303
+ state = await device.check_power()
304
+ ```
305
+
306
+ ## Light bulbs
307
+
308
+ ### Fetching data
309
+ ```python3
310
+ state = await device.get_state()
311
+ ```
312
+
313
+ ### Setting state attributes
314
+ ```python3
315
+ await devices[0].set_state(pwr=0)
316
+ await devices[0].set_state(pwr=1)
317
+ await devices[0].set_state(brightness=75)
318
+ await devices[0].set_state(bulb_colormode=0)
319
+ await devices[0].set_state(blue=255)
320
+ await devices[0].set_state(red=0)
321
+ await devices[0].set_state(green=128)
322
+ await devices[0].set_state(bulb_colormode=1)
323
+ ```
324
+
325
+ ## Environment sensors
326
+
327
+ ### Fetching sensor data
328
+ ```python3
329
+ data = await device.check_sensors()
330
+ ```
331
+
332
+ ## Hubs
333
+
334
+ ### Discovering subdevices
335
+ ```python3
336
+ await device.get_subdevices()
337
+ ```
338
+
339
+ ### Fetching data
340
+ Use the DID obtained from get_subdevices() for the input parameter to query specific sub-device.
341
+
342
+ ```python3
343
+ await device.get_state(did="00000000000000000000a043b0d06963")
344
+ ```
345
+
346
+ ### Setting state attributes
347
+ The parameters depend on the type of subdevice that is being controlled. In this example, we are controlling LC-1 switches:
348
+
349
+ #### Turn on
350
+ ```python3
351
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr=1)
352
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr1=1)
353
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr2=1)
354
+ ```
355
+ #### Turn off
356
+ ```python3
357
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr=0)
358
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr1=0)
359
+ await device.set_state(did="00000000000000000000a043b0d0783a", pwr2=0)
360
+ ```