Add LED mode select entities to opentherm_gw (#125702)

Add select entities for LED mode to opentherm_gw
pull/125705/head
mvn23 2024-09-11 00:12:09 +02:00 committed by GitHub
parent 1be455e0e0
commit 2611f72f5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 264 additions and 19 deletions

View File

@ -5,7 +5,16 @@ from dataclasses import dataclass
from enum import IntEnum, StrEnum
from functools import partial
from pyotgw.vars import OTGW_GPIO_A, OTGW_GPIO_B
from pyotgw.vars import (
OTGW_GPIO_A,
OTGW_GPIO_B,
OTGW_LED_A,
OTGW_LED_B,
OTGW_LED_C,
OTGW_LED_D,
OTGW_LED_E,
OTGW_LED_F,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
@ -37,6 +46,23 @@ class OpenThermSelectGPIOMode(StrEnum):
DHW_BLOCK = "dhw_block"
class OpenThermSelectLEDMode(StrEnum):
"""OpenThermGateway LED modes."""
RX_ANY = "receive_any"
TX_ANY = "transmit_any"
THERMOSTAT_TRAFFIC = "thermostat_traffic"
BOILER_TRAFFIC = "boiler_traffic"
SETPOINT_OVERRIDE_ACTIVE = "setpoint_override_active"
FLAME_ON = "flame_on"
CENTRAL_HEATING_ON = "central_heating_on"
HOT_WATER_ON = "hot_water_on"
COMFORT_MODE_ON = "comfort_mode_on"
TX_ERROR_DETECTED = "transmit_error_detected"
BOILER_MAINTENANCE_REQUIRED = "boiler_maintenance_required"
RAISED_POWER_MODE_ACTIVE = "raised_power_mode_active"
class PyotgwGPIOMode(IntEnum):
"""pyotgw GPIO modes."""
@ -51,6 +77,34 @@ class PyotgwGPIOMode(IntEnum):
DHW_BLOCK = 8
class PyotgwLEDMode(StrEnum):
"""pyotgw LED modes."""
RX_ANY = "R"
TX_ANY = "X"
THERMOSTAT_TRAFFIC = "T"
BOILER_TRAFFIC = "B"
SETPOINT_OVERRIDE_ACTIVE = "O"
FLAME_ON = "F"
CENTRAL_HEATING_ON = "H"
HOT_WATER_ON = "W"
COMFORT_MODE_ON = "C"
TX_ERROR_DETECTED = "E"
BOILER_MAINTENANCE_REQUIRED = "M"
RAISED_POWER_MODE_ACTIVE = "P"
def pyotgw_led_mode_to_ha_led_mode(
pyotgw_led_mode: PyotgwLEDMode,
) -> OpenThermSelectLEDMode | None:
"""Convert pyotgw LED mode to Home Assistant LED mode."""
return (
OpenThermSelectLEDMode[PyotgwLEDMode(pyotgw_led_mode).name]
if pyotgw_led_mode in PyotgwLEDMode
else None
)
async def set_gpio_mode(
gpio_id: str, gw_hub: OpenThermGatewayHub, mode: str
) -> OpenThermSelectGPIOMode | None:
@ -65,6 +119,20 @@ async def set_gpio_mode(
)
async def set_led_mode(
led_id: str, gw_hub: OpenThermGatewayHub, mode: str
) -> OpenThermSelectLEDMode | None:
"""Set gpio mode, return selected option or None."""
value = await gw_hub.gateway.set_led_mode(
led_id, PyotgwLEDMode[OpenThermSelectLEDMode(mode).name]
)
return (
OpenThermSelectLEDMode[PyotgwLEDMode(value).name]
if value in PyotgwLEDMode
else None
)
@dataclass(frozen=True, kw_only=True)
class OpenThermSelectEntityDescription(
OpenThermEntityDescription, SelectEntityDescription
@ -106,6 +174,60 @@ SELECT_DESCRIPTIONS: tuple[OpenThermSelectEntityDescription, ...] = (
else None
),
),
OpenThermSelectEntityDescription(
key=OTGW_LED_A,
translation_key="led_mode_n",
translation_placeholders={"led_id": "A"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "A"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
OpenThermSelectEntityDescription(
key=OTGW_LED_B,
translation_key="led_mode_n",
translation_placeholders={"led_id": "B"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "B"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
OpenThermSelectEntityDescription(
key=OTGW_LED_C,
translation_key="led_mode_n",
translation_placeholders={"led_id": "C"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "C"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
OpenThermSelectEntityDescription(
key=OTGW_LED_D,
translation_key="led_mode_n",
translation_placeholders={"led_id": "D"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "D"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
OpenThermSelectEntityDescription(
key=OTGW_LED_E,
translation_key="led_mode_n",
translation_placeholders={"led_id": "E"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "E"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
OpenThermSelectEntityDescription(
key=OTGW_LED_F,
translation_key="led_mode_n",
translation_placeholders={"led_id": "F"},
device_description=GATEWAY_DEVICE_DESCRIPTION,
options=list(OpenThermSelectLEDMode),
select_action=partial(set_led_mode, "F"),
convert_pyotgw_state_to_ha_state=pyotgw_led_mode_to_ha_led_mode,
),
)

View File

@ -172,6 +172,23 @@
"ds1820": "DS1820",
"dhw_block": "Block hot water"
}
},
"led_mode_n": {
"name": "LED {led_id} mode",
"state": {
"receive_any": "Receiving on any interface",
"transmit_any": "Transmitting on any interface",
"thermostat_traffic": "Traffic on the thermostat interface",
"boiler_traffic": "Traffic on the boiler interface",
"setpoint_override_active": "Setpoint override is active",
"flame_on": "Boiler flame is on",
"central_heating_on": "Central heating is on",
"hot_water_on": "Hot water is on",
"comfort_mode_on": "Comfort mode is on",
"transmit_error_detected": "Transmit error detected",
"boiler_maintenance_required": "Boiler maintenance required",
"raised_power_mode_active": "Raised power mode active"
}
}
},
"sensor": {

View File

@ -1,8 +1,18 @@
"""Test opentherm_gw select entities."""
from typing import Any
from unittest.mock import AsyncMock, MagicMock
from pyotgw.vars import OTGW_GPIO_A, OTGW_GPIO_B
from pyotgw.vars import (
OTGW_GPIO_A,
OTGW_GPIO_B,
OTGW_LED_A,
OTGW_LED_B,
OTGW_LED_C,
OTGW_LED_D,
OTGW_LED_E,
OTGW_LED_F,
)
import pytest
from homeassistant.components.opentherm_gw import DOMAIN as OPENTHERM_DOMAIN
@ -13,7 +23,9 @@ from homeassistant.components.opentherm_gw.const import (
)
from homeassistant.components.opentherm_gw.select import (
OpenThermSelectGPIOMode,
OpenThermSelectLEDMode,
PyotgwGPIOMode,
PyotgwLEDMode,
)
from homeassistant.components.select import (
ATTR_OPTION,
@ -29,23 +41,90 @@ from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("entity_key", "gpio_id"),
(
"entity_key",
"target_func_name",
"target_param_1",
"target_param_2",
"resulting_state",
),
[
(OTGW_GPIO_A, "A"),
(OTGW_GPIO_B, "B"),
(
OTGW_GPIO_A,
"set_gpio_mode",
"A",
PyotgwGPIOMode.VCC,
OpenThermSelectGPIOMode.VCC,
),
(
OTGW_GPIO_B,
"set_gpio_mode",
"B",
PyotgwGPIOMode.HOME,
OpenThermSelectGPIOMode.HOME,
),
(
OTGW_LED_A,
"set_led_mode",
"A",
PyotgwLEDMode.TX_ANY,
OpenThermSelectLEDMode.TX_ANY,
),
(
OTGW_LED_B,
"set_led_mode",
"B",
PyotgwLEDMode.RX_ANY,
OpenThermSelectLEDMode.RX_ANY,
),
(
OTGW_LED_C,
"set_led_mode",
"C",
PyotgwLEDMode.BOILER_TRAFFIC,
OpenThermSelectLEDMode.BOILER_TRAFFIC,
),
(
OTGW_LED_D,
"set_led_mode",
"D",
PyotgwLEDMode.THERMOSTAT_TRAFFIC,
OpenThermSelectLEDMode.THERMOSTAT_TRAFFIC,
),
(
OTGW_LED_E,
"set_led_mode",
"E",
PyotgwLEDMode.FLAME_ON,
OpenThermSelectLEDMode.FLAME_ON,
),
(
OTGW_LED_F,
"set_led_mode",
"F",
PyotgwLEDMode.BOILER_MAINTENANCE_REQUIRED,
OpenThermSelectLEDMode.BOILER_MAINTENANCE_REQUIRED,
),
],
)
async def test_gpio_mode_select(
async def test_select_change_value(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_pyotgw: MagicMock,
entity_key: str,
gpio_id: str,
target_func_name: str,
target_param_1: str,
target_param_2: str | int,
resulting_state: str,
) -> None:
"""Test GPIO mode selector."""
mock_pyotgw.return_value.set_gpio_mode = AsyncMock(return_value=PyotgwGPIOMode.VCC)
setattr(
mock_pyotgw.return_value,
target_func_name,
AsyncMock(return_value=target_param_2),
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
@ -63,29 +142,56 @@ async def test_gpio_mode_select(
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{ATTR_ENTITY_ID: select_entity_id, ATTR_OPTION: OpenThermSelectGPIOMode.VCC},
{ATTR_ENTITY_ID: select_entity_id, ATTR_OPTION: resulting_state},
blocking=True,
)
assert hass.states.get(select_entity_id).state == OpenThermSelectGPIOMode.VCC
assert hass.states.get(select_entity_id).state == resulting_state
mock_pyotgw.return_value.set_gpio_mode.assert_awaited_once_with(
gpio_id, PyotgwGPIOMode.VCC.value
)
target = getattr(mock_pyotgw.return_value, target_func_name)
target.assert_awaited_once_with(target_param_1, target_param_2)
@pytest.mark.parametrize(
("entity_key"),
("entity_key", "test_value", "resulting_state"),
[
(OTGW_GPIO_A),
(OTGW_GPIO_B),
(OTGW_GPIO_A, PyotgwGPIOMode.AWAY, OpenThermSelectGPIOMode.AWAY),
(OTGW_GPIO_B, PyotgwGPIOMode.LED_F, OpenThermSelectGPIOMode.LED_F),
(
OTGW_LED_A,
PyotgwLEDMode.SETPOINT_OVERRIDE_ACTIVE,
OpenThermSelectLEDMode.SETPOINT_OVERRIDE_ACTIVE,
),
(
OTGW_LED_B,
PyotgwLEDMode.CENTRAL_HEATING_ON,
OpenThermSelectLEDMode.CENTRAL_HEATING_ON,
),
(OTGW_LED_C, PyotgwLEDMode.HOT_WATER_ON, OpenThermSelectLEDMode.HOT_WATER_ON),
(
OTGW_LED_D,
PyotgwLEDMode.COMFORT_MODE_ON,
OpenThermSelectLEDMode.COMFORT_MODE_ON,
),
(
OTGW_LED_E,
PyotgwLEDMode.TX_ERROR_DETECTED,
OpenThermSelectLEDMode.TX_ERROR_DETECTED,
),
(
OTGW_LED_F,
PyotgwLEDMode.RAISED_POWER_MODE_ACTIVE,
OpenThermSelectLEDMode.RAISED_POWER_MODE_ACTIVE,
),
],
)
async def test_gpio_mode_state_update(
async def test_select_state_update(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_pyotgw: MagicMock,
entity_key: str,
test_value: Any,
resulting_state: str,
) -> None:
"""Test GPIO mode selector."""
@ -111,10 +217,10 @@ async def test_gpio_mode_state_update(
gw_hub.update_signal,
{
OpenThermDeviceIdentifier.BOILER: {},
OpenThermDeviceIdentifier.GATEWAY: {entity_key: 4},
OpenThermDeviceIdentifier.GATEWAY: {entity_key: test_value},
OpenThermDeviceIdentifier.THERMOSTAT: {},
},
)
await hass.async_block_till_done()
assert hass.states.get(select_entity_id).state == OpenThermSelectGPIOMode.LED_F
assert hass.states.get(select_entity_id).state == resulting_state