Skip to content

Commit

Permalink
Release v7.10.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdwetering committed Jul 27, 2024
2 parents c101066 + d7fd1b9 commit 393bf59
Show file tree
Hide file tree
Showing 30 changed files with 525 additions and 531 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/validations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: "ubuntu-latest"
strategy:
matrix:
python-version: ["3.11"]
python-version: ["3.12"]
steps:
- uses: "actions/checkout@v4"
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -32,7 +32,7 @@ jobs:
pytest
- name: Check typing
run: |
mypy custom_components --check-untyped-defs
mypy custom_components --check-untyped-defs --enable-incomplete-feature=NewGenericSyntax
- name: Hassfest validation
uses: "home-assistant/actions/hassfest@master"
- name: HACS validation
Expand Down
61 changes: 33 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

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

According to reports of users and info found on the internet the following AV receivers should be working, there are probably more, just give it a try. If your receiver works and is not in the list please post a message in the [discussions](https://github.com/mvdwetering/yamaha_ynca/discussions) so the list can be updated.
According to reports of users and info found on the internet the following AV receivers should be working. There are probably more receivers that work, just give it a try. If your receiver works and is not in the list, please post a message in the [discussions](https://github.com/mvdwetering/yamaha_ynca/discussions) so the list can be updated.

> HTR-4065, HTR-4071, HTR-6064, RX-A2A, RX-A6A, RX-A660, RX-A700, RX-A710, RX-A720, RX-A740, RX-A750, RX-A800, RX-A810, RX-A820, RX-A830, RX-A840, RX-A850, RX-A870, RX-A1000, RX-A1010, RX-A1020, RX-A1030, RX-A1040, RX-A2000, RX-A2010, RX-A2020, RX-A2070, RX-A3000, RX-A3010, RX-A3020, RX-A3030, RX-A3070, RX-S600D, RX-V475, RX-V477, RX-V481D, RX-V483, RX-V500D, RX-V575, RX-V671, RX-V673, RX-V675, RX-V677, RX-V679, RX-V681, RX-V685, RX-V771, RX-V773, RX-V775, RX-V777, RX-V867, RX-V871, RX-V1067, RX-V1071, RX-V1085, RX-V2067, RX-V2071, RX-V3067, RX-V3071, TSR-700, TSR-7850
Expand All @@ -28,9 +28,9 @@ In case of issues or feature requests please [submit an issue on Github](https:/
* Volume control and mute
* Source selection
* Soundmode selection
* Show metadata like artist, album, song (depends on source)
* Control playback (depends on source)
* Activate scenes
* Control playback state (depends on source)
* Provide metadata like artist, album, song (depends on source)
* Activate scenes (like the buttons on the front)
* [Presets](#presets)
* Send [remote control commands](#remote-control)
* Several controllable settings (if supported by receiver):
Expand All @@ -56,24 +56,24 @@ HACS is a 3rd party downloader for Home Assistant to easily install and update c

* 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)". You might need to clear the browser cache for it to show up (e.g. reload with CTRL+F5).
* Go to the Home Assistant integrations menu, 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 the zipfile from the releases.
* Download the zipfile from the releases.
* Extract the zip and copy the contents to the `custom_components` directory.
* Restart Home Assistant
* 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).
* Go to the Home Assistant integrations menu, 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).

## Volume (dB) entity

The volume of a `media_player` entity in Home Assistant has to be in the range 0-to-1. The range of a Yamaha receiver is typically -80.5dB to 16.5dB and is shown in dB unit on the display. This integration maps the full dB range onto the 0-to-1 range in Home Assistant. But this makes setting volume in Home Assistant difficult as those Home Assistant numbers are not easily convertible to the dB numbers shown by the receiver.
The volume of a `media_player` entity in Home Assistant has to be in the range 0-to-1 (shown as 0-100% in the dashboard). The range of a Yamaha receiver is typically -80.5dB to 16.5dB and is shown in the dB unit on the display/overlay. To provide the full volume range to Home Assistant this integration maps the full dB range onto the 0-to-1 range in Home Assistant. However, this makes controlling volume in Home Assistant difficult as the Home Assistant numbers are not easily convertible to the dB numbers as shown by the receiver.

The "Volume (dB)" entity was added to work around this. It is basically the same as the `media_player` volume, but using the familiar dB values that the receiver shows.
The "Volume (dB)" entity was added to simplify this. It is a number entity that controls the volume of a zone, but using the familiar dB unit.

## Remote control

The remote control entity allows sending remote control codes and commands to the receiver. There is remote entity for each zone. Some remote commands are forwarded through HDMI-CEC and can be used to control other devices that way. I guess the commands are also sent over the remote out of the receiver, but that needs to be validated by someone that has equipment connected to the remote out port.
The remote control entity allows sending remote control codes and commands to the receiver. There is remote entity for each zone.

The current list of commands is below. For the list of supported commands for a specific entity check the "commands" attribute of the remote entity. Note that this command list does not take zone capabilities into account, just that there is a known remote control code for that command.

Expand All @@ -98,12 +98,12 @@ target:
In case you want to have buttons on a dashboard to send the commands the code below can be used as a starting point. It uses only standard built-in Home Assistant cards, so it should work on all configurations.
On a dashboard add a "manual" card. Paste the code below and search and replace the `entity_id` with your own.
![image](https://github.com/mvdwetering/yamaha_ynca/assets/732514/321181e2-81c3-4a1d-8084-8efceb94f7ff)
<details>
<summary>Grid with buttons for remote control commands.</summary>
<summary>Code for the grid with buttons for remote control commands.</summary>
![image](https://github.com/mvdwetering/yamaha_ynca/assets/732514/321181e2-81c3-4a1d-8084-8efceb94f7ff)
On a dashboard, add a "manual" card. Paste the code below and search and replace the `entity_id` with your own.

```yaml
type: vertical-stack
Expand Down Expand Up @@ -518,17 +518,16 @@ cards:

## Presets

> [!NOTE]
> Presets for DAB tuner are currently experimental. The DAB tuner uses different commands from the other inputs so I had to guess a bit on how it works and might have been wrong. I am unable to test it because my receiver does not support DAB. Please provide feedback in the [Discussions](https://github.com/mvdwetering/yamaha_ynca/discussions) or [Issues](https://github.com/mvdwetering/yamaha_ynca/issues).
Presets can be activated and stored with the integration for several inputsources. The most obvious input that support presets is the radio inputs like AM/FM or DAB tuner. Inputs that support presets are: Napster, Netradio, Pandora, PC, Rhapsody, Sirius, SiriusIR, Tuner and USB.

Presets can be activated and stored with the integration on many inputs. The most obvious inputs that support presets are the radio inputs like AM/FM tuner. Due to limitations on the protocol the integration can only show the preset number, no name or what is stored. Inputs that support presets are: Napster, Netradio, Pandora, PC, Rhapsody, Sirius, SiriusIR, Tuner and USB.
Presets can be selected in the mediabrowser of the mediaplayer or in automations with the `media_player.play_media` service. When selecting a preset, the receiver will turn on and switch input if needed.

Presets can be selected in the mediabrowser of the mediaplayer or in automations with the `media_player.play_media` service. When selecting a preset the receiver will turn on and switch input if needed.
Due to limitations on the protocol the integration can only show the preset number, no name or what is stored.

### Store presets

Some presets can be managed in the Yamaha AV Control app (e.g. Tuner).
Home Assistant has no standardized way to store presets, so the `store_preset` service was added. It will store a preset with the provided number for the current playing item.
Some presets can be managed in the Yamaha AV Control app (e.g. Tuner presets).
Home Assistant has no standardized way to manage presets, so the `store_preset` service was added. It will store a preset with the provided number for the current playing item.

```yaml
service: yamaha_ynca.store_preset
Expand All @@ -540,7 +539,7 @@ target:

### Media content format

In some cases it is not possible to select presets from the UI and it is needed to provide the `media_content_id` and `media_content_type` manually.
In some cases it is not possible to select presets from the UI and it is needed to manually provide the `media_content_id` and `media_content_type`.

The `media_content_type` is always "music". The `media_content_id` format is listed in the table below. Replace the "1" at the end with the preset number you need.

Expand All @@ -565,16 +564,22 @@ The `media_content_type` is always "music". The `media_content_id` format is lis
The receiver does not allow changing of settings when it is in standby, so the entities become Unavailable in Home Assistant to indicate this.

* **Q: Why does the integration shows too many or not enough features that are available on my receiver?**
The integration tries to autodetect as many features as possible, but it is not possible for all features on all receivers. For example, supported soundmodes, available inputs, scenes or surround decoders cannot always be detected. You can adjust these for your receiver in the integration configuration.
The integration tries to autodetect as many features as possible, but it is not possible for all features on all receivers. You can adjust detected/supported features for your receiver in the integration configuration.

* **Q: Why are Scene buttons are not working?**
On some receivers (e.g. RX-V475 with firmware 1.34/2.06) the command to activate the scenes does not work even though the receiver indicates support for them. There might be more receivers with this issue, please report them in an issue or start a discussion. The non-working buttons can be disabled in the integration configuration by selecting "0" instead of "Auto detect".
* **Q: How can I stream audio from a URL?**
You can't with this integration since the protocol does not support that. You might be able to use the [DLNA Digital Media Renderer integration](https://www.home-assistant.io/integrations/dlna_dmr/) that comes with Home Assistant.

As an alternative the scenes can be activated by sending the scene commands through the [Remote control entity](#remote-control).
* **Q: Why are Scene buttons are not working on some receivers?**
On some receivers (e.g. RX-V475 with firmware 1.34/2.06) the command to activate the scenes does not work even though the receiver indicates support for them. There might be more receivers with this issue, please report them in an issue or start a discussion.

* **Q: How can I fix the connection settings if the connection is not working?**
When the integration cannot connect to the receiver (e.g. due to changed IP address) you can use the "Configure" button on the integration card. A dialog will appear with a message that it can't connect. Press "Submit" in this dialog to mark the integration for reconfiguration. Home Assistant will now allow you to reconfigure the integration (reload of the page in the browser seems required to show the reconfigure card).
The non-working buttons can be disabled in the integration configuration by selecting "0" for number of scenes instead of "Auto detect".

* **Q: How can I stream audio from a URL?**
You can't with this integration since the protocol does not support that. You might be able to use the [DLNA Digital Media Renderer integration](https://www.home-assistant.io/integrations/dlna_dmr/) that comes with Home Assistant.
As an alternative the scenes can be activated by sending the scene commands through service calls on the [Remote control entity](#remote-control).

```yaml
service: remote.send_command
data:
command: scene_1
target:
entity_id: remote.rx_V475_main_remote
```
2 changes: 1 addition & 1 deletion coverage.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
pytest --cov=custom_components/yamaha_ynca tests/ --cov-report term-missing --cov-report html
mypy custom_components --check-untyped-defs
mypy custom_components --check-untyped-defs --enable-incomplete-feature=NewGenericSyntax
64 changes: 38 additions & 26 deletions custom_components/yamaha_ynca/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""The Yamaha (YNCA) integration."""

from __future__ import annotations

import asyncio
Expand All @@ -11,8 +12,9 @@
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry
from homeassistant.helpers import config_validation as cv, device_registry
from homeassistant.helpers.service import ServiceCall, async_extract_config_entry_ids
from homeassistant.helpers.typing import ConfigType

from .const import (
COMMUNICATION_LOG_SIZE,
Expand All @@ -36,6 +38,7 @@
Platform.REMOTE,
]


async def update_device_registry(
hass: HomeAssistant, config_entry: ConfigEntry, receiver: ynca.YncaApi
):
Expand Down Expand Up @@ -106,16 +109,37 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:


async def async_handle_send_raw_ynca(hass: HomeAssistant, call: ServiceCall):
config_entry_ids = await async_extract_config_entry_ids(hass, call)
for config_entry_id in config_entry_ids:
if domain_entry_info := hass.data[DOMAIN].get(config_entry_id, None):
for line in call.data.get("raw_data").splitlines():
line = line.strip()
if line.startswith('@'):
domain_entry_info.api.send_raw(line)
for config_entry_id in await async_extract_config_entry_ids(hass, call):
if config_entry := hass.config_entries.async_get_entry(config_entry_id):
# Check if configentry is ours, could be others when targeting areas for example
if (config_entry.domain == DOMAIN) and (domain_entry_info := config_entry.runtime_data):
# Handle actual call
for line in call.data.get("raw_data").splitlines():
line = line.strip()
if line.startswith("@"):
domain_entry_info.api.send_raw(line)


CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Yamaha (YNCA) integration."""

async def async_handle_send_raw_ynca_local(call: ServiceCall):
await async_handle_send_raw_ynca(hass, call)

hass.services.async_register(
DOMAIN, SERVICE_SEND_RAW_YNCA, async_handle_send_raw_ynca_local
)

return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

type YamahaYncaConfigEntry = ConfigEntry[DomainEntryData]


async def async_setup_entry(hass: HomeAssistant, entry: YamahaYncaConfigEntry) -> bool:
"""Set up Yamaha (YNCA) from a config entry."""

def initialize_ynca(ynca_receiver: ynca.YncaApi):
Expand Down Expand Up @@ -164,44 +188,32 @@ def on_disconnect():
await update_device_registry(hass, entry, ynca_receiver)
await update_configentry(hass, entry, ynca_receiver)

assert(ynca_receiver.sys is not None)
assert ynca_receiver.sys is not None
if receiver_requires_audio_input_workaround(ynca_receiver.sys.modelname):
# Pretend AUDIO provides a name like a normal input
# This makes it work with standard code
ynca_receiver.sys.inpnameaudio = "AUDIO" # type: ignore

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = DomainEntryData(
entry.runtime_data = DomainEntryData(
api=ynca_receiver,
initialization_events=ynca_receiver.get_communication_log_items(),
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

if not hass.services.has_service(DOMAIN, SERVICE_SEND_RAW_YNCA):

async def async_handle_send_raw_ynca_local(call: ServiceCall):
await async_handle_send_raw_ynca(hass, call)

hass.services.async_register(
DOMAIN, SERVICE_SEND_RAW_YNCA, async_handle_send_raw_ynca_local
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

entry.async_on_unload(entry.add_update_listener(async_update_options))

return initialized


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: YamahaYncaConfigEntry) -> bool:
"""Unload a config entry."""

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

if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
domain_entry_info = hass.data[DOMAIN].pop(entry.entry_id)
domain_entry_info = entry.runtime_data
await hass.async_add_executor_job(close_ynca, domain_entry_info.api)

if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_SEND_RAW_YNCA)

return unload_ok
13 changes: 5 additions & 8 deletions custom_components/yamaha_ynca/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,34 @@
from typing import Any

from homeassistant.components.button import ButtonEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import YamahaYncaConfigEntry
from .const import (
CONF_NUMBER_OF_SCENES,
DOMAIN,
MAX_NUMBER_OF_SCENES,
NUMBER_OF_SCENES_AUTODETECT,
ZONE_ATTRIBUTE_NAMES,
)
from .helpers import DomainEntryData


async def async_setup_entry(hass, config_entry, async_add_entities):

domain_entry_data: DomainEntryData = hass.data[DOMAIN][config_entry.entry_id]

async def async_setup_entry(hass: HomeAssistant, config_entry: YamahaYncaConfigEntry, async_add_entities: AddEntitiesCallback):
domain_entry_data = config_entry.runtime_data
entities = []
for zone_attr_name in ZONE_ATTRIBUTE_NAMES:
if zone_subunit := getattr(domain_entry_data.api, zone_attr_name):
number_of_scenes = config_entry.options.get(zone_subunit.id, {}).get(
CONF_NUMBER_OF_SCENES, NUMBER_OF_SCENES_AUTODETECT
)

if number_of_scenes == NUMBER_OF_SCENES_AUTODETECT:
number_of_scenes = 0
for scene_id in range(1, MAX_NUMBER_OF_SCENES + 1):
if getattr(zone_subunit, f"scene{scene_id}name"):
number_of_scenes += 1

number_of_scenes = min(MAX_NUMBER_OF_SCENES, number_of_scenes)

for scene_id in range(1, number_of_scenes + 1):
entities.append(
YamahaYncaSceneButton(config_entry.entry_id, zone_subunit, scene_id)
Expand Down
Loading

0 comments on commit 393bf59

Please sign in to comment.