Repository navigation
Releases: bazwilliams/openhomedevice
Release list
2.7
What's Changed
- Publish to PyPI when a version is tagged by @bazwilliams in #32
- Hand the subscription lifecycle to the caller by @bazwilliams in #33
Full Changelog: 2.6...2.7
2.6
Subscribe to device events instead of polling
A device can push its changes rather than being asked for them. Each key is
named for the method returning the same value, so is_muted in an event is
what is_muted() would have returned.
def on_event(changes):
if changes.get("is_subscribed") is False:
return # subscription lost; treat the state as stale
print(changes) # {"volume": 42, "is_muted": False}
if device.events_enabled:
await device.subscribe(on_event)
...
await device.unsubscribe()The first event after subscribing carries the device's whole state; after
that only what changed. Subscribing needs the Product, Transport and Info
services — events_enabled says whether this device has them, and
subscribe() raises OpenhomeDeviceError if it does not, so older firmware
and third party renderers should keep polling.
Subscriptions are renewed in the background. If one cannot be renewed the
rest are released and the callback is called a final time with
{"is_subscribed": False}; call subscribe() again to recover.
By default a notify server is started per device. Pass an event_handler to
the constructor to share one across several, in the same way a session can
already be shared.
Report what the device can do with what is playing
Rather than guessing from the source type, ask the device.
await device.can_pause() # False on a turntable at a line input
await device.can_skip_next() # True on a playlist
await device.can_skip_previous()Each returns None on a device with no Transport service, meaning it cannot
say rather than the answer being no, so a caller can fall back to guessing
from the source type as before.
Fixes
sources() now reads <Visible>1</Visible> as well as <Visible>true</Visible>,
so it no longer returns an empty list on AURALiC devices. Diagnosed by
@droman42 in #26.
await device.sources() # ['Playlist', 'Radio', 'Songcast', 'Spotify']pins_enabled reports whether the Pins service resolved, rather than whether
the device advertises Pins version 1 specifically, so a later version of the
service no longer reads as having no pins at all.
What's Changed
- Tidy setup.py and delete the dead setup.cfg by @bazwilliams in #28
- Report what the device can do with what is playing by @bazwilliams in #29
- Subscribe to device events instead of polling by @bazwilliams in #30
- Bump version to 2.6 by @bazwilliams in #31
Full Changelog: 2.5...2.6
2.5
No breaking changes. Both additions below are opt-in: code written against
2.4.1 behaves identically on 2.5.
Pass in your own aiohttp session
Device now takes an optional session:
device = Device(location, session=session)
await device.init()Previously every request opened and closed a ClientSession of its own, so
polling a device meant a new session and TCP connection each time. Supplying a
session reuses it and its connection pool. The session is never closed by this
library — whoever created it owns it. Omitting it keeps the 2.4.1 behaviour.
Errors now come from openhomedevice.exceptions
Callers previously had to import async_upnp_client to catch a failed request,
coupling their error handling to a transitive dependency. Every method that
talks to the device now raises one of:
| Exception | Meaning |
|---|---|
OpenhomeError |
Base class for all of the below |
OpenhomeConnectionError |
The device could not be reached |
OpenhomeTimeoutError |
The device did not answer in time (subclass of the above) |
OpenhomeDeviceError |
The device answered, but refused the request or replied unusably |
The original exception is preserved as __cause__. Because OpenhomeTimeoutError
subclasses OpenhomeConnectionError, catching the latter covers a device that is
off or unreachable for any reason.
Separately, a bare except: in the DIDL-Lite parser is now narrowed to
ParseError and TypeError; as written it also swallowed KeyboardInterrupt
and SystemExit.
Packaging and CI
python_requirescorrected from>=3.6to>=3.10.async_upnp_clienthas
required 3.10 for some time, so the old floor was untrue.- Tests now run on GitHub Actions for every push and pull request, across
Python 3.10, 3.11, 3.12, 3.13 and 3.14.
What's Changed
- Bump version to 2.5 by @bazwilliams in #27
Full Changelog: 2.4.1...2.5
2.4.1
What's Changed
- Remove lxml dependency from setup.py by @droman42 in #25
- Added songcast support so supported devices can join and leave and songcast group for multi room audio
New Contributors
Full Changelog: 2.3.1...2.4.1
2.3.1
Fixed test data
Full Changelog: 2.3...2.3.1
2.3
Fix escaping issue when parsing metadata
Full Changelog: 2.2...2.3
2.2
Added manufacturer, model name and friendly name to API
Full Changelog: 2.1...2.2
2.1
- Fixed test suite
- Add support for firmware updates
Full Changelog: 2.0.2...2.1
2.0.2
- updated dependencies and fixed changed imports
Full Changelog: 2.0.1...2.0.2