mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 08:51:46 -04:00
Bump pyjvcprojector to 2.0.0 (#160739)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
co-authored by
Joost Lekkerkerker
parent
862a2bc95c
commit
907861effd
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
|
||||
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorTimeoutError
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@@ -11,8 +11,9 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
|
||||
from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
|
||||
|
||||
@@ -28,8 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
try:
|
||||
await device.connect(True)
|
||||
except JvcProjectorConnectError as err:
|
||||
await device.connect()
|
||||
except JvcProjectorTimeoutError as err:
|
||||
await device.disconnect()
|
||||
raise ConfigEntryNotReady(
|
||||
f"Unable to connect to {entry.data[CONF_HOST]}"
|
||||
@@ -50,6 +51,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect)
|
||||
)
|
||||
|
||||
await async_migrate_entities(hass, entry, coordinator)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
@@ -60,3 +63,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
await entry.runtime_data.device.disconnect()
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_migrate_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: JVCConfigEntry,
|
||||
coordinator: JvcProjectorDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Migrate old entities as needed."""
|
||||
|
||||
@callback
|
||||
def _update_entry(entry: RegistryEntry) -> dict[str, str] | None:
|
||||
"""Fix unique_id of power binary_sensor entry."""
|
||||
if entry.domain == Platform.BINARY_SENSOR and ":" not in entry.unique_id:
|
||||
if "_power" in entry.unique_id:
|
||||
return {"new_unique_id": f"{coordinator.unique_id}_power"}
|
||||
return None
|
||||
|
||||
await async_migrate_entries(hass, config_entry.entry_id, _update_entry)
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from jvcprojector import const
|
||||
from jvcprojector import command as cmd
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import POWER
|
||||
from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
|
||||
from .entity import JvcProjectorEntity
|
||||
|
||||
ON_STATUS = (const.ON, const.WARMING)
|
||||
ON_STATUS = (cmd.Power.ON, cmd.Power.WARMING)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -21,14 +22,13 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the JVC Projector platform from a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities([JvcBinarySensor(coordinator)])
|
||||
|
||||
|
||||
class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
|
||||
"""The entity class for JVC Projector Binary Sensor."""
|
||||
|
||||
_attr_translation_key = "jvc_power"
|
||||
_attr_translation_key = "power"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -36,9 +36,9 @@ class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
|
||||
) -> None:
|
||||
"""Initialize the JVC Projector sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = f"{coordinator.device.mac}_power"
|
||||
self._attr_unique_id = f"{coordinator.unique_id}_power"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the JVC is on."""
|
||||
return self.coordinator.data["power"] in ON_STATUS
|
||||
"""Return true if the JVC Projector is on."""
|
||||
return self.coordinator.data[POWER] in ON_STATUS
|
||||
|
||||
@@ -5,7 +5,12 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
|
||||
from jvcprojector import (
|
||||
JvcProjector,
|
||||
JvcProjectorAuthError,
|
||||
JvcProjectorTimeoutError,
|
||||
command as cmd,
|
||||
)
|
||||
from jvcprojector.projector import DEFAULT_PORT
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -40,7 +45,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
mac = await get_mac_address(host, port, password)
|
||||
except InvalidHost:
|
||||
errors["base"] = "invalid_host"
|
||||
except JvcProjectorConnectError:
|
||||
except JvcProjectorTimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except JvcProjectorAuthError:
|
||||
errors["base"] = "invalid_auth"
|
||||
@@ -91,7 +96,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
try:
|
||||
await get_mac_address(host, port, password)
|
||||
except JvcProjectorConnectError:
|
||||
except JvcProjectorTimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except JvcProjectorAuthError:
|
||||
errors["base"] = "invalid_auth"
|
||||
@@ -115,7 +120,7 @@ async def get_mac_address(host: str, port: int, password: str | None) -> str:
|
||||
"""Get device mac address for config flow."""
|
||||
device = JvcProjector(host, port=port, password=password)
|
||||
try:
|
||||
await device.connect(True)
|
||||
await device.connect()
|
||||
return await device.get(cmd.MacAddress)
|
||||
finally:
|
||||
await device.disconnect()
|
||||
return device.mac
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
NAME = "JVC Projector"
|
||||
DOMAIN = "jvc_projector"
|
||||
MANUFACTURER = "JVC"
|
||||
|
||||
POWER = "power"
|
||||
INPUT = "input"
|
||||
SOURCE = "source"
|
||||
|
||||
@@ -4,22 +4,21 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from jvcprojector import (
|
||||
JvcProjector,
|
||||
JvcProjectorAuthError,
|
||||
JvcProjectorConnectError,
|
||||
const,
|
||||
JvcProjectorTimeoutError,
|
||||
command as cmd,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import NAME
|
||||
from .const import INPUT, NAME, POWER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -46,26 +45,33 @@ class JvcProjectorDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str]]):
|
||||
update_interval=INTERVAL_SLOW,
|
||||
)
|
||||
|
||||
self.device = device
|
||||
self.unique_id = format_mac(device.mac)
|
||||
self.device: JvcProjector = device
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert config_entry.unique_id is not None
|
||||
self.unique_id = config_entry.unique_id
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Get the latest state data."""
|
||||
state: dict[str, str | None] = {
|
||||
POWER: None,
|
||||
INPUT: None,
|
||||
}
|
||||
|
||||
try:
|
||||
state = await self.device.get_state()
|
||||
except JvcProjectorConnectError as err:
|
||||
state[POWER] = await self.device.get(cmd.Power)
|
||||
|
||||
if state[POWER] == cmd.Power.ON:
|
||||
state[INPUT] = await self.device.get(cmd.Input)
|
||||
|
||||
except JvcProjectorTimeoutError as err:
|
||||
raise UpdateFailed(f"Unable to connect to {self.device.host}") from err
|
||||
except JvcProjectorAuthError as err:
|
||||
raise ConfigEntryAuthFailed("Password authentication failed") from err
|
||||
|
||||
old_interval = self.update_interval
|
||||
|
||||
if state[const.POWER] != const.STANDBY:
|
||||
if state[POWER] != cmd.Power.STANDBY:
|
||||
self.update_interval = INTERVAL_FAST
|
||||
else:
|
||||
self.update_interval = INTERVAL_SLOW
|
||||
|
||||
if self.update_interval != old_interval:
|
||||
_LOGGER.debug("Changed update interval to %s", self.update_interval)
|
||||
|
||||
return state
|
||||
|
||||
@@ -26,7 +26,7 @@ class JvcProjectorEntity(CoordinatorEntity[JvcProjectorDataUpdateCoordinator]):
|
||||
|
||||
self._attr_unique_id = coordinator.unique_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.unique_id)},
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
name=NAME,
|
||||
model=self.device.model,
|
||||
manufacturer=MANUFACTURER,
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["jvcprojector"],
|
||||
"requirements": ["pyjvcprojector==1.1.3"]
|
||||
"requirements": ["pyjvcprojector==2.0.0"]
|
||||
}
|
||||
|
||||
@@ -7,54 +7,62 @@ from collections.abc import Iterable
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from jvcprojector import const
|
||||
from jvcprojector import command as cmd
|
||||
|
||||
from homeassistant.components.remote import RemoteEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import POWER
|
||||
from .coordinator import JVCConfigEntry
|
||||
from .entity import JvcProjectorEntity
|
||||
|
||||
COMMANDS = {
|
||||
"menu": const.REMOTE_MENU,
|
||||
"up": const.REMOTE_UP,
|
||||
"down": const.REMOTE_DOWN,
|
||||
"left": const.REMOTE_LEFT,
|
||||
"right": const.REMOTE_RIGHT,
|
||||
"ok": const.REMOTE_OK,
|
||||
"back": const.REMOTE_BACK,
|
||||
"mpc": const.REMOTE_MPC,
|
||||
"hide": const.REMOTE_HIDE,
|
||||
"info": const.REMOTE_INFO,
|
||||
"input": const.REMOTE_INPUT,
|
||||
"cmd": const.REMOTE_CMD,
|
||||
"advanced_menu": const.REMOTE_ADVANCED_MENU,
|
||||
"picture_mode": const.REMOTE_PICTURE_MODE,
|
||||
"color_profile": const.REMOTE_COLOR_PROFILE,
|
||||
"lens_control": const.REMOTE_LENS_CONTROL,
|
||||
"setting_memory": const.REMOTE_SETTING_MEMORY,
|
||||
"gamma_settings": const.REMOTE_GAMMA_SETTINGS,
|
||||
"hdmi_1": const.REMOTE_HDMI_1,
|
||||
"hdmi_2": const.REMOTE_HDMI_2,
|
||||
"mode_1": const.REMOTE_MODE_1,
|
||||
"mode_2": const.REMOTE_MODE_2,
|
||||
"mode_3": const.REMOTE_MODE_3,
|
||||
"mode_4": const.REMOTE_MODE_4,
|
||||
"mode_5": const.REMOTE_MODE_5,
|
||||
"mode_6": const.REMOTE_MODE_6,
|
||||
"mode_7": const.REMOTE_MODE_7,
|
||||
"mode_8": const.REMOTE_MODE_8,
|
||||
"mode_9": const.REMOTE_MODE_9,
|
||||
"mode_10": const.REMOTE_MODE_10,
|
||||
"lens_ap": const.REMOTE_LENS_AP,
|
||||
"gamma": const.REMOTE_GAMMA,
|
||||
"color_temp": const.REMOTE_COLOR_TEMP,
|
||||
"natural": const.REMOTE_NATURAL,
|
||||
"cinema": const.REMOTE_CINEMA,
|
||||
"anamo": const.REMOTE_ANAMO,
|
||||
"3d_format": const.REMOTE_3D_FORMAT,
|
||||
COMMANDS: list[str] = [
|
||||
cmd.Remote.MENU,
|
||||
cmd.Remote.UP,
|
||||
cmd.Remote.DOWN,
|
||||
cmd.Remote.LEFT,
|
||||
cmd.Remote.RIGHT,
|
||||
cmd.Remote.OK,
|
||||
cmd.Remote.BACK,
|
||||
cmd.Remote.MPC,
|
||||
cmd.Remote.HIDE,
|
||||
cmd.Remote.INFO,
|
||||
cmd.Remote.INPUT,
|
||||
cmd.Remote.CMD,
|
||||
cmd.Remote.ADVANCED_MENU,
|
||||
cmd.Remote.PICTURE_MODE,
|
||||
cmd.Remote.COLOR_PROFILE,
|
||||
cmd.Remote.LENS_CONTROL,
|
||||
cmd.Remote.SETTING_MEMORY,
|
||||
cmd.Remote.GAMMA_SETTINGS,
|
||||
cmd.Remote.HDMI1,
|
||||
cmd.Remote.HDMI2,
|
||||
cmd.Remote.MODE_1,
|
||||
cmd.Remote.MODE_2,
|
||||
cmd.Remote.MODE_3,
|
||||
cmd.Remote.MODE_4,
|
||||
cmd.Remote.MODE_5,
|
||||
cmd.Remote.MODE_6,
|
||||
cmd.Remote.MODE_7,
|
||||
cmd.Remote.MODE_8,
|
||||
cmd.Remote.MODE_9,
|
||||
cmd.Remote.MODE_10,
|
||||
cmd.Remote.GAMMA,
|
||||
cmd.Remote.NATURAL,
|
||||
cmd.Remote.CINEMA,
|
||||
cmd.Remote.COLOR_TEMP,
|
||||
cmd.Remote.ANAMORPHIC,
|
||||
cmd.Remote.LENS_APERTURE,
|
||||
cmd.Remote.V3D_FORMAT,
|
||||
]
|
||||
|
||||
RENAMED_COMMANDS: dict[str, str] = {
|
||||
"anamo": cmd.Remote.ANAMORPHIC,
|
||||
"lens_ap": cmd.Remote.LENS_APERTURE,
|
||||
"hdmi1": cmd.Remote.HDMI1,
|
||||
"hdmi2": cmd.Remote.HDMI2,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -77,25 +85,34 @@ class JvcProjectorRemote(JvcProjectorEntity, RemoteEntity):
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
return self.coordinator.data["power"] in [const.ON, const.WARMING]
|
||||
"""Return True if the entity is on."""
|
||||
return self.coordinator.data[POWER] in (cmd.Power.ON, cmd.Power.WARMING)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
await self.device.power_on()
|
||||
await self.device.set(cmd.Power, cmd.Power.ON)
|
||||
await asyncio.sleep(1)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
await self.device.power_off()
|
||||
await self.device.set(cmd.Power, cmd.Power.OFF)
|
||||
await asyncio.sleep(1)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
|
||||
"""Send a remote command to the device."""
|
||||
for cmd in command:
|
||||
if cmd not in COMMANDS:
|
||||
raise HomeAssistantError(f"{cmd} is not a known command")
|
||||
_LOGGER.debug("Sending command '%s'", cmd)
|
||||
await self.device.remote(COMMANDS[cmd])
|
||||
for send_command in command:
|
||||
# Legacy name replace
|
||||
if send_command in RENAMED_COMMANDS:
|
||||
send_command = RENAMED_COMMANDS[send_command]
|
||||
|
||||
# Legacy name fixup
|
||||
if "_" in send_command:
|
||||
send_command = send_command.replace("_", "-")
|
||||
|
||||
if send_command not in COMMANDS:
|
||||
raise HomeAssistantError(f"{send_command} is not a known command")
|
||||
|
||||
_LOGGER.debug("Sending command '%s'", send_command)
|
||||
await self.device.remote(send_command)
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Final
|
||||
|
||||
from jvcprojector import JvcProjector, const
|
||||
from jvcprojector import JvcProjector, command as cmd
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -23,16 +23,12 @@ class JvcProjectorSelectDescription(SelectEntityDescription):
|
||||
command: Callable[[JvcProjector, str], Awaitable[None]]
|
||||
|
||||
|
||||
OPTIONS: Final[dict[str, dict[str, str]]] = {
|
||||
"input": {const.HDMI1: const.REMOTE_HDMI_1, const.HDMI2: const.REMOTE_HDMI_2}
|
||||
}
|
||||
|
||||
SELECTS: Final[list[JvcProjectorSelectDescription]] = [
|
||||
JvcProjectorSelectDescription(
|
||||
key="input",
|
||||
translation_key="input",
|
||||
options=list(OPTIONS["input"]),
|
||||
command=lambda device, option: device.remote(OPTIONS["input"][option]),
|
||||
options=[cmd.Input.HDMI1, cmd.Input.HDMI2],
|
||||
command=lambda device, option: device.set(cmd.Input, option),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from jvcprojector import const
|
||||
from jvcprojector import command as cmd
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -23,11 +23,11 @@ JVC_SENSORS = (
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=[
|
||||
const.STANDBY,
|
||||
const.ON,
|
||||
const.WARMING,
|
||||
const.COOLING,
|
||||
const.ERROR,
|
||||
cmd.Power.STANDBY,
|
||||
cmd.Power.ON,
|
||||
cmd.Power.WARMING,
|
||||
cmd.Power.COOLING,
|
||||
cmd.Power.ERROR,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"jvc_power": {
|
||||
"power": {
|
||||
"name": "[%key:component::binary_sensor::entity_component::power::name%]"
|
||||
}
|
||||
},
|
||||
@@ -50,7 +50,7 @@
|
||||
},
|
||||
"sensor": {
|
||||
"jvc_power_status": {
|
||||
"name": "Power status",
|
||||
"name": "Status",
|
||||
"state": {
|
||||
"cooling": "Cooling",
|
||||
"error": "[%key:common::state::error%]",
|
||||
|
||||
Generated
+1
-1
@@ -2132,7 +2132,7 @@ pyitachip2ir==0.0.7
|
||||
pyituran==0.1.5
|
||||
|
||||
# homeassistant.components.jvc_projector
|
||||
pyjvcprojector==1.1.3
|
||||
pyjvcprojector==2.0.0
|
||||
|
||||
# homeassistant.components.kaleidescape
|
||||
pykaleidescape==1.0.2
|
||||
|
||||
Generated
+1
-1
@@ -1806,7 +1806,7 @@ pyisy==3.4.1
|
||||
pyituran==0.1.5
|
||||
|
||||
# homeassistant.components.jvc_projector
|
||||
pyjvcprojector==1.1.3
|
||||
pyjvcprojector==2.0.0
|
||||
|
||||
# homeassistant.components.kaleidescape
|
||||
pykaleidescape==1.0.2
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
MOCK_HOST = "127.0.0.1"
|
||||
MOCK_PORT = 20554
|
||||
MOCK_PASSWORD = "jvcpasswd"
|
||||
MOCK_MAC = "jvcmac"
|
||||
MOCK_MODEL = "jvcmodel"
|
||||
MOCK_MAC = "E0DADC0A1234"
|
||||
MOCK_MAC_FORMATED = "e0:da:dc:0a:12:34"
|
||||
MOCK_MODEL = "B2A2"
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from jvcprojector import command as cmd
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.jvc_projector.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from . import MOCK_HOST, MOCK_MAC, MOCK_MODEL, MOCK_PASSWORD, MOCK_PORT
|
||||
|
||||
@@ -20,16 +22,31 @@ def fixture_mock_device(
|
||||
) -> Generator[MagicMock]:
|
||||
"""Return a mocked JVC Projector device."""
|
||||
target = "homeassistant.components.jvc_projector.JvcProjector"
|
||||
fixture: dict[str, str] = {
|
||||
"mac": MOCK_MAC,
|
||||
"power": "standby",
|
||||
"input": "hdmi-1",
|
||||
}
|
||||
|
||||
if hasattr(request, "param"):
|
||||
target = request.param
|
||||
target = request.param.get("target", target)
|
||||
fixture = request.param.get("get", fixture)
|
||||
|
||||
async def device_get(command) -> str:
|
||||
if command is cmd.MacAddress:
|
||||
return fixture["mac"]
|
||||
if command is cmd.Power:
|
||||
return fixture["power"]
|
||||
if command is cmd.Input:
|
||||
return fixture["input"]
|
||||
raise ValueError(f"Fixture failure; unexpected command {command}")
|
||||
|
||||
with patch(target, autospec=True) as mock:
|
||||
device = mock.return_value
|
||||
device.host = MOCK_HOST
|
||||
device.port = MOCK_PORT
|
||||
device.mac = MOCK_MAC
|
||||
device.model = MOCK_MODEL
|
||||
device.get_state.return_value = {"power": "standby", "input": "hdmi1"}
|
||||
device.get.side_effect = device_get
|
||||
yield device
|
||||
|
||||
|
||||
@@ -38,7 +55,7 @@ def fixture_mock_config_entry() -> MockConfigEntry:
|
||||
"""Return a mock config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=MOCK_MAC,
|
||||
unique_id=format_mac(MOCK_MAC),
|
||||
version=1,
|
||||
data={
|
||||
CONF_HOST: MOCK_HOST,
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.jvc_projector.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import MOCK_MAC, MOCK_MAC_FORMATED
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
ENTITY_ID = "binary_sensor.jvc_projector_power"
|
||||
@@ -20,3 +24,40 @@ async def test_entity_state(
|
||||
entity = hass.states.get(ENTITY_ID)
|
||||
assert entity
|
||||
assert entity_registry.async_get(entity.entity_id)
|
||||
|
||||
|
||||
async def test_migrate_old_unique_id(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_device: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Tests power binary sensor unique id is reformatted."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
# Entity to be migrated
|
||||
entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
f"{MOCK_MAC}_power",
|
||||
config_entry=mock_config_entry,
|
||||
suggested_object_id="jvc_projector_power",
|
||||
)
|
||||
|
||||
# Ignored entity to get to 100% coverage
|
||||
entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
f"{MOCK_MAC}_ignore",
|
||||
config_entry=mock_config_entry,
|
||||
suggested_object_id="jvc_projector_ignore",
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get(ENTITY_ID)
|
||||
assert entity
|
||||
entry = entity_registry.async_get(entity.entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == f"{MOCK_MAC_FORMATED}_power"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.jvc_projector.const import DOMAIN
|
||||
@@ -18,7 +18,7 @@ from tests.common import MockConfigEntry
|
||||
TARGET = "homeassistant.components.jvc_projector.config_flow.JvcProjector"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_user_config_flow_success(
|
||||
hass: HomeAssistant, mock_device: AsyncMock
|
||||
) -> None:
|
||||
@@ -46,12 +46,12 @@ async def test_user_config_flow_success(
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_user_config_flow_bad_connect_errors(
|
||||
hass: HomeAssistant, mock_device: AsyncMock
|
||||
) -> None:
|
||||
"""Test errors when connection error occurs."""
|
||||
mock_device.connect.side_effect = JvcProjectorConnectError
|
||||
mock_device.connect.side_effect = JvcProjectorTimeoutError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
@@ -80,7 +80,7 @@ async def test_user_config_flow_bad_connect_errors(
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_user_config_flow_device_exists_abort(
|
||||
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
|
||||
) -> None:
|
||||
@@ -94,7 +94,7 @@ async def test_user_config_flow_device_exists_abort(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_user_config_flow_bad_host_errors(
|
||||
hass: HomeAssistant, mock_device: AsyncMock
|
||||
) -> None:
|
||||
@@ -124,7 +124,7 @@ async def test_user_config_flow_bad_host_errors(
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_user_config_flow_bad_auth_errors(
|
||||
hass: HomeAssistant, mock_device: AsyncMock
|
||||
) -> None:
|
||||
@@ -158,7 +158,7 @@ async def test_user_config_flow_bad_auth_errors(
|
||||
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_reauth_config_flow_success(
|
||||
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
|
||||
) -> None:
|
||||
@@ -180,7 +180,7 @@ async def test_reauth_config_flow_success(
|
||||
assert mock_integration.data[CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_reauth_config_flow_auth_error(
|
||||
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
|
||||
) -> None:
|
||||
@@ -221,12 +221,12 @@ async def test_reauth_config_flow_auth_error(
|
||||
assert mock_integration.data[CONF_PASSWORD] == MOCK_PASSWORD
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
|
||||
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
|
||||
async def test_reauth_config_flow_connect_error(
|
||||
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test reauth config flow when connect fails."""
|
||||
mock_device.connect.side_effect = JvcProjectorConnectError
|
||||
mock_device.connect.side_effect = JvcProjectorTimeoutError
|
||||
|
||||
result = await mock_integration.start_reauth_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.jvc_projector.coordinator import (
|
||||
INTERVAL_FAST,
|
||||
@@ -13,6 +14,8 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import MOCK_MAC
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@@ -22,49 +25,51 @@ async def test_coordinator_update(
|
||||
mock_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test coordinator update runs."""
|
||||
mock_device.get_state.return_value = {"power": "standby", "input": "hdmi1"}
|
||||
async_fire_time_changed(
|
||||
hass, utcnow() + timedelta(seconds=INTERVAL_SLOW.seconds + 1)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_device.get_state.call_count == 3
|
||||
coordinator = mock_integration.runtime_data
|
||||
assert coordinator.update_interval == INTERVAL_SLOW
|
||||
|
||||
|
||||
async def test_coordinator_connect_error(
|
||||
async def test_coordinator_setup_connect_error(
|
||||
hass: HomeAssistant,
|
||||
mock_device: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test coordinator connect error."""
|
||||
mock_device.get_state.side_effect = JvcProjectorConnectError
|
||||
mock_device.get.side_effect = JvcProjectorTimeoutError
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_coordinator_auth_error(
|
||||
async def test_coordinator_setup_auth_error(
|
||||
hass: HomeAssistant,
|
||||
mock_device: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test coordinator auth error."""
|
||||
mock_device.get_state.side_effect = JvcProjectorAuthError
|
||||
mock_device.get.side_effect = JvcProjectorAuthError
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_device",
|
||||
[{"get": {"mac": MOCK_MAC, "power": "on", "input": "hdmi-1"}}],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_coordinator_device_on(
|
||||
hass: HomeAssistant,
|
||||
mock_device: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test coordinator changes update interval when device is on."""
|
||||
mock_device.get_state.return_value = {"power": "on", "input": "hdmi1"}
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
|
||||
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
|
||||
|
||||
from homeassistant.components.jvc_projector.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from . import MOCK_MAC
|
||||
|
||||
@@ -21,9 +23,10 @@ async def test_init(
|
||||
mock_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test initialization."""
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, MOCK_MAC)})
|
||||
mac = format_mac(MOCK_MAC)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, mac)})
|
||||
assert device is not None
|
||||
assert device.identifiers == {(DOMAIN, MOCK_MAC)}
|
||||
assert device.identifiers == {(DOMAIN, mac)}
|
||||
|
||||
|
||||
async def test_unload_config_entry(
|
||||
@@ -38,6 +41,23 @@ async def test_unload_config_entry(
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_device.disconnect.call_count == 1
|
||||
|
||||
|
||||
async def test_disconnect_on_hass_stop(
|
||||
hass: HomeAssistant,
|
||||
mock_device: AsyncMock,
|
||||
mock_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test device disconnects when Home Assistant stops."""
|
||||
assert mock_integration.state is ConfigEntryState.LOADED
|
||||
assert mock_device.disconnect.call_count == 0
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_device.disconnect.call_count == 1
|
||||
|
||||
|
||||
async def test_config_entry_connect_error(
|
||||
hass: HomeAssistant,
|
||||
@@ -45,7 +65,7 @@ async def test_config_entry_connect_error(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test config entry with connect error."""
|
||||
mock_device.connect.side_effect = JvcProjectorConnectError
|
||||
mock_device.connect.side_effect = JvcProjectorTimeoutError
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
@@ -43,7 +43,7 @@ async def test_commands(
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_device.power_on.call_count == 1
|
||||
assert mock_device.get.call_count == 3
|
||||
|
||||
await hass.services.async_call(
|
||||
REMOTE_DOMAIN,
|
||||
@@ -51,7 +51,7 @@ async def test_commands(
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_device.power_off.call_count == 1
|
||||
assert mock_device.get.call_count == 4
|
||||
|
||||
await hass.services.async_call(
|
||||
REMOTE_DOMAIN,
|
||||
@@ -64,11 +64,27 @@ async def test_commands(
|
||||
await hass.services.async_call(
|
||||
REMOTE_DOMAIN,
|
||||
SERVICE_SEND_COMMAND,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["hdmi_1"]},
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["hdmi1"]},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_device.remote.call_count == 2
|
||||
|
||||
await hass.services.async_call(
|
||||
REMOTE_DOMAIN,
|
||||
SERVICE_SEND_COMMAND,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["anamo"]},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_device.remote.call_count == 3
|
||||
|
||||
await hass.services.async_call(
|
||||
REMOTE_DOMAIN,
|
||||
SERVICE_SEND_COMMAND,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["picture_mode"]},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_device.remote.call_count == 4
|
||||
|
||||
|
||||
async def test_unknown_command(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from jvcprojector import const
|
||||
from jvcprojector import command as cmd
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTIONS,
|
||||
@@ -13,11 +14,18 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_OPTION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import MOCK_MAC
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
INPUT_ENTITY_ID = "select.jvc_projector_input"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_device",
|
||||
[{"get": {"mac": MOCK_MAC, "power": "on", "input": "hdmi-1"}}],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_input_select(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@@ -28,17 +36,15 @@ async def test_input_select(
|
||||
entity = hass.states.get(INPUT_ENTITY_ID)
|
||||
assert entity
|
||||
assert entity.attributes.get(ATTR_FRIENDLY_NAME) == "JVC Projector Input"
|
||||
assert entity.attributes.get(ATTR_OPTIONS) == [const.HDMI1, const.HDMI2]
|
||||
assert entity.state == const.HDMI1
|
||||
assert entity.attributes.get(ATTR_OPTIONS) == [cmd.Input.HDMI1, cmd.Input.HDMI2]
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: INPUT_ENTITY_ID,
|
||||
ATTR_OPTION: const.HDMI2,
|
||||
ATTR_OPTION: cmd.Input.HDMI2,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_device.remote.assert_called_once_with(const.REMOTE_HDMI_2)
|
||||
mock_device.set.assert_called_once_with(cmd.Input, cmd.Input.HDMI2)
|
||||
|
||||
@@ -7,7 +7,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
POWER_ID = "sensor.jvc_projector_power_status"
|
||||
POWER_ID = "sensor.jvc_projector_status"
|
||||
|
||||
|
||||
async def test_entity_state(
|
||||
|
||||
Reference in New Issue
Block a user