Skip to content

Commit

Permalink
Release v5.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdwetering committed Oct 31, 2022
2 parents 6c3624e + ad97697 commit 36bffab
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 46 deletions.
36 changes: 33 additions & 3 deletions custom_components/yamaha_ynca/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
LOGGER,
MANUFACTURER_NAME,
)
from .helpers import serial_url_from_user_input, DomainEntryData
from .helpers import DomainEntryData

PLATFORMS: List[Platform] = [Platform.MEDIA_PLAYER, Platform.BUTTON]

Expand All @@ -39,7 +39,7 @@ async def update_device_registry(
configuration_url = None
if matches := re.match(
r"socket:\/\/(.+):\d+", # Extract IP or hostname
serial_url_from_user_input(config_entry.data[CONF_SERIAL_URL]),
config_entry.data[CONF_SERIAL_URL],
):
configuration_url = f"http://{matches[1]}"

Expand Down Expand Up @@ -70,6 +70,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
if config_entry.version == 3:
migrate_v3(hass, config_entry)

if config_entry.version == 4:
migrate_v4(hass, config_entry)

# When adding new migrations do _not_ forget
# to increase the VERSION of the YamahaYncaConfigFlow

Expand All @@ -82,6 +85,33 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return True


def migrate_v4(hass: HomeAssistant, config_entry: ConfigEntry):
# For "network" type the IP address or host is stored as a socket:// url directly
# Convert serial urls using the old "network" format
# Re-uses the old `serial_url_from_user_input` helper function
import ipaddress

def serial_url_from_user_input(user_input: str) -> str:
# Try and see if an IP address was passed in
# and convert to a socket url
try:
parts = user_input.split(":")
if len(parts) <= 2:
ipaddress.ip_address(parts[0]) # Throws when invalid IP
port = int(parts[1]) if len(parts) == 2 else 50000
return f"socket://{parts[0]}:{port}"
except ValueError:
pass

return user_input

new = {**config_entry.data}
new["serial_url"] = serial_url_from_user_input(config_entry.data["serial_url"])

config_entry.version = 5
hass.config_entries.async_update_entry(config_entry, data=new)


def migrate_v3(hass: HomeAssistant, config_entry: ConfigEntry):
# Changed how hidden soundmodes are stored
# Used to be the enum name, now it is the value
Expand Down Expand Up @@ -193,7 +223,7 @@ def on_disconnect():
hass.config_entries.async_update_entry(entry, data=entry.data)

ynca_receiver = ynca.Ynca(
serial_url_from_user_input(entry.data[CONF_SERIAL_URL]),
entry.data[CONF_SERIAL_URL],
on_disconnect,
COMMUNICATION_LOG_SIZE,
)
Expand Down
12 changes: 6 additions & 6 deletions custom_components/yamaha_ynca/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
CONF_HIDDEN_INPUTS_FOR_ZONE,
CONF_HIDDEN_SOUND_MODES,
CONF_SERIAL_URL,
CONF_IP_ADDRESS,
CONF_HOST,
CONF_PORT,
DOMAIN,
ZONE_SUBUNIT_IDS,
LOGGER,
)
from .helpers import DomainEntryData, serial_url_from_user_input
from .helpers import DomainEntryData

import ynca

Expand All @@ -47,7 +47,7 @@ def get_network_schema(user_input):
return vol.Schema(
{
vol.Required(
CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS, vol.UNDEFINED)
CONF_HOST, default=user_input.get(CONF_HOST, vol.UNDEFINED)
): str,
vol.Required(CONF_PORT, default=user_input.get(CONF_PORT, 50000)): int,
}
Expand All @@ -64,7 +64,7 @@ def validate_connection(serial_url):
return ynca.Ynca(serial_url).connection_check()

modelname = await hass.async_add_executor_job(
validate_connection, serial_url_from_user_input(data[CONF_SERIAL_URL])
validate_connection, data[CONF_SERIAL_URL]
)

# Return info that you want to store in the config entry.
Expand All @@ -74,7 +74,7 @@ def validate_connection(serial_url):
class YamahaYncaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Yamaha (YNCA)."""

VERSION = 4
VERSION = 5

@staticmethod
@callback
Expand Down Expand Up @@ -137,7 +137,7 @@ async def async_step_network(
)

connection_data = {
CONF_SERIAL_URL: f"{user_input[CONF_IP_ADDRESS]}:{user_input[CONF_PORT]}"
CONF_SERIAL_URL: f"socket://{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
}
return await self.async_try_connect(
STEP_ID_NETWORK, get_network_schema(user_input), connection_data
Expand Down
3 changes: 2 additions & 1 deletion custom_components/yamaha_ynca/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
COMMUNICATION_LOG_SIZE = 1000

CONF_SERIAL_URL = "serial_url"
CONF_IP_ADDRESS = "ip_address"
CONF_HOST = "host"
CONF_PORT = "port"

MANUFACTURER_NAME = "Yamaha"
Expand All @@ -25,5 +25,6 @@

CONF_HIDDEN_SOUND_MODES = "hidden_sound_modes"


def CONF_HIDDEN_INPUTS_FOR_ZONE(zone: str):
return f"hidden_inputs_{zone}"
16 changes: 0 additions & 16 deletions custom_components/yamaha_ynca/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Helpers for the Yamaha (YNCA) integration."""

from dataclasses import dataclass
import ipaddress
from typing import List

import ynca
Expand All @@ -25,18 +24,3 @@ def scale(input_value, input_range, output_range):
value_scaled = float(input_value - input_min) / float(input_spread)

return output_min + (value_scaled * output_spread)


def serial_url_from_user_input(user_input: str) -> str:
# Try and see if an IP address was passed in
# and convert to a socket url
try:
parts = user_input.split(":")
if len(parts) <= 2:
ipaddress.ip_address(parts[0]) # Throws when invalid IP
port = int(parts[1]) if len(parts) == 2 else 50000
return f"socket://{parts[0]}:{port}"
except ValueError:
pass

return user_input
2 changes: 1 addition & 1 deletion custom_components/yamaha_ynca/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
"loggers": [
"ynca"
],
"version": "5.0.0"
"version": "5.1.0"
}
6 changes: 3 additions & 3 deletions custom_components/yamaha_ynca/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
},
"network": {
"title": "Network connection",
"description": "Input the IP address of the receiver and the port.\n\nLeave the port at default 50000 unless you have configured a different port on the receiver.",
"description": "Input the IP address or hostname of the receiver and the port.\n\nLeave the port at default 50000 unless you have configured a different port on the receiver.",
"data": {
"ip_address": "Receiver IP address e.g. 192.168.1.123",
"host": "Receiver IP address or hostname e.g. 192.168.1.123",
"port": "YNCA port; default is 50000"
}
},
"advanced": {
"title": "Advanced",
"title": "PySerial URL handler (advanced)",
"description": "Provide any [URL handler as supported by PySerial](https://pyserial.readthedocs.io/en/latest/url_handlers.html).\n\nThis can come in handy when addressing USB adapters by serial with `hwgrep://` or use `rfc2217://hostname_or_ip` to connect through an rfc2217 compatible server.",
"data": {
"serial_url": "URL handler"
Expand Down
6 changes: 3 additions & 3 deletions custom_components/yamaha_ynca/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
},
"network": {
"title": "Network connection",
"description": "Input the IP address of the receiver and the port.\n\nLeave the port at default 50000 unless you have configured a different port on the receiver.",
"description": "Input the IP address or hostname of the receiver and the port.\n\nLeave the port at default 50000 unless you have configured a different port on the receiver.",
"data": {
"ip_address": "Receiver IP address e.g. 192.168.1.123",
"host": "Receiver IP address or hostname e.g. 192.168.1.123",
"port": "YNCA port; default is 50000"
}
},
"advanced": {
"title": "Advanced",
"title": "PySerial URL handler (advanced)",
"description": "Provide any [URL handler as supported by PySerial](https://pyserial.readthedocs.io/en/latest/url_handlers.html).\n\nThis can come in handy when addressing USB adapters by serial with `hwgrep://` or use `rfc2217://hostname_or_ip` to connect through an rfc2217 compatible server.",
"data": {
"serial_url": "URL handler"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async def test_network_connect(hass: HomeAssistant) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
yamaha_ynca.const.CONF_IP_ADDRESS: "192.168.1.123",
yamaha_ynca.const.CONF_HOST: "hostname_or_ipaddress",
yamaha_ynca.const.CONF_PORT: 12345,
},
)
Expand All @@ -47,7 +47,7 @@ async def test_network_connect(hass: HomeAssistant) -> None:
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "ModelName"
assert result2["data"] == {
yamaha_ynca.CONF_SERIAL_URL: "192.168.1.123:12345",
yamaha_ynca.CONF_SERIAL_URL: "socket://hostname_or_ipaddress:12345",
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
Expand Down
22 changes: 13 additions & 9 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from custom_components.yamaha_ynca.helpers import serial_url_from_user_input
from custom_components.yamaha_ynca.helpers import scale


def test_serial_url_from_user_input_ip_address_ok(hass):
assert serial_url_from_user_input("1.2.3.4") == "socket://1.2.3.4:50000"
assert serial_url_from_user_input("1.2.3.4:5") == "socket://1.2.3.4:5"
def test_scale(hass):
assert scale(1, [1, 10], [2, 11]) == 2


def test_serial_url_from_user_input_not_an_ip_address(hass):
assert serial_url_from_user_input("not an ip address") == "not an ip address"
assert serial_url_from_user_input("1.2.3.999") == "1.2.3.999"
assert serial_url_from_user_input("1.2.3.4:abcd") == "1.2.3.4:abcd"
assert serial_url_from_user_input("1.2.3.4:5:6") == "1.2.3.4:5:6"
# def test_serial_url_from_user_input_ip_address_ok(hass):
# assert serial_url_from_user_input("1.2.3.4") == "socket://1.2.3.4:50000"
# assert serial_url_from_user_input("1.2.3.4:5") == "socket://1.2.3.4:5"


# def test_serial_url_from_user_input_not_an_ip_address(hass):
# assert serial_url_from_user_input("not an ip address") == "not an ip address"
# assert serial_url_from_user_input("1.2.3.999") == "1.2.3.999"
# assert serial_url_from_user_input("1.2.3.4:abcd") == "1.2.3.4:abcd"
# assert serial_url_from_user_input("1.2.3.4:5:6") == "1.2.3.4:5:6"
68 changes: 66 additions & 2 deletions tests/test_init_migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


async def test_async_migration_entry(hass: HomeAssistant):
"""Full chain of migrations should result is last version"""
"""Full chain of migrations should result in last version"""

old_entry = MockConfigEntry(
domain=yamaha_ynca.DOMAIN,
Expand All @@ -30,7 +30,7 @@ async def test_async_migration_entry(hass: HomeAssistant):
assert migration_success == True

new_entry = hass.config_entries.async_get_entry(old_entry.entry_id)
assert new_entry.version == 4
assert new_entry.version == 5


async def test_async_migration_entry_version_1(hass: HomeAssistant):
Expand Down Expand Up @@ -150,3 +150,67 @@ async def test_async_migration_entry_version_3_no_hidden_soundmodes(
new_entry = hass.config_entries.async_get_entry(old_entry.entry_id)
assert new_entry.version == 4
assert new_entry.options.get(CONF_HIDDEN_SOUND_MODES, None) is None


async def test_async_migration_entry_version_4_is_ipaddress(hass: HomeAssistant):

old_entry = MockConfigEntry(
domain=yamaha_ynca.DOMAIN,
entry_id="entry_id",
title="ModelName",
data={"serial_url": "1.2.3.4"},
version=4,
)
old_entry.add_to_hass(hass)

yamaha_ynca.migrate_v4(hass, old_entry)
await hass.async_block_till_done()

# IP address converted to socket:// url
new_entry = hass.config_entries.async_get_entry(old_entry.entry_id)
assert new_entry.version == 5
assert new_entry.data["serial_url"] == "socket://1.2.3.4:50000"


async def test_async_migration_entry_version_4_is_ipaddress_and_port(
hass: HomeAssistant,
):

old_entry = MockConfigEntry(
domain=yamaha_ynca.DOMAIN,
entry_id="entry_id",
title="ModelName",
data={"serial_url": "1.2.3.4:56789"},
version=4,
)
old_entry.add_to_hass(hass)

yamaha_ynca.migrate_v4(hass, old_entry)
await hass.async_block_till_done()

# IP address converted to socket:// url
new_entry = hass.config_entries.async_get_entry(old_entry.entry_id)
assert new_entry.version == 5
assert new_entry.data["serial_url"] == "socket://1.2.3.4:56789"


async def test_async_migration_entry_version_4_is_not_ipaddress(
hass: HomeAssistant,
):

old_entry = MockConfigEntry(
domain=yamaha_ynca.DOMAIN,
entry_id="entry_id",
title="ModelName",
data={"serial_url": "not an ip address"},
version=4,
)
old_entry.add_to_hass(hass)

yamaha_ynca.migrate_v4(hass, old_entry)
await hass.async_block_till_done()

# IP address converted to socket:// url
new_entry = hass.config_entries.async_get_entry(old_entry.entry_id)
assert new_entry.version == 5
assert new_entry.data["serial_url"] == "not an ip address"

0 comments on commit 36bffab

Please sign in to comment.