Add switch to enable/disable boost in IronOS integration (#147831)

This commit is contained in:
Manu 2025-07-01 20:53:13 +02:00 committed by GitHub
parent d6fb860889
commit 926e9261ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 152 additions and 4 deletions

View File

@ -209,6 +209,12 @@
"state": {
"off": "mdi:card-bulleted-off-outline"
}
},
"boost": {
"default": "mdi:thermometer-high",
"state": {
"off": "mdi:thermometer-off"
}
}
}
}

View File

@ -464,6 +464,16 @@ class IronOSTemperatureNumberEntity(IronOSNumberEntity):
else super().native_max_value
)
@property
def available(self) -> bool:
"""Return True if entity is available."""
if (
self.entity_description.key is PinecilNumber.BOOST_TEMP
and self.native_value == 0
):
return False
return super().available
class IronOSSetpointNumberEntity(IronOSTemperatureNumberEntity):
"""IronOS setpoint temperature entity."""

View File

@ -278,6 +278,9 @@
},
"calibrate_cjc": {
"name": "Calibrate CJC"
},
"boost": {
"name": "Boost"
}
}
},

View File

@ -7,7 +7,7 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from pynecil import CharSetting, SettingsDataResponse
from pynecil import CharSetting, SettingsDataResponse, TempUnit
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
@ -15,6 +15,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import IronOSConfigEntry
from .const import MIN_BOOST_TEMP, MIN_BOOST_TEMP_F
from .coordinator import IronOSCoordinators
from .entity import IronOSBaseEntity
@ -39,6 +40,7 @@ class IronOSSwitch(StrEnum):
INVERT_BUTTONS = "invert_buttons"
DISPLAY_INVERT = "display_invert"
CALIBRATE_CJC = "calibrate_cjc"
BOOST = "boost"
SWITCH_DESCRIPTIONS: tuple[IronOSSwitchEntityDescription, ...] = (
@ -94,6 +96,13 @@ SWITCH_DESCRIPTIONS: tuple[IronOSSwitchEntityDescription, ...] = (
entity_registry_enabled_default=False,
entity_category=EntityCategory.CONFIG,
),
IronOSSwitchEntityDescription(
key=IronOSSwitch.BOOST,
translation_key=IronOSSwitch.BOOST,
characteristic=CharSetting.BOOST_TEMP,
is_on_fn=lambda x: bool(x.get("boost_temp")),
entity_category=EntityCategory.CONFIG,
),
)
@ -136,7 +145,15 @@ class IronOSSwitchEntity(IronOSBaseEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self.settings.write(self.entity_description.characteristic, True)
if self.entity_description.key is IronOSSwitch.BOOST:
await self.settings.write(
self.entity_description.characteristic,
MIN_BOOST_TEMP_F
if self.settings.data.get("temp_unit") is TempUnit.FAHRENHEIT
else MIN_BOOST_TEMP,
)
else:
await self.settings.write(self.entity_description.characteristic, True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity on."""

View File

@ -47,6 +47,54 @@
'state': 'on',
})
# ---
# name: test_switch_platform[switch.pinecil_boost-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.pinecil_boost',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Boost',
'platform': 'iron_os',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <IronOSSwitch.BOOST: 'boost'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_boost',
'unit_of_measurement': None,
})
# ---
# name: test_switch_platform[switch.pinecil_boost-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Pinecil Boost',
}),
'context': <ANY>,
'entity_id': 'switch.pinecil_boost',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_platform[switch.pinecil_calibrate_cjc-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@ -20,7 +20,7 @@ from homeassistant.components.number import (
SERVICE_SET_VALUE,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
@ -248,3 +248,26 @@ async def test_set_value_exception(
target={ATTR_ENTITY_ID: "number.pinecil_setpoint_temperature"},
blocking=True,
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
async def test_boost_temp_unavailable(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pynecil: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test boost temp input is unavailable when off."""
mock_pynecil.get_settings.return_value["boost_temp"] = 0
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
freezer.tick(timedelta(seconds=3))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert (state := hass.states.get("number.pinecil_boost_temperature"))
assert state.state == STATE_UNAVAILABLE

View File

@ -5,7 +5,7 @@ from datetime import timedelta
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pynecil import CharSetting, CommunicationError
from pynecil import CharSetting, CommunicationError, TempUnit
import pytest
from syrupy.assertion import SnapshotAssertion
@ -110,6 +110,47 @@ async def test_turn_on_off_toggle(
mock_pynecil.write.assert_called_once_with(target, value)
@pytest.mark.parametrize(
("service", "value", "temp_unit"),
[
(SERVICE_TOGGLE, False, TempUnit.CELSIUS),
(SERVICE_TURN_OFF, False, TempUnit.CELSIUS),
(SERVICE_TURN_ON, 250, TempUnit.CELSIUS),
(SERVICE_TURN_ON, 480, TempUnit.FAHRENHEIT),
],
)
@pytest.mark.usefixtures("ble_device")
async def test_turn_on_off_toggle_boost(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pynecil: AsyncMock,
freezer: FrozenDateTimeFactory,
service: str,
value: bool,
temp_unit: TempUnit,
) -> None:
"""Test the IronOS switch turn on/off, toggle services."""
mock_pynecil.get_settings.return_value["temp_unit"] = temp_unit
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
freezer.tick(timedelta(seconds=3))
async_fire_time_changed(hass)
await hass.async_block_till_done()
await hass.services.async_call(
SWITCH_DOMAIN,
service,
service_data={ATTR_ENTITY_ID: "switch.pinecil_boost"},
blocking=True,
)
assert len(mock_pynecil.write.mock_calls) == 1
mock_pynecil.write.assert_called_once_with(CharSetting.BOOST_TEMP, value)
@pytest.mark.parametrize(
"service",
[SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON],