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

Support STR pre-amplifier and integrated amplifier #40

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Changes from 3 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
92 changes: 74 additions & 18 deletions anthemav/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@
"All Channel Mono": 8,
}

ALM_NUMBER_STR = {
"Stereo": 0,
"Mono": 1,
"Both=Left": 2,
"Both=Right": 3,
}

# Some models (eg:MRX 520) provide a limited list of listening mode
ALM_RESTRICTED = ["00", "01", "02", "03", "04", "05", "06", "07"]

Expand Down Expand Up @@ -187,13 +194,33 @@
"Z1VIR",
]
COMMANDS_MDX = ["MAC"]
COMMANDS_STR_IGNORE = [
"ECH",
"EMAC",
"GCFPB",
"GCTXS",
"IDR",
"MAC",
"SIP",
"WMAC",
"Z1AIC",
"Z1AIN",
"Z1AIR",
"Z1BRT",
"Z1DIA",
"Z1DYN",
"Z1IRH",
"Z1IRV",
"Z1VIR",
]

EMPTY_MAC = "00:00:00:00:00:00"
UNKNOWN_MODEL = "Unknown Model"

MODEL_X40 = "x40"
MODEL_X20 = "x20"
MODEL_MDX = "mdx"
MODEL_STR = "str"


# pylint: disable=too-many-instance-attributes, too-many-public-methods
Expand Down Expand Up @@ -242,6 +269,7 @@ def __init__(
self._deviceinfo_received = asyncio.Event()
self._alm_number = {"None": 0}
self._available_input_numbers = []
self._attenuation_range = [-90.0, 0]
self.zones: Dict[int, Zone] = {1: Zone(self, 1)}
self.values: Dict[str, str] = {}

Expand Down Expand Up @@ -394,7 +422,10 @@ async def _assemble_buffer(self):
disassembles the chain of datagrams into individual messages which
are then passed on for interpretation.
"""
self.transport.pause_reading()
try:
self.transport.pause_reading()
except AttributeError:
self.log.warning('Lost connection to receiver while assembling buffer')
Hyralex marked this conversation as resolved.
Show resolved Hide resolved

for message in self.buffer.split(";"):
if message != "":
Expand All @@ -409,7 +440,10 @@ async def _assemble_buffer(self):

self.buffer = ""

self.transport.resume_reading()
try:
self.transport.resume_reading()
except AttributeError:
self.log.warning('Lost connection to receiver while assembling buffer')
return

def _populate_inputs(self, total):
Expand Down Expand Up @@ -692,6 +726,13 @@ def set_model_command(self, model: str):
self._ignored_commands = COMMANDS_X20 + COMMANDS_X40 + COMMANDS_MDX_IGNORE
self._model_series = MODEL_MDX
self.query("MAC")
elif "STR" in model:
self.log.debug("Set Command to Model STR")
self._ignored_commands = COMMANDS_STR_IGNORE
self._model_series = MODEL_STR
self._alm_number = ALM_NUMBER_STR
self._attenuation_range = [-96.0, 7.0]
self.query("IDN")
else:
self.log.debug("Set Command to Model x20")
self._ignored_commands = COMMANDS_X40 + COMMANDS_MDX
Expand All @@ -709,6 +750,8 @@ def set_zones(self, model: str):
number_of_zones = 4
# MDX 16 input number range is 1 to 12, but MDX 8 only have 1 to 4 and 9
self._available_input_numbers = [1, 2, 3, 4, 9]
elif self._model_series == MODEL_STR:
number_of_zones = 1
else:
number_of_zones = 2

Expand Down Expand Up @@ -765,6 +808,18 @@ def formatted_command(self, command: str):
"No transport found, unable to send command. error: %s", str(error)
)

@property
def min_attenuation(self):
return self._attenuation_range[0]

@property
def max_attenuation(self):
return self._attenuation_range[1]

@property
def attenuation_range(self):
return self.max_attenuation - self.min_attenuation

@property
def support_audio_listening_mode(self) -> bool:
"""Return true if the zone support audio listening mode."""
Expand All @@ -785,7 +840,7 @@ def attenuation(self):
"""Current volume attenuation in dB (read/write).

You can get or set the current attenuation value on the device with this
property. Valid range from -90 to 0.
property. Valid range usually from -90 to 0.

:Examples:

Expand Down Expand Up @@ -1251,11 +1306,12 @@ def get_current_input_value(self, command: str) -> str:
@property
def support_attenuation(self) -> bool:
"""Return true if the zone support sound mode and sound mode list."""
return self._avr._model_series == MODEL_X20
return self._avr._model_series in [MODEL_X20, MODEL_STR]

#
# Volume and Attenuation handlers. The Anthem tracks volume internally as
# an attenuation level ranging from -90dB (silent) to 0dB (bleeding ears)
# an attenuation level usually ranging from -90dB (silent) to 0dB (bleeding ears).
# Note that STR pre-amplifiers have a range from -96dB to +7dB
#
# We expose this in three methods for the convenience of downstream apps
# which will almost certainly be doing things their own way:
Expand All @@ -1265,23 +1321,23 @@ def support_attenuation(self) -> bool:
# - volume_as_percentage (0-1 floating point)
#

def attenuation_to_volume(self, value: int) -> int:
def attenuation_to_volume(self, value: float) -> int:
"""Convert a native attenuation value to a volume value.

Takes an attenuation in dB from the Anthem (-90 to 0) and converts it
Takes an attenuation in dB from the Anthem (usually -90 to 0) and converts it
into a normal volume value (0-100).

:param arg1: attenuation in dB (negative integer from -90 to 0)
:type arg1: int
:param arg1: attenuation in dB
:type arg1: float

returns an integer value representing volume
"""
try:
return round((90.00 + int(value)) / 90 * 100)
return int((round(2 * (float(value) - self._avr.min_attenuation)) / 2) / self._avr.attenuation_range * 100)
except ValueError:
return 0

def volume_to_attenuation(self, value: int):
def volume_to_attenuation(self, value: int) -> float:
"""Convert a volume value to a native attenuation value.

Takes a volume value and turns it into an attenuation value suitable
Expand All @@ -1290,12 +1346,12 @@ def volume_to_attenuation(self, value: int):
:param arg1: volume (integer from 0 to 100)
:type arg1: int

returns a negative integer value representing attenuation in dB
returns a float value representing attenuation in dB
"""
try:
return round((value / 100) * 90) - 90
return round(2 * (self._avr.min_attenuation + ((float(value) / 100) * self._avr.attenuation_range))) / 2
except ValueError:
return -90
return self._avr.min_attenuation

@property
def power(self) -> bool:
Expand Down Expand Up @@ -1361,22 +1417,22 @@ def volume_as_percentage(self, value: float):
self.volume = value

@property
def attenuation(self) -> int:
def attenuation(self) -> float:
"""Current volume attenuation in dB (read/write).

You can get or set the current attenuation value on the device with this
property. Valid range from -90 to 0.
property. Valid range usually from -90 to 0.

:Examples:

>>> attvalue = attenuation
>>> attenuation = -50
"""
return self._get_integer("VOL", -90)
return self._get_integer("VOL", self._avr.min_attenuation)

@attenuation.setter
def attenuation(self, value: int):
if -90 <= value <= 0:
if self._avr.min_attenuation <= value <= self._avr.max_attenuation:
self._avr.log.debug("Setting attenuation to %s", str(value))
self.command(f"VOL{value}")

Expand Down