Skip to content

Commit

Permalink
feat: add feature flags & API firmware check
Browse files Browse the repository at this point in the history
  • Loading branch information
noahhusby committed Jul 19, 2024
1 parent c298ce8 commit d6ccd2d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 18 deletions.
19 changes: 18 additions & 1 deletion aiorussound/const.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import json
from enum import Enum

MAX_ZONES_KEY = "max_zones"
RNET_SUPPORT_KEY = "rnet_support"

MINIMUM_API_SUPPORT = "1.03.00"


# TODO: Add features 1.06.00 and up
class FeatureFlag(Enum):
DMS_3_1 = 1
KEY_CODE_EVENT = 2
PROPERTY_IP_ADDRESS = 3
SUPPORT_SHUFFLE = 4
COMMAND_MM_CLOSE = 5
SUPPORT_PAGE_ZONE = 6
SUPPORT_REPEAT = 7
PROPERTY_CTRL_TYPE = 8
PROPERTY_SYS_LANG = 9


# Each controller is separated to support future differences in features
with open('aiorussound/devices.json') as json_file:
CONTROLLER_ATTR = json.load(json_file)
CONTROLLER_ATTR = json.load(json_file)
40 changes: 25 additions & 15 deletions aiorussound/rio.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
else:
ensure_future = getattr(asyncio, "async")

logger = logging.getLogger("russound")
_LOGGER = logging.getLogger(__package__)

_re_response = re.compile(
r"(?:(?:S\[(?P<source>\d+)\])|(?:C\[(?P<controller>\d+)\](?:\.Z\[(?P<zone>\d+)\])?))\.(?P<variable>\S+)=\s*\"(?P<value>.*)\""
r"(?:(?:C\[(?P<controller>\d+)](?:\.Z\[(?P<zone>\d+)])?|C\[(?P<controller_alt>\d+)]\.S\[(?P<source>\d+)])?\.("
r"?P<variable>\S+)|(?P<variable_no_prefix>\S+))=\s*\"(?P<value>.*)\""
)


Expand All @@ -27,6 +28,11 @@ class UncachedVariable(Exception):
pass


class UnsupportedFeature(Exception):
"""A requested command is not supported on this controller"""
pass


class Russound:
"""Manages the RIO connection to a Russound device."""

Expand Down Expand Up @@ -54,7 +60,7 @@ def _retrieve_cached_zone_variable(self, controller_id, zone_id, name):
"""
try:
s = self._zone_state[f"C[{controller_id}].Z[{zone_id}]"][name.lower()]
logger.debug(
_LOGGER.debug(
"Zone Cache retrieve %s.%s = %s", f"C[{controller_id}.Z[{zone_id}]", name, s
)
return s
Expand All @@ -69,7 +75,7 @@ def _store_cached_zone_variable(self, zone_id, name, value):
zone_state = self._zone_state.setdefault(zone_id, {})
name = name.lower()
zone_state[name] = value
logger.debug("Zone Cache store %s.%s = %s", zone_id.device_str(), name, value)
_LOGGER.debug("Zone Cache store %s.%s = %s", zone_id.device_str(), name, value)
for callback in self._zone_callbacks:
callback(zone_id, name, value)

Expand All @@ -81,7 +87,7 @@ def _retrieve_cached_source_variable(self, source_id, name):
"""
try:
s = self._source_state[source_id][name.lower()]
logger.debug("Source Cache retrieve S[%d].%s = %s", source_id, name, s)
_LOGGER.debug("Source Cache retrieve S[%d].%s = %s", source_id, name, s)
return s
except KeyError:
raise UncachedVariable
Expand All @@ -94,15 +100,15 @@ def _store_cached_source_variable(self, source_id, name, value):
source_state = self._source_state.setdefault(source_id, {})
name = name.lower()
source_state[name] = value
logger.debug("Source Cache store S[%d].%s = %s", source_id, name, value)
_LOGGER.debug("Source Cache store S[%d].%s = %s", source_id, name, value)
for callback in self._source_callbacks:
callback(source_id, name, value)

def _process_response(self, res):
s = str(res, "utf-8").strip()
ty, payload = s[0], s[2:]
if ty == "E":
logger.debug("Device responded with error: %s", payload)
_LOGGER.debug("Device responded with error: %s", payload)
raise CommandException(payload)

m = _re_response.match(payload)
Expand All @@ -124,7 +130,7 @@ async def _ioloop(self, reader, writer):
queue_future = ensure_future(self._cmd_queue.get())
net_future = ensure_future(reader.readline())
try:
logger.debug("Starting IO loop")
_LOGGER.debug("Starting IO loop")
while True:
done, pending = await asyncio.wait(
[queue_future, net_future], return_when=asyncio.FIRST_COMPLETED
Expand Down Expand Up @@ -206,16 +212,16 @@ async def connect(self):
"""
Connect to the controller and start processing responses.
"""
logger.info("Connecting to %s:%s", self._host, self._port)
_LOGGER.info("Connecting to %s:%s", self._host, self._port)
reader, writer = await asyncio.open_connection(self._host, self._port)
self._ioloop_future = ensure_future(self._ioloop(reader, writer))
logger.info("Connected")
_LOGGER.info("Connected")

async def close(self):
"""
Disconnect from the controller.
"""
logger.info("Closing connection to %s:%s", self._host, self._port)
_LOGGER.info("Closing connection to %s:%s", self._host, self._port)
self._ioloop_future.cancel()
try:
await self._ioloop_future
Expand Down Expand Up @@ -340,7 +346,7 @@ def _retrieve_cached_controller_variable(self, controller_id, name):
"""
try:
s = self._controller_state[controller_id][name.lower()]
logger.debug(
_LOGGER.debug(
"Controller Cache retrieve C[%d].%s = %s", controller_id, name, s
)
return s
Expand All @@ -354,13 +360,18 @@ def _store_cached_controller_variable(self, controller_id, name, value):
controller_state = self._controller_state.setdefault(controller_id, {})
name = name.lower()
controller_state[name] = value
logger.debug("Controller Cache store C[%d].%s = %s", controller_id, name, value)
_LOGGER.debug("Controller Cache store C[%d].%s = %s", controller_id, name, value)

@property
def api_version(self):
return self._send_cmd("VERSION")


class Controller:
"""Uniquely identifies a controller"""

def __init__(self, instance: Russound, controller_id: int, mac_address: str, controller_type: str, firmware_version: str):
def __init__(self, instance: Russound, controller_id: int, mac_address: str, controller_type: str,
firmware_version: str):
self.instance = instance
self.controller_id = controller_id
self.mac_address = mac_address
Expand Down Expand Up @@ -523,4 +534,3 @@ def sleep_time_remaining(self):
@property
def enabled(self):
return self._get('enabled')

5 changes: 3 additions & 2 deletions examples/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
# is used for tests.
import sys
import os

sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..'))

from aiorussound import Russound # noqa: E402


async def demo(loop, host):
rus = Russound(loop, host)
await rus.connect()

print("Finding controllers")
controllers = await rus.enumerate_controllers()

Expand All @@ -24,7 +25,7 @@ async def demo(loop, host):
valid_zones = await c.enumerate_zones()

for zone_id, zone in valid_zones:
print("%s: %s" % (zone_id, await zone.turn_on_volume))
print("%s: %s" % (zone_id, await zone.volume))

sources = await rus.enumerate_sources()
for source_id, name in sources:
Expand Down

0 comments on commit d6ccd2d

Please sign in to comment.