mirror of
https://github.com/home-assistant/core.git
synced 2026-10-07 06:50:41 -04:00
bosch_shc: add Smart Plug energy-saving power threshold number (#183699)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
b3ae7d658f
commit
d41802ec50
@@ -4,7 +4,12 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from boschshcpy import SHCMicromoduleRelay, SHCShutterContact2
|
||||
from boschshcpy import (
|
||||
SHCMicromoduleRelay,
|
||||
SHCShutterContact2,
|
||||
SHCSmartPlug,
|
||||
SHCSmartPlugCompact,
|
||||
)
|
||||
from boschshcpy.device import SHCDevice
|
||||
|
||||
from homeassistant.components.number import (
|
||||
@@ -13,7 +18,7 @@ from homeassistant.components.number import (
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.const import EntityCategory, UnitOfTime
|
||||
from homeassistant.const import EntityCategory, UnitOfPower, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -50,6 +55,12 @@ async def _impulse_length_set_value_fn(
|
||||
await device.async_set_impulse_length(round(value * 10))
|
||||
|
||||
|
||||
async def _power_threshold_set_value_fn(
|
||||
device: SHCSmartPlug | SHCSmartPlugCompact, value: float
|
||||
) -> None:
|
||||
await device.async_set_power_threshold(value)
|
||||
|
||||
|
||||
IMPULSE_LENGTH = "impulse_length"
|
||||
|
||||
NUMBER_TYPES: dict[str, SHCNumberEntityDescription] = {
|
||||
@@ -81,6 +92,19 @@ NUMBER_TYPES: dict[str, SHCNumberEntityDescription] = {
|
||||
round(value)
|
||||
),
|
||||
),
|
||||
"power_threshold": SHCNumberEntityDescription[SHCSmartPlug | SHCSmartPlugCompact](
|
||||
key="power_threshold",
|
||||
translation_key="energy_saving_power_threshold",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
native_min_value=0.0,
|
||||
native_max_value=3680.0,
|
||||
native_step=1.0,
|
||||
mode=NumberMode.BOX,
|
||||
value_fn=lambda device: device.power_threshold,
|
||||
set_value_fn=_power_threshold_set_value_fn,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +151,29 @@ async def async_setup_entry(
|
||||
for device in session.device_helper.shutter_contacts2
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
SHCNumber(
|
||||
hass=hass,
|
||||
device=device,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
description=NUMBER_TYPES["power_threshold"],
|
||||
)
|
||||
for device in session.device_helper.smart_plugs
|
||||
if device.supports_energy_saving_mode and device.power_threshold is not None
|
||||
)
|
||||
entities.extend(
|
||||
SHCNumber(
|
||||
hass=hass,
|
||||
device=device,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
description=NUMBER_TYPES["power_threshold"],
|
||||
)
|
||||
for device in session.device_helper.smart_plugs_compact
|
||||
if device.supports_energy_saving_mode and device.power_threshold is not None
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
"bypass_timeout": {
|
||||
"name": "Break function timeout"
|
||||
},
|
||||
"energy_saving_power_threshold": {
|
||||
"name": "Energy-saving power threshold"
|
||||
},
|
||||
"impulse_length": {
|
||||
"name": "Pulse length"
|
||||
}
|
||||
|
||||
@@ -261,6 +261,7 @@ def smart_plug_device(
|
||||
routing: RoutingService.State = RoutingService.State.DISABLED,
|
||||
supports_energy_saving_mode: bool = False,
|
||||
energy_saving_mode_enabled: bool = False,
|
||||
power_threshold: float | None = None,
|
||||
) -> SHCSmartPlug:
|
||||
"""Build a minimal device double for the smart_plugs bucket."""
|
||||
device = create_autospec(SHCSmartPlug, instance=True, spec_set=True)
|
||||
@@ -277,6 +278,7 @@ def smart_plug_device(
|
||||
device.routing = routing
|
||||
device.supports_energy_saving_mode = supports_energy_saving_mode
|
||||
device.energy_saving_mode_enabled = energy_saving_mode_enabled
|
||||
device.power_threshold = power_threshold
|
||||
return device
|
||||
|
||||
|
||||
@@ -285,6 +287,7 @@ def smart_plug_compact_device(
|
||||
name: str = "Smart Plug Compact",
|
||||
supports_energy_saving_mode: bool = False,
|
||||
energy_saving_mode_enabled: bool = False,
|
||||
power_threshold: float | None = None,
|
||||
) -> SHCSmartPlugCompact:
|
||||
"""Build a minimal device double for the smart_plugs_compact bucket."""
|
||||
device = create_autospec(SHCSmartPlugCompact, instance=True, spec_set=True)
|
||||
@@ -300,6 +303,7 @@ def smart_plug_compact_device(
|
||||
device.switchstate = PowerSwitchService.State.OFF
|
||||
device.supports_energy_saving_mode = supports_energy_saving_mode
|
||||
device.energy_saving_mode_enabled = energy_saving_mode_enabled
|
||||
device.power_threshold = power_threshold
|
||||
return device
|
||||
|
||||
|
||||
|
||||
@@ -16,12 +16,18 @@ from .conftest import (
|
||||
micromodule_relay_device,
|
||||
setup_integration,
|
||||
shutter_contact2_device,
|
||||
smart_plug_compact_device,
|
||||
smart_plug_device,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
IMPULSE_LENGTH_ENTITY_ID = "number.relay_pulse_length"
|
||||
BYPASS_TIMEOUT_ENTITY_ID = "number.shutter_contact_break_function_timeout"
|
||||
POWER_THRESHOLD_ENTITY_ID = "number.smart_plug_energy_saving_power_threshold"
|
||||
POWER_THRESHOLD_COMPACT_ENTITY_ID = (
|
||||
"number.smart_plug_compact_energy_saving_power_threshold"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -146,3 +152,91 @@ async def test_shutter_contact2_bypass_timeout_set_value(
|
||||
blocking=True,
|
||||
)
|
||||
device.async_set_bypass_timeout.assert_awaited_once_with(11)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[
|
||||
{
|
||||
"smart_plugs": [
|
||||
smart_plug_device(supports_energy_saving_mode=True, power_threshold=5.0)
|
||||
],
|
||||
"smart_plugs_compact": [
|
||||
smart_plug_compact_device(
|
||||
supports_energy_saving_mode=True, power_threshold=7.0
|
||||
)
|
||||
],
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_smart_plug_power_threshold_value(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""The power threshold is reported for both smart plug variants."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(POWER_THRESHOLD_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "5.0"
|
||||
state = hass.states.get(POWER_THRESHOLD_COMPACT_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "7.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[
|
||||
{
|
||||
"smart_plugs": [
|
||||
smart_plug_device(supports_energy_saving_mode=True, power_threshold=5.0)
|
||||
]
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_smart_plug_power_threshold_set_value(
|
||||
hass: HomeAssistant,
|
||||
mock_session: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Setting a value writes the power threshold to the device."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device = mock_session.device_helper.smart_plugs[0]
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: POWER_THRESHOLD_ENTITY_ID, ATTR_VALUE: 20},
|
||||
blocking=True,
|
||||
)
|
||||
device.async_set_power_threshold.assert_awaited_once_with(20)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[
|
||||
{
|
||||
"smart_plugs": [smart_plug_device(power_threshold=5.0)],
|
||||
"smart_plugs_compact": [
|
||||
smart_plug_compact_device(
|
||||
supports_energy_saving_mode=True, power_threshold=None
|
||||
)
|
||||
],
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_smart_plug_no_power_threshold_support(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""No entity is created without energy-saving or a power threshold."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get(POWER_THRESHOLD_ENTITY_ID) is None
|
||||
assert hass.states.get(POWER_THRESHOLD_COMPACT_ENTITY_ID) is None
|
||||
|
||||
Reference in New Issue
Block a user