python-broadlink 1.0.5__tar.gz → 1.0.6__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.
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/CHANGELOG.md +31 -4
- {python_broadlink-1.0.5/python_broadlink.egg-info → python_broadlink-1.0.6}/PKG-INFO +1 -1
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/__init__.py +3 -3
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/device.py +13 -1
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/remote.py +6 -4
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/pyproject.toml +1 -1
- {python_broadlink-1.0.5 → python_broadlink-1.0.6/python_broadlink.egg-info}/PKG-INFO +1 -1
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_loopback.py +16 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_transport.py +19 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/LICENSE +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/MANIFEST.in +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/README.md +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/TROUBLESHOOTING.md +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/alarm.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/climate.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/const.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/cover.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/exceptions.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/helpers.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/hub.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/light.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/protocol.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/sensor.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/broadlink/switch.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/cli/README.md +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/cli/broadlink_cli +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/cli/broadlink_discovery +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/protocol.md +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/python_broadlink.egg-info/SOURCES.txt +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/python_broadlink.egg-info/dependency_links.txt +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/python_broadlink.egg-info/requires.txt +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/python_broadlink.egg-info/top_level.txt +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/setup.cfg +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/__init__.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/oracle/__init__.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/oracle/cases.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/oracle/fixtures.json +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/oracle/harness.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/oracle/record.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_capture.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_helpers.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_oracle.py +0 -0
- {python_broadlink-1.0.5 → python_broadlink-1.0.6}/tests/test_remote.py +0 -0
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project are recorded here. The format follows
|
|
4
4
|
Keep a Changelog; versions follow Semantic Versioning.
|
|
5
5
|
|
|
6
|
+
## 1.0.6 - 2026-09-06
|
|
7
|
+
|
|
8
|
+
Small items from a sixth review, of 1.0.5, which found no defect in the
|
|
9
|
+
code. No change to the wire format or the public API.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A network failure during the library's own re-authentication (a timeout,
|
|
14
|
+
or the endpoint being closed) is raised as that failure. 1.0.3 to 1.0.5
|
|
15
|
+
returned the device's original expired-key answer instead, so the caller
|
|
16
|
+
raised `AuthorizationError` for what was really a timeout.
|
|
17
|
+
- An endpoint with no address on either side (discovery, `ping()`,
|
|
18
|
+
`setup()`) is bound to `0.0.0.0` explicitly, for the proactor loop on
|
|
19
|
+
Windows, which starts receiving as soon as the endpoint exists.
|
|
20
|
+
- The RM Max comment and the 1.0.0 changelog entry no longer claim more
|
|
21
|
+
than upstream #838 showed: the mapping follows that pull request's diff,
|
|
22
|
+
and its testers reported the device answering "locked" on
|
|
23
|
+
authentication, so it is listed as reported, not confirmed.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- An undecodable capture is logged at debug rather than warning, since the
|
|
28
|
+
window re-arms and carries on by itself.
|
|
29
|
+
- The RF capture loop closes its inner generator with `aclosing` like the
|
|
30
|
+
IR one.
|
|
31
|
+
|
|
6
32
|
## 1.0.5 - 2026-09-06
|
|
7
33
|
|
|
8
34
|
Fixes from a fifth review, of 1.0.4. No change to the wire format or the
|
|
@@ -314,16 +340,17 @@ history below starts at that fork point.
|
|
|
314
340
|
byte.
|
|
315
341
|
- Devices, carried over from pull requests against the original repository
|
|
316
342
|
with their authors credited (the changes were squash-merged with
|
|
317
|
-
`Co-authored-by` trailers naming each author):
|
|
318
|
-
RM5 plus 0x5224 with a new `rm5plus` class (#831, Anil Daoud); RM mini 3
|
|
343
|
+
`Co-authored-by` trailers naming each author): RM5 plus 0x5224 with a new `rm5plus` class (#831, Anil Daoud); RM mini 3
|
|
319
344
|
OEM 0xA544 (#823, Bartłomiej Nogaś); RM mini 3 CMCC 0x27C8 (#802,
|
|
320
345
|
shuxin); LB26 R1 0xA517 (#812, techitapart); SP mini 3-AL 0x7D15 (#805,
|
|
321
346
|
bbcbbk); LEDVANCE SMART+ WIFI CEILING TW 24W 0x6498 (#799, Felipe Martins
|
|
322
347
|
Diel).
|
|
323
348
|
- Devices reported in issues against the original repository, added by
|
|
324
349
|
model name to the existing class for that family and not yet confirmed on
|
|
325
|
-
hardware: MP1-1K3S2U 0x4EDA (#816) and SP4 0xA57A (#758)
|
|
326
|
-
|
|
350
|
+
hardware: MP1-1K3S2U 0x4EDA (#816) and SP4 0xA57A (#758), and the RM Max
|
|
351
|
+
0xAF8B from #838 (Alexey Masolov, credited), whose testers reported the
|
|
352
|
+
device answering "locked" on authentication. Please open an issue if any
|
|
353
|
+
of them does not behave.
|
|
327
354
|
- `cryptography` 43 or newer is required, the first release with wheels for
|
|
328
355
|
Python 3.13 (supersedes mjg59/python-broadlink#749).
|
|
329
356
|
- A test suite. The `tests/oracle` package records, for every public method
|
|
@@ -116,9 +116,9 @@ SUPPORTED_TYPES = {
|
|
|
116
116
|
0x27A6: ("RM plus", "Broadlink"),
|
|
117
117
|
0x27A9: ("RM pro+", "Broadlink"),
|
|
118
118
|
0x27C3: ("RM pro+", "Broadlink"),
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
119
|
+
# Mapping follows the diff in upstream #838 (its text says rm4pro,
|
|
120
|
+
# its diff says rmpro). Testers in that thread reported the device
|
|
121
|
+
# answering "locked" on auth, so treat it as reported, not confirmed.
|
|
122
122
|
0xAF8B: ("RM Max", "Broadlink"),
|
|
123
123
|
},
|
|
124
124
|
rmminib: {
|
|
@@ -120,7 +120,14 @@ async def _open_endpoint(
|
|
|
120
120
|
remote_addr: tuple[str, int] | None = None,
|
|
121
121
|
broadcast: bool = False,
|
|
122
122
|
) -> tuple[asyncio.DatagramTransport, _Protocol]:
|
|
123
|
-
"""Create a UDP endpoint. Tests replace this to fake the network.
|
|
123
|
+
"""Create a UDP endpoint. Tests replace this to fake the network.
|
|
124
|
+
|
|
125
|
+
An endpoint with no address on either side is bound to ``0.0.0.0``
|
|
126
|
+
explicitly; the proactor loop on Windows starts receiving as soon as the
|
|
127
|
+
endpoint exists, which needs a bound socket.
|
|
128
|
+
"""
|
|
129
|
+
if local_addr is None and remote_addr is None:
|
|
130
|
+
local_addr = ("0.0.0.0", 0)
|
|
124
131
|
loop = asyncio.get_running_loop()
|
|
125
132
|
transport, protocol = await loop.create_datagram_endpoint(
|
|
126
133
|
_Protocol,
|
|
@@ -643,6 +650,11 @@ class Device:
|
|
|
643
650
|
if self._auth_generation == generation:
|
|
644
651
|
try:
|
|
645
652
|
await self.auth()
|
|
653
|
+
except (e.NetworkTimeoutError, e.EndpointClosedError):
|
|
654
|
+
# A network failure during re-authentication is
|
|
655
|
+
# reported as what it is, not as the device's
|
|
656
|
+
# original expired-key answer.
|
|
657
|
+
raise
|
|
646
658
|
except e.BroadlinkException as err:
|
|
647
659
|
_LOGGER.debug(
|
|
648
660
|
"%s: re-authentication failed: %s", self.host[0], err
|
|
@@ -408,7 +408,7 @@ class rmmini(Device):
|
|
|
408
408
|
except ValueError as err:
|
|
409
409
|
# A packet the device returned but we cannot decode. Log
|
|
410
410
|
# it, re-arm and keep the window open.
|
|
411
|
-
_LOGGER.
|
|
411
|
+
_LOGGER.debug(
|
|
412
412
|
"%s: ignoring an undecodable capture (%s): %s",
|
|
413
413
|
self.host[0],
|
|
414
414
|
err,
|
|
@@ -500,10 +500,12 @@ class rmpro(rmmini):
|
|
|
500
500
|
raise ValueError("window must be 0 or positive, poll_interval positive")
|
|
501
501
|
await self._claim_window()
|
|
502
502
|
try:
|
|
503
|
-
|
|
503
|
+
body = self._capture_rf_body(
|
|
504
504
|
window, frequency, stop_after_first, poll_interval, rearm_interval
|
|
505
|
-
)
|
|
506
|
-
|
|
505
|
+
)
|
|
506
|
+
async with contextlib.aclosing(body):
|
|
507
|
+
async for signal in body:
|
|
508
|
+
yield signal
|
|
507
509
|
finally:
|
|
508
510
|
self._release_window()
|
|
509
511
|
|
|
@@ -108,3 +108,19 @@ def test_endpoint_heals_after_a_socket_error_on_loopback(caplog):
|
|
|
108
108
|
|
|
109
109
|
assert asyncio.run(go()) == 9
|
|
110
110
|
assert "unreachable" in caplog.text
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_unbound_endpoint_is_bound_explicitly():
|
|
114
|
+
"""scan, ping and setup open an endpoint with no address on either
|
|
115
|
+
side; it is bound to 0.0.0.0 so it can receive on every platform."""
|
|
116
|
+
|
|
117
|
+
async def go():
|
|
118
|
+
transport, _ = await device_module._open_endpoint(broadcast=True)
|
|
119
|
+
try:
|
|
120
|
+
host, port = transport.get_extra_info("sockname")[:2]
|
|
121
|
+
return host, port
|
|
122
|
+
finally:
|
|
123
|
+
transport.close()
|
|
124
|
+
|
|
125
|
+
host, port = asyncio.run(go())
|
|
126
|
+
assert host == "0.0.0.0" and port > 0
|
|
@@ -631,6 +631,25 @@ def test_failed_reauth_returns_the_original_reply(net):
|
|
|
631
631
|
e.check_error(resp[0x22:0x24])
|
|
632
632
|
|
|
633
633
|
|
|
634
|
+
def test_timeout_during_reauth_is_reported_as_a_timeout(net):
|
|
635
|
+
"""If the device answers the request with an expired-key code and then
|
|
636
|
+
goes silent during the re-authentication, that is a network failure and
|
|
637
|
+
is raised as one, not returned as the device's original answer."""
|
|
638
|
+
dev = fixed_device()
|
|
639
|
+
dev.timeout = 0.03
|
|
640
|
+
|
|
641
|
+
async def go():
|
|
642
|
+
await dev._endpoint()
|
|
643
|
+
ep = net.endpoints[-1]
|
|
644
|
+
ep.replies = [(make_response(dev, b"", error=0xFFF9), HOST)] # then silence
|
|
645
|
+
with pytest.raises(e.NetworkTimeoutError):
|
|
646
|
+
await dev.send_packet(0x6A, b"")
|
|
647
|
+
return [int.from_bytes(f[0x26:0x28], "little") for f, _ in ep.sent]
|
|
648
|
+
|
|
649
|
+
types = run(go())
|
|
650
|
+
assert types[0] == 0x6A and set(types[1:]) == {0x65} # auth was tried and resent
|
|
651
|
+
|
|
652
|
+
|
|
634
653
|
def test_locked_device_surfaces_as_the_original_error(net):
|
|
635
654
|
"""Device locked in the app: request answered -7, auth answered -1. The
|
|
636
655
|
caller gets the -7 frame back (its check_error raises
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_broadlink-1.0.5 → python_broadlink-1.0.6}/python_broadlink.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|