Skip to content

Commit

Permalink
fix: configured AVRs access in event handlers (#14)
Browse files Browse the repository at this point in the history
- Make sure there's an avr instance.
- Loop over values and not the tuples.
- Add more type information.
  • Loading branch information
zehnm authored Oct 26, 2023
1 parent fae29ee commit 5e09a9d
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 34 deletions.
78 changes: 57 additions & 21 deletions intg-denonavr/avr.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import asyncio
import logging
import re
from asyncio import AbstractEventLoop
from enum import IntEnum

import denonavr
Expand Down Expand Up @@ -59,27 +60,28 @@ async def discover_denon_avrs():
class DenonAVR:
"""Representing a Denon AVR Device."""

def __init__(self, loop, ipaddress):
def __init__(self, loop: AbstractEventLoop, ipaddress: str):
"""Create instance with given IP address of AVR."""
self._loop = loop
self._loop: AbstractEventLoop = loop
self.events = AsyncIOEventEmitter(self._loop)
self._avr = None
self.name = ""
self.model = ""
self.manufacturer = ""
self.id = ""
self.ipaddress = ipaddress
self.getting_data = False

self.state = STATES.OFF
self.volume = 0
self.input = ""
self.input_list = []
self.artist = ""
self.title = ""
self.artwork = ""
self.position = 0
self.duration = 0
self._avr: denonavr.DenonAVR | None = None
self.name: str | None = None
self.model: str | None = None
self.manufacturer: str | None = None
self.id: str | None = None
self.ipaddress: str = ipaddress
self.getting_data: bool = False

# TODO shouldn't default state be UNKNOWN from the entity base class?
self.state: STATES = STATES.OFF
self.volume: float = 0
self.input: str | None = None
self.input_list: list[str] = []
self.artist: str | None = None
self.title: str | None = None
self.artwork: str | None = None
self.position: int = 0
self.duration: int = 0

LOG.debug("Denon AVR created: %s", self.ipaddress)

Expand Down Expand Up @@ -124,7 +126,9 @@ async def connect(self):
self.manufacturer = self._avr.manufacturer
self.model = self._avr.model_name
self.name = self._avr.name
# TODO any chance we don't get a serial number from the device?
self.id = self._avr.serial_number

LOG.debug(
"Denon AVR connected. Manufacturer=%s, Model=%s, Name=%s, Id=%s, State=%s",
self.manufacturer,
Expand All @@ -133,8 +137,12 @@ async def connect(self):
self.id,
self._avr.state,
)

await self._subscribe_events()
self.events.emit(EVENTS.CONNECTED, self.id)
if self.id:
self.events.emit(EVENTS.CONNECTED, self.id)
else:
LOG.error("Device communication error: no serial number retrieved from AVR!")

if self._avr.state == "on":
self.state = STATES.ON
Expand All @@ -158,9 +166,12 @@ async def disconnect(self):
"""Disconnect from AVR."""
await self._unsubscribe_events()
self._avr = None
self.events.emit(EVENTS.DISCONNECTED, self.id)
if self.id:
self.events.emit(EVENTS.DISCONNECTED, self.id)

async def _get_data(self):
if self._avr is None:
return
if self.getting_data:
return

Expand Down Expand Up @@ -203,6 +214,8 @@ async def _get_data(self):

async def _update_callback(self, zone, event, parameter):
LOG.debug("Zone: %s Event: %s Parameter: %s", zone, event, parameter)
if self._avr is None:
return
try:
await self._avr.async_update()
except denonavr.exceptions.DenonAvrError as e:
Expand Down Expand Up @@ -236,6 +249,8 @@ async def _update_callback(self, zone, event, parameter):
# LOG.debug(f"Time: {self.position}, Percentage: {self.duration}")

async def _subscribe_events(self):
if self._avr is None:
return
try:
await self._avr.async_telnet_connect()
await self._avr.async_update()
Expand All @@ -245,6 +260,9 @@ async def _subscribe_events(self):
LOG.debug("Subscribed to events")

async def _unsubscribe_events(self):
if self._avr is None:
return

try:
self._avr.unregister_callback("ALL", self._update_callback)
# TODO is async_update() required?
Expand All @@ -270,34 +288,50 @@ async def _command_wrapper(self, fn):

async def power_on(self):
"""Send power-on command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_power_on)

async def power_off(self):
"""Send power-off command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_power_off)

async def volume_up(self):
"""Send volume-up command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_volume_up)

async def volume_down(self):
"""Send volume-down command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_volume_down)

async def play_pause(self):
"""Send toggle-play-pause command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_toggle_play_pause)

async def next(self):
"""Send next-track command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_next_track)

async def previous(self):
"""Send previous-track command to AVR."""
if self._avr is None:
return
return await self._command_wrapper(self._avr.async_previous_track)

async def mute(self, muted):
"""Send mute command to AVR."""
if self._avr is None:
return
try:
await self._avr.async_mute(muted)
return True
Expand All @@ -307,6 +341,8 @@ async def mute(self, muted):

async def set_input(self, input_source):
"""Send input_source command to AVR."""
if self._avr is None:
return
try:
await self._avr.async_set_input_func(input_source)
return True
Expand Down
25 changes: 12 additions & 13 deletions intg-denonavr/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

CFG_FILENAME = "config.json"
# Global variables
CFG_FILE_PATH = None
CFG_FILE_PATH: str | None = None
api = uc.IntegrationAPI(LOOP)
config = []
configuredAVRs = {}
config: list[dict[str, any]] = []
configuredAVRs: dict[str, avr.DenonAVR] = {}


async def clear_config():
Expand Down Expand Up @@ -145,29 +145,28 @@ async def _on_connect():
# When the core disconnects, we just set the device state
@api.events.on(uc.uc.EVENTS.DISCONNECT)
async def _on_disconnect():
for entity_id in configuredAVRs.items():
LOG.debug("Client disconnected, disconnecting all AVRs")
a = configuredAVRs[entity_id]
a.events.remove_all_listeners()
await a.disconnect()
LOG.debug("Client disconnected, disconnecting all AVRs")
for configured in configuredAVRs.values():
configured.events.remove_all_listeners()
await configured.disconnect()

await api.setDeviceState(uc.uc.DEVICE_STATES.DISCONNECTED)


# On standby, we disconnect every Denon AVR objects
@api.events.on(uc.uc.EVENTS.ENTER_STANDBY)
async def _on_enter_standby():
for a in configuredAVRs.items():
await configuredAVRs[a].disconnect()
for configured in configuredAVRs.values():
await configured.disconnect()


# On exit standby we wait a bit then connect all Denon AVR objects
@api.events.on(uc.uc.EVENTS.EXIT_STANDBY)
async def _on_exit_standy():
async def _on_exit_standby():
await asyncio.sleep(2)

for a in configuredAVRs.items():
await configuredAVRs[a].connect()
for configured in configuredAVRs.values():
await configured.connect()


# When the core subscribes to entities, we set these to UNAVAILABLE state
Expand Down

0 comments on commit 5e09a9d

Please sign in to comment.