Skip to content

Commit

Permalink
Release v5.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdwetering committed Dec 2, 2022
2 parents 36bffab + e75c017 commit d8f7214
Show file tree
Hide file tree
Showing 22 changed files with 830 additions and 444 deletions.
11 changes: 0 additions & 11 deletions .github/dependabot.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Zip custom components dir
working-directory: "custom_components"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/validations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
matrix:
python-version: ["3.9", "3.10"]
steps:
- uses: "actions/checkout@v2"
- uses: "actions/checkout@v3"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Custom integration for Home Assistant to support Yamaha AV receivers with the YNCA protocol (serial and IP).

According to the protocol documentation the following AV receivers should be supported (not all tested), there might be more.
According to reports of users and info found on the internet the following AV receivers should be supported (not all tested), there might be more. If your receiver works and is not in the list please let us know!

> RX-A700, RX-A710, RX-A800, RX-A810, RX-A840, RX-A850, RX-A1000, RX-A1010, RX-A1040, RX-A2000, RX-A2010, RX-A3000, RX-A3010, RX-V475, RX-V671, RX-V673, RX-V867, RX-V871, RX-V1067, RX-V2067, RX-V3067, TSR-700
Expand Down Expand Up @@ -30,31 +30,33 @@ It is not possible to autodetect all features of a receiver. However there are s

### Scene buttons not working

For some receivers (e.g. RX-V475) the command to activate the scenes does not work even though scenes are supported by the receiver. As a workaround, just hide the scene button entities in Home Assistant
For some receivers (e.g. RX-V475) the command to activate the scenes does not work even though scenes are supported by the receiver. As a workaround, hide the scene button entities in Home Assistant

### Inputs do not match zone

It is only possible to detect all possible inputs on the receiver, not which ones work which what zones.
For most receivers the inputs available on the receiver can be detected, but it is not possible to detect which of those inputs are available per zone.

You can hide the inputs per zone in the integration configuration which can be accessed by pressing the "Configure" button on the integration card in the "Devices & Services" section of the Home Assistant settings.
You can select the inputs per zone in the integration configuration which can be accessed by pressing the "Configure" button on the integration card in the "Devices & Services" section of the Home Assistant settings.

### Soundmodes do not match receiver

Since the list of soundmodes can not be detected by default the whole list of known soundmodes is shown.
You can hide the soundmodes that do not apply to the receiver in the integration configuration which can be accessed by pressing the "Configure" button on the integration card in the "Devices & Services" section of the Home Assistant settings.
The list of soundmodes can not be detected, so by default the whole list of known soundmodes is shown.

You can select the soundmodes applicable the specific receiver in the integration configuration which can be accessed by pressing the "Configure" button on the integration card in the "Devices & Services" section of the Home Assistant settings.

## Installation

### HACS

Recommended as you get notified of updates.
*Recommended as you get notified of updates.*

* Add integration within HACS (use the + button and search for "YNCA")
* Restart Home Assistant
* Go to the Home Assistant integrations menu and press the Add button and search for "Yamaha (YNCA)"
* Go to the Home Assistant integrations menu and press the Add button and search for "Yamaha (YNCA)". You might need to clear the browser cache for it to show up (e.g. reload with CTRL+F5).

### Manual

* Install the custom component by downloading it and copy it to the `custom_components` directory as usual.
* Install the custom component by downloading the zipfile from the release
* Extract the zip and copy the contents to the `custom_components` directory as usual.
* Restart Home Assistant
* Go to the Home Assistant integrations menu and press the Add button and search for "Yamaha (YNCA)"
* Go to the Home Assistant integrations menu and press the Add button and search for "Yamaha (YNCA)". You might need to clear the browser cache for it to show up (e.g. reload with CTRL+F5).
16 changes: 8 additions & 8 deletions custom_components/yamaha_ynca/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@


async def update_device_registry(
hass: HomeAssistant, config_entry: ConfigEntry, receiver: ynca.Ynca
hass: HomeAssistant, config_entry: ConfigEntry, receiver: ynca.YncaApi
):
assert receiver.SYS is not None
assert receiver.sys is not None

# Configuration URL for devices connected through IP
configuration_url = None
Expand All @@ -49,9 +49,9 @@ async def update_device_registry(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, config_entry.entry_id)},
manufacturer=MANUFACTURER_NAME,
name=f"{MANUFACTURER_NAME} {receiver.SYS.modelname}",
model=receiver.SYS.modelname,
sw_version=receiver.SYS.version,
name=f"{MANUFACTURER_NAME} {receiver.sys.modelname}",
model=receiver.sys.modelname,
sw_version=receiver.sys.version,
configuration_url=configuration_url,
)

Expand Down Expand Up @@ -182,7 +182,7 @@ async def async_handle_send_raw_ynca(hass: HomeAssistant, call: ServiceCall):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Yamaha (YNCA) from a config entry."""

def initialize_ynca(ynca_receiver: ynca.Ynca):
def initialize_ynca(ynca_receiver: ynca.YncaApi):
try:
# Sync function taking a long time (> 10 seconds depending on receiver capabilities)
ynca_receiver.initialize()
Expand Down Expand Up @@ -222,7 +222,7 @@ def on_disconnect():

hass.config_entries.async_update_entry(entry, data=entry.data)

ynca_receiver = ynca.Ynca(
ynca_receiver = ynca.YncaApi(
entry.data[CONF_SERIAL_URL],
on_disconnect,
COMMUNICATION_LOG_SIZE,
Expand Down Expand Up @@ -254,7 +254,7 @@ async def async_handle_send_raw_ynca_local(call: ServiceCall):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""

def close_ynca(ynca_receiver: ynca.Ynca):
def close_ynca(ynca_receiver: ynca.YncaApi):
ynca_receiver.close()

if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
Expand Down
26 changes: 15 additions & 11 deletions custom_components/yamaha_ynca/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from homeassistant.components.button import ButtonEntity

from .const import DOMAIN, ZONE_SUBUNIT_IDS
from .const import DOMAIN, ZONE_SUBUNITS
from .helpers import DomainEntryData


Expand All @@ -11,12 +11,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
domain_entry_data: DomainEntryData = hass.data[DOMAIN][config_entry.entry_id]

entities = []
for zone in ZONE_SUBUNIT_IDS:
if zone_subunit := getattr(domain_entry_data.api, zone):
for scene_id in zone_subunit.scenenames.keys():
entities.append(
YamahaYncaSceneButton(config_entry.entry_id, zone_subunit, scene_id)
)
for zone_attr_name in ZONE_SUBUNITS:
if zone_subunit := getattr(domain_entry_data.api, zone_attr_name):
for scene_id in range(1, 12 + 1):
if getattr(zone_subunit, f"scene{scene_id}name"):
entities.append(
YamahaYncaSceneButton(
config_entry.entry_id, zone_subunit, scene_id
)
)

async_add_entities(entities)

Expand All @@ -37,8 +40,9 @@ def __init__(self, receiver_unique_id, zone, scene_id):
"identifiers": {(DOMAIN, receiver_unique_id)},
}

def update_callback(self):
self.schedule_update_ha_state()
def update_callback(self, function, value):
if function in ["ZONENAME", f"SCENE{self._scene_id}NAME"]:
self.schedule_update_ha_state()

async def async_added_to_hass(self):
self._zone.register_update_callback(self.update_callback)
Expand All @@ -48,7 +52,7 @@ async def async_will_remove_from_hass(self):

@property
def name(self):
return f"{self._zone.zonename}: {self._zone.scenenames[self._scene_id]}"
return f"{self._zone.zonename}: {getattr(self._zone, f'scene{self._scene_id}name', f'Scene {self._scene_id}')}"

def press(self) -> None:
self._zone.activate_scene(self._scene_id)
self._zone.scene(self._scene_id)
32 changes: 19 additions & 13 deletions custom_components/yamaha_ynca/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv

from custom_components.yamaha_ynca.input_helpers import InputHelper

from .const import (
CONF_HIDDEN_INPUTS_FOR_ZONE,
CONF_HIDDEN_SOUND_MODES,
CONF_SERIAL_URL,
CONF_HOST,
CONF_PORT,
DOMAIN,
ZONE_SUBUNIT_IDS,
ZONE_SUBUNITS,
LOGGER,
)
from .helpers import DomainEntryData
Expand Down Expand Up @@ -61,7 +63,7 @@ async def validate_input(hass: HomeAssistant, data: Dict[str, Any]) -> Dict[str,
"""

def validate_connection(serial_url):
return ynca.Ynca(serial_url).connection_check()
return ynca.YncaApi(serial_url).connection_check()

modelname = await hass.async_add_executor_job(
validate_connection, data[CONF_SERIAL_URL]
Expand All @@ -74,6 +76,7 @@ def validate_connection(serial_url):
class YamahaYncaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Yamaha (YNCA)."""

# When updating also update the one used in `setup_integration` for tests
VERSION = 5

@staticmethod
Expand Down Expand Up @@ -172,11 +175,13 @@ async def async_step_init(self, user_input=None):
self.config_entry.entry_id, None
)
api = domain_entry_data.api
modelinfo = ynca.get_modelinfo(api.SYS.modelname)
modelinfo = ynca.YncaModelInfo.get(api.sys.modelname)

# Hiding sound modes
sound_modes = []
for sound_mode in ynca.SoundPrg:
if sound_mode is ynca.SoundPrg.UNKNOWN:
continue
if modelinfo and not sound_mode in modelinfo.soundprg:
continue # Skip soundmodes not supported on the model
sound_modes.append(sound_mode.value)
Expand All @@ -199,22 +204,23 @@ async def async_step_init(self, user_input=None):

# Hiding inputs per zone
inputs = {}
for inputinfo in ynca.get_inputinfo_list(api):
inputs[inputinfo.input] = (
f"{inputinfo.input} ({inputinfo.name})"
if inputinfo.input != inputinfo.name
else inputinfo.name
for input, name in InputHelper.get_source_mapping(api).items():
inputs[input.value] = (
f"{input.value} ({name})"
if input.value.lower() != name.strip().lower()
else name
)

# Sorts the inputs (3.7+ dicts maintain insertion order)
inputs = dict(sorted(inputs.items(), key=lambda tup: tup[0]))
inputs = dict(sorted(inputs.items(), key=lambda item: item[1]))

for zone_id in ZONE_SUBUNIT_IDS:
if getattr(api, zone_id, None):
for zone_attr_name in ZONE_SUBUNITS:
if getattr(api, zone_attr_name, None):
schema[
vol.Required(
CONF_HIDDEN_INPUTS_FOR_ZONE(zone_id),
CONF_HIDDEN_INPUTS_FOR_ZONE(zone_attr_name.upper()),
default=self.config_entry.options.get(
CONF_HIDDEN_INPUTS_FOR_ZONE(zone_id), []
CONF_HIDDEN_INPUTS_FOR_ZONE(zone_attr_name.upper()), []
),
)
] = cv.multi_select(inputs)
Expand Down
12 changes: 6 additions & 6 deletions custom_components/yamaha_ynca/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Constants for the Yamaha (YNCA) integration."""

import logging
import ynca

DOMAIN = "yamaha_ynca"
LOGGER = logging.getLogger(__package__)
Expand All @@ -14,13 +13,14 @@

MANUFACTURER_NAME = "Yamaha"

ZONE_MAX_VOLUME = 16.5 # Seems to be 16.5 when MAXVOL function not implemented
ZONE_MIN_VOLUME = -80.5

ZONE_SUBUNIT_IDS = [
ynca.Subunit.MAIN,
ynca.Subunit.ZONE2,
ynca.Subunit.ZONE3,
ynca.Subunit.ZONE4,
ZONE_SUBUNITS = [
"main",
"zone2",
"zone3",
"zone4",
]

CONF_HIDDEN_SOUND_MODES = "hidden_sound_modes"
Expand Down
10 changes: 5 additions & 5 deletions custom_components/yamaha_ynca/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ async def async_get_config_entry_diagnostics(
# Add data from the device itself
domain_entry_data: DomainEntryData = hass.data[DOMAIN].get(entry.entry_id, None)
if domain_entry_data:
api: ynca.Ynca = domain_entry_data.api
if api.SYS:
data["SYS"] = {
"modelname": api.SYS.modelname,
"version": api.SYS.version,
api: ynca.YncaApi = domain_entry_data.api
if api.sys:
data["sys"] = {
"modelname": api.sys.modelname,
"version": api.sys.version,
}
data["communication"] = {
"initialization": domain_entry_data.initialization_events,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/yamaha_ynca/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

@dataclass
class DomainEntryData:
api: ynca.Ynca
api: ynca.YncaApi
initialization_events: List[str]


Expand Down
Loading

0 comments on commit d8f7214

Please sign in to comment.