Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new callback style #40

Merged
merged 1 commit into from
Sep 24, 2024
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
3 changes: 2 additions & 1 deletion aiorussound/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Asynchronous Python client for Russound RIO."""

from .exceptions import (
CommandError,
UncachedVariableError,
Expand All @@ -21,5 +22,5 @@
"RussoundTcpConnectionHandler",
"ZoneProperties",
"SourceProperties",
"RussoundMessage"
"RussoundMessage",
]
24 changes: 16 additions & 8 deletions aiorussound/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ def _process_response(res: bytes) -> Optional[RussoundMessage]:
"""Process an incoming string of bytes into a RussoundMessage"""
try:
# Attempt to decode in Latin and re-encode in UTF-8 to support international characters
str_res = res.decode(encoding="iso-8859-1").encode(encoding="utf-8").decode(encoding="utf-8").strip()
str_res = (
res.decode(encoding="iso-8859-1")
.encode(encoding="utf-8")
.decode(encoding="utf-8")
.strip()
)
except UnicodeDecodeError as e:
_LOGGER.warning("Failed to decode Russound response %s", res, e)
return None
Expand All @@ -36,7 +41,9 @@ def _process_response(res: bytes) -> Optional[RussoundMessage]:
return RussoundMessage(tag, None, None, None, None, None)
p = m.groupdict()
value = p["value"] or p["value_only"]
return RussoundMessage(tag, p["variable"], value, p["zone"], p["controller"], p["source"])
return RussoundMessage(
tag, p["variable"], value, p["zone"], p["controller"], p["source"]
)


class RussoundConnectionHandler:
Expand Down Expand Up @@ -94,14 +101,15 @@ def remove_message_callback(self, callback) -> None:
"""Removes a previously registered callback."""
self._message_callback.remove(callback)

def _on_msg_recv(self, msg: RussoundMessage) -> None:
async def _on_msg_recv(self, msg: RussoundMessage) -> None:
for callback in self._message_callback:
callback(msg)
await callback(msg)


class RussoundTcpConnectionHandler(RussoundConnectionHandler):

def __init__(self, loop: AbstractEventLoop, host: str, port: int = DEFAULT_PORT) -> None:
def __init__(
self, loop: AbstractEventLoop, host: str, port: int = DEFAULT_PORT
) -> None:
"""Initialize the Russound object using the event loop, host and port
provided.
"""
Expand Down Expand Up @@ -129,7 +137,7 @@ async def close(self):
self._set_connected(False)

async def _ioloop(
self, reader: StreamReader, writer: StreamWriter, reconnect: bool
self, reader: StreamReader, writer: StreamWriter, reconnect: bool
) -> None:
queue_future = ensure_future(self._cmd_queue.get())
net_future = ensure_future(reader.readline())
Expand All @@ -148,7 +156,7 @@ async def _ioloop(
try:
msg = _process_response(response)
if msg:
self._on_msg_recv(msg)
await self._on_msg_recv(msg)
if msg.tag == "S" and last_command_future:
last_command_future.set_result(msg.value)
last_command_future = None
Expand Down
52 changes: 41 additions & 11 deletions aiorussound/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Models for aiorussound."""

from dataclasses import dataclass, field
from enum import StrEnum
from typing import Optional

from mashumaro import field_options
Expand All @@ -9,6 +11,7 @@
@dataclass
class RussoundMessage:
"""Incoming russound message."""

tag: str
variable: Optional[str] = None
value: Optional[str] = None
Expand All @@ -26,18 +29,32 @@ class ZoneProperties(DataClassORJSONMixin):
treble: str = field(metadata=field_options(alias="treble"), default="0")
balance: str = field(metadata=field_options(alias="balance"), default="0")
loudness: str = field(metadata=field_options(alias="loudness"), default="OFF")
turn_on_volume: str = field(metadata=field_options(alias="turnonvolume"), default="20")
do_not_disturb: str = field(metadata=field_options(alias="donotdisturb"), default="OFF")
turn_on_volume: str = field(
metadata=field_options(alias="turnonvolume"), default="20"
)
do_not_disturb: str = field(
metadata=field_options(alias="donotdisturb"), default="OFF"
)
party_mode: str = field(metadata=field_options(alias="partymode"), default="OFF")
status: str = field(metadata=field_options(alias="status"), default="OFF")
is_mute: str = field(metadata=field_options(alias="mute"), default="OFF")
shared_source: str = field(metadata=field_options(alias="sharedsource"), default="OFF")
last_error: Optional[str] = field(metadata=field_options(alias="lasterror"), default=None)
shared_source: str = field(
metadata=field_options(alias="sharedsource"), default="OFF"
)
last_error: Optional[str] = field(
metadata=field_options(alias="lasterror"), default=None
)
page: Optional[str] = field(metadata=field_options(alias="page"), default=None)
sleep_time_default: Optional[str] = field(metadata=field_options(alias="sleeptimedefault"), default=None)
sleep_time_remaining: Optional[str] = field(metadata=field_options(alias="sleeptimeremaining"), default=None)
sleep_time_default: Optional[str] = field(
metadata=field_options(alias="sleeptimedefault"), default=None
)
sleep_time_remaining: Optional[str] = field(
metadata=field_options(alias="sleeptimeremaining"), default=None
)
enabled: str = field(metadata=field_options(alias="enabled"), default="False")
current_source: str = field(metadata=field_options(alias="currentsource"), default="1")
current_source: str = field(
metadata=field_options(alias="currentsource"), default="1"
)


@dataclass
Expand All @@ -46,14 +63,20 @@ class SourceProperties(DataClassORJSONMixin):

type: str = field(metadata=field_options(alias="type"), default=None)
channel: str = field(metadata=field_options(alias="channel"), default=None)
cover_art_url: str = field(metadata=field_options(alias="covertarturl"), default=None)
cover_art_url: str = field(
metadata=field_options(alias="covertarturl"), default=None
)
channel_name: str = field(metadata=field_options(alias="channelname"), default=None)
genre: str = field(metadata=field_options(alias="genre"), default=None)
artist_name: str = field(metadata=field_options(alias="artistname"), default=None)
album_name: str = field(metadata=field_options(alias="albumname"), default=None)
playlist_name: str = field(metadata=field_options(alias="playlistname"), default=None)
playlist_name: str = field(
metadata=field_options(alias="playlistname"), default=None
)
song_name: str = field(metadata=field_options(alias="songname"), default=None)
program_service_name: str = field(metadata=field_options(alias="programservicename"), default=None)
program_service_name: str = field(
metadata=field_options(alias="programservicename"), default=None
)
radio_text: str = field(metadata=field_options(alias="radiotext"), default=None)
shuffle_mode: str = field(metadata=field_options(alias="shufflemode"), default=None)
repeat_mode: str = field(metadata=field_options(alias="repeatmode"), default=None)
Expand All @@ -63,4 +86,11 @@ class SourceProperties(DataClassORJSONMixin):
bit_rate: str = field(metadata=field_options(alias="bitrate"), default=None)
bit_depth: str = field(metadata=field_options(alias="bitdepth"), default=None)
play_time: str = field(metadata=field_options(alias="playtime"), default=None)
track_time: str = field(metadata=field_options(alias="tracktime"), default=None)
track_time: str = field(metadata=field_options(alias="tracktime"), default=None)


class CallbackType(StrEnum):
"""Callback type."""

STATE = "state"
CONNECTION = "connection"
Loading