Skip to content

Commit

Permalink
Merge pull request #40 from noahhusby/feat/revamp-callback-style
Browse files Browse the repository at this point in the history
Implement new callback style
  • Loading branch information
noahhusby authored Sep 24, 2024
2 parents 612f58d + eb32a88 commit 56c7438
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 103 deletions.
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

0 comments on commit 56c7438

Please sign in to comment.