mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Add additional number entities to IronOS (#131943)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
11a2a62144
commit
ea7f1b2a4e
@ -19,15 +19,22 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import IronOSFirmwareUpdateCoordinator, IronOSLiveDataCoordinator
|
from .coordinator import (
|
||||||
|
IronOSCoordinators,
|
||||||
|
IronOSFirmwareUpdateCoordinator,
|
||||||
|
IronOSLiveDataCoordinator,
|
||||||
|
IronOSSettingsCoordinator,
|
||||||
|
)
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR, Platform.UPDATE]
|
PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR, Platform.UPDATE]
|
||||||
|
|
||||||
|
|
||||||
type IronOSConfigEntry = ConfigEntry[IronOSLiveDataCoordinator]
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
type IronOSConfigEntry = ConfigEntry[IronOSCoordinators]
|
||||||
IRON_OS_KEY: HassKey[IronOSFirmwareUpdateCoordinator] = HassKey(DOMAIN)
|
IRON_OS_KEY: HassKey[IronOSFirmwareUpdateCoordinator] = HassKey(DOMAIN)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -59,10 +66,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: IronOSConfigEntry) -> bo
|
|||||||
|
|
||||||
device = Pynecil(ble_device)
|
device = Pynecil(ble_device)
|
||||||
|
|
||||||
coordinator = IronOSLiveDataCoordinator(hass, device)
|
live_data = IronOSLiveDataCoordinator(hass, device)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await live_data.async_config_entry_first_refresh()
|
||||||
|
|
||||||
entry.runtime_data = coordinator
|
settings = IronOSSettingsCoordinator(hass, device)
|
||||||
|
await settings.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
entry.runtime_data = IronOSCoordinators(
|
||||||
|
live_data=live_data,
|
||||||
|
settings=settings,
|
||||||
|
)
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -2,15 +2,23 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from aiogithubapi import GitHubAPI, GitHubException, GitHubReleaseModel
|
from aiogithubapi import GitHubAPI, GitHubException, GitHubReleaseModel
|
||||||
from pynecil import CommunicationError, DeviceInfoResponse, LiveDataResponse, Pynecil
|
from pynecil import (
|
||||||
|
CommunicationError,
|
||||||
|
DeviceInfoResponse,
|
||||||
|
LiveDataResponse,
|
||||||
|
Pynecil,
|
||||||
|
SettingsDataResponse,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -19,24 +27,58 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=5)
|
SCAN_INTERVAL = timedelta(seconds=5)
|
||||||
SCAN_INTERVAL_GITHUB = timedelta(hours=3)
|
SCAN_INTERVAL_GITHUB = timedelta(hours=3)
|
||||||
|
SCAN_INTERVAL_SETTINGS = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
class IronOSLiveDataCoordinator(DataUpdateCoordinator[LiveDataResponse]):
|
@dataclass
|
||||||
"""IronOS live data coordinator."""
|
class IronOSCoordinators:
|
||||||
|
"""IronOS data class holding coordinators."""
|
||||||
|
|
||||||
|
live_data: IronOSLiveDataCoordinator
|
||||||
|
settings: IronOSSettingsCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
class IronOSBaseCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
||||||
|
"""IronOS base coordinator."""
|
||||||
|
|
||||||
device_info: DeviceInfoResponse
|
device_info: DeviceInfoResponse
|
||||||
config_entry: ConfigEntry
|
config_entry: ConfigEntry
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, device: Pynecil) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: Pynecil,
|
||||||
|
update_interval: timedelta,
|
||||||
|
) -> None:
|
||||||
"""Initialize IronOS coordinator."""
|
"""Initialize IronOS coordinator."""
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=SCAN_INTERVAL,
|
update_interval=update_interval,
|
||||||
|
request_refresh_debouncer=Debouncer(
|
||||||
|
hass, _LOGGER, cooldown=3, immediate=False
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.device = device
|
self.device = device
|
||||||
|
|
||||||
|
async def _async_setup(self) -> None:
|
||||||
|
"""Set up the coordinator."""
|
||||||
|
try:
|
||||||
|
self.device_info = await self.device.get_device_info()
|
||||||
|
|
||||||
|
except CommunicationError as e:
|
||||||
|
raise UpdateFailed("Cannot connect to device") from e
|
||||||
|
|
||||||
|
|
||||||
|
class IronOSLiveDataCoordinator(IronOSBaseCoordinator):
|
||||||
|
"""IronOS coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, device: Pynecil) -> None:
|
||||||
|
"""Initialize IronOS coordinator."""
|
||||||
|
super().__init__(hass, device=device, update_interval=SCAN_INTERVAL)
|
||||||
|
|
||||||
async def _async_update_data(self) -> LiveDataResponse:
|
async def _async_update_data(self) -> LiveDataResponse:
|
||||||
"""Fetch data from Device."""
|
"""Fetch data from Device."""
|
||||||
|
|
||||||
@ -80,3 +122,24 @@ class IronOSFirmwareUpdateCoordinator(DataUpdateCoordinator[GitHubReleaseModel])
|
|||||||
assert release.data
|
assert release.data
|
||||||
|
|
||||||
return release.data
|
return release.data
|
||||||
|
|
||||||
|
|
||||||
|
class IronOSSettingsCoordinator(IronOSBaseCoordinator):
|
||||||
|
"""IronOS coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, device: Pynecil) -> None:
|
||||||
|
"""Initialize IronOS coordinator."""
|
||||||
|
super().__init__(hass, device=device, update_interval=SCAN_INTERVAL_SETTINGS)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> SettingsDataResponse:
|
||||||
|
"""Fetch data from Device."""
|
||||||
|
|
||||||
|
characteristics = set(self.async_contexts())
|
||||||
|
|
||||||
|
if self.device.is_connected and characteristics:
|
||||||
|
try:
|
||||||
|
return await self.device.get_settings(list(characteristics))
|
||||||
|
except CommunicationError as e:
|
||||||
|
_LOGGER.debug("Failed to fetch settings", exc_info=e)
|
||||||
|
|
||||||
|
return self.data or SettingsDataResponse()
|
||||||
|
@ -2,28 +2,29 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import MANUFACTURER, MODEL
|
from .const import MANUFACTURER, MODEL
|
||||||
from .coordinator import IronOSLiveDataCoordinator
|
from .coordinator import IronOSBaseCoordinator
|
||||||
|
|
||||||
|
|
||||||
class IronOSBaseEntity(CoordinatorEntity[IronOSLiveDataCoordinator]):
|
class IronOSBaseEntity(CoordinatorEntity[IronOSBaseCoordinator]):
|
||||||
"""Base IronOS entity."""
|
"""Base IronOS entity."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: IronOSLiveDataCoordinator,
|
coordinator: IronOSBaseCoordinator,
|
||||||
entity_description: EntityDescription,
|
entity_description: EntityDescription,
|
||||||
|
context: Any | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, context=context)
|
||||||
|
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
|
@ -3,6 +3,63 @@
|
|||||||
"number": {
|
"number": {
|
||||||
"setpoint_temperature": {
|
"setpoint_temperature": {
|
||||||
"default": "mdi:thermometer"
|
"default": "mdi:thermometer"
|
||||||
|
},
|
||||||
|
"sleep_temperature": {
|
||||||
|
"default": "mdi:thermometer-low"
|
||||||
|
},
|
||||||
|
"sleep_timeout": {
|
||||||
|
"default": "mdi:timer-sand"
|
||||||
|
},
|
||||||
|
"qc_max_voltage": {
|
||||||
|
"default": "mdi:flash-alert-outline"
|
||||||
|
},
|
||||||
|
"pd_timeout": {
|
||||||
|
"default": "mdi:timer-alert-outline"
|
||||||
|
},
|
||||||
|
"boost_temp": {
|
||||||
|
"default": "mdi:thermometer-high"
|
||||||
|
},
|
||||||
|
"shutdown_timeout": {
|
||||||
|
"default": "mdi:thermometer-off"
|
||||||
|
},
|
||||||
|
"display_brightness": {
|
||||||
|
"default": "mdi:brightness-6"
|
||||||
|
},
|
||||||
|
"voltage_div": {
|
||||||
|
"default": "mdi:call-split"
|
||||||
|
},
|
||||||
|
"temp_increment_short": {
|
||||||
|
"default": "mdi:gesture-tap-button"
|
||||||
|
},
|
||||||
|
"temp_increment_long": {
|
||||||
|
"default": "mdi:gesture-tap-button"
|
||||||
|
},
|
||||||
|
"accel_sensitivity": {
|
||||||
|
"default": "mdi:motion"
|
||||||
|
},
|
||||||
|
"calibration_offset": {
|
||||||
|
"default": "mdi:contrast"
|
||||||
|
},
|
||||||
|
"hall_sensitivity": {
|
||||||
|
"default": "mdi:leak"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_delay": {
|
||||||
|
"default": "mdi:clock-end"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_duration": {
|
||||||
|
"default": "mdi:clock-start"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_power": {
|
||||||
|
"default": "mdi:waves-arrow-up"
|
||||||
|
},
|
||||||
|
"min_voltage_per_cell": {
|
||||||
|
"default": "mdi:fuel-cell"
|
||||||
|
},
|
||||||
|
"min_dc_voltage_cells": {
|
||||||
|
"default": "mdi:battery-arrow-down"
|
||||||
|
},
|
||||||
|
"power_limit": {
|
||||||
|
"default": "mdi:flash-alert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
@ -6,21 +6,34 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
|
||||||
from pynecil import CharSetting, CommunicationError, LiveDataResponse
|
from pynecil import (
|
||||||
|
CharSetting,
|
||||||
|
CommunicationError,
|
||||||
|
LiveDataResponse,
|
||||||
|
SettingsDataResponse,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.components.number import (
|
from homeassistant.components.number import (
|
||||||
|
DEFAULT_MAX_VALUE,
|
||||||
NumberDeviceClass,
|
NumberDeviceClass,
|
||||||
NumberEntity,
|
NumberEntity,
|
||||||
NumberEntityDescription,
|
NumberEntityDescription,
|
||||||
NumberMode,
|
NumberMode,
|
||||||
)
|
)
|
||||||
from homeassistant.const import UnitOfTemperature
|
from homeassistant.const import (
|
||||||
|
EntityCategory,
|
||||||
|
UnitOfElectricPotential,
|
||||||
|
UnitOfPower,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfTime,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import IronOSConfigEntry
|
from . import IronOSConfigEntry
|
||||||
from .const import DOMAIN, MAX_TEMP, MIN_TEMP
|
from .const import DOMAIN, MAX_TEMP, MIN_TEMP
|
||||||
|
from .coordinator import IronOSCoordinators
|
||||||
from .entity import IronOSBaseEntity
|
from .entity import IronOSBaseEntity
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -30,15 +43,39 @@ PARALLEL_UPDATES = 0
|
|||||||
class IronOSNumberEntityDescription(NumberEntityDescription):
|
class IronOSNumberEntityDescription(NumberEntityDescription):
|
||||||
"""Describes IronOS number entity."""
|
"""Describes IronOS number entity."""
|
||||||
|
|
||||||
value_fn: Callable[[LiveDataResponse], float | int | None]
|
value_fn: Callable[[LiveDataResponse, SettingsDataResponse], float | int | None]
|
||||||
max_value_fn: Callable[[LiveDataResponse], float | int]
|
max_value_fn: Callable[[LiveDataResponse], float | int] | None = None
|
||||||
set_key: CharSetting
|
characteristic: CharSetting
|
||||||
|
raw_value_fn: Callable[[float], float | int] | None = None
|
||||||
|
|
||||||
|
|
||||||
class PinecilNumber(StrEnum):
|
class PinecilNumber(StrEnum):
|
||||||
"""Number controls for Pinecil device."""
|
"""Number controls for Pinecil device."""
|
||||||
|
|
||||||
SETPOINT_TEMP = "setpoint_temperature"
|
SETPOINT_TEMP = "setpoint_temperature"
|
||||||
|
SLEEP_TEMP = "sleep_temperature"
|
||||||
|
SLEEP_TIMEOUT = "sleep_timeout"
|
||||||
|
QC_MAX_VOLTAGE = "qc_max_voltage"
|
||||||
|
PD_TIMEOUT = "pd_timeout"
|
||||||
|
BOOST_TEMP = "boost_temp"
|
||||||
|
SHUTDOWN_TIMEOUT = "shutdown_timeout"
|
||||||
|
DISPLAY_BRIGHTNESS = "display_brightness"
|
||||||
|
POWER_LIMIT = "power_limit"
|
||||||
|
CALIBRATION_OFFSET = "calibration_offset"
|
||||||
|
HALL_SENSITIVITY = "hall_sensitivity"
|
||||||
|
MIN_VOLTAGE_PER_CELL = "min_voltage_per_cell"
|
||||||
|
ACCEL_SENSITIVITY = "accel_sensitivity"
|
||||||
|
KEEP_AWAKE_PULSE_POWER = "keep_awake_pulse_power"
|
||||||
|
KEEP_AWAKE_PULSE_DELAY = "keep_awake_pulse_delay"
|
||||||
|
KEEP_AWAKE_PULSE_DURATION = "keep_awake_pulse_duration"
|
||||||
|
VOLTAGE_DIV = "voltage_div"
|
||||||
|
TEMP_INCREMENT_SHORT = "temp_increment_short"
|
||||||
|
TEMP_INCREMENT_LONG = "temp_increment_long"
|
||||||
|
|
||||||
|
|
||||||
|
def multiply(value: float | None, multiplier: float) -> float | None:
|
||||||
|
"""Multiply if not None."""
|
||||||
|
return value * multiplier if value is not None else None
|
||||||
|
|
||||||
|
|
||||||
PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
|
PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
|
||||||
@ -47,13 +84,249 @@ PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
|
|||||||
translation_key=PinecilNumber.SETPOINT_TEMP,
|
translation_key=PinecilNumber.SETPOINT_TEMP,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=NumberDeviceClass.TEMPERATURE,
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
value_fn=lambda data: data.setpoint_temp,
|
value_fn=lambda data, _: data.setpoint_temp,
|
||||||
set_key=CharSetting.SETPOINT_TEMP,
|
characteristic=CharSetting.SETPOINT_TEMP,
|
||||||
mode=NumberMode.BOX,
|
mode=NumberMode.BOX,
|
||||||
native_min_value=MIN_TEMP,
|
native_min_value=MIN_TEMP,
|
||||||
native_step=5,
|
native_step=5,
|
||||||
max_value_fn=lambda data: min(data.max_tip_temp_ability or MAX_TEMP, MAX_TEMP),
|
max_value_fn=lambda data: min(data.max_tip_temp_ability or MAX_TEMP, MAX_TEMP),
|
||||||
),
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.SLEEP_TEMP,
|
||||||
|
translation_key=PinecilNumber.SLEEP_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
|
value_fn=lambda _, settings: settings.get("sleep_temp"),
|
||||||
|
characteristic=CharSetting.SLEEP_TEMP,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=MIN_TEMP,
|
||||||
|
native_max_value=MAX_TEMP,
|
||||||
|
native_step=10,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.BOOST_TEMP,
|
||||||
|
translation_key=PinecilNumber.BOOST_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
device_class=NumberDeviceClass.TEMPERATURE,
|
||||||
|
value_fn=lambda _, settings: settings.get("boost_temp"),
|
||||||
|
characteristic=CharSetting.BOOST_TEMP,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=MAX_TEMP,
|
||||||
|
native_step=10,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.QC_MAX_VOLTAGE,
|
||||||
|
translation_key=PinecilNumber.QC_MAX_VOLTAGE,
|
||||||
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
device_class=NumberDeviceClass.VOLTAGE,
|
||||||
|
value_fn=lambda _, settings: settings.get("qc_ideal_voltage"),
|
||||||
|
characteristic=CharSetting.QC_IDEAL_VOLTAGE,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=9.0,
|
||||||
|
native_max_value=22.0,
|
||||||
|
native_step=0.1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.PD_TIMEOUT,
|
||||||
|
translation_key=PinecilNumber.PD_TIMEOUT,
|
||||||
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
|
device_class=NumberDeviceClass.DURATION,
|
||||||
|
value_fn=lambda _, settings: settings.get("pd_negotiation_timeout"),
|
||||||
|
characteristic=CharSetting.PD_NEGOTIATION_TIMEOUT,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=5.0,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.SHUTDOWN_TIMEOUT,
|
||||||
|
translation_key=PinecilNumber.SHUTDOWN_TIMEOUT,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
device_class=NumberDeviceClass.DURATION,
|
||||||
|
value_fn=lambda _, settings: settings.get("shutdown_time"),
|
||||||
|
characteristic=CharSetting.SHUTDOWN_TIME,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=60,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.DISPLAY_BRIGHTNESS,
|
||||||
|
translation_key=PinecilNumber.DISPLAY_BRIGHTNESS,
|
||||||
|
value_fn=lambda _, settings: settings.get("display_brightness"),
|
||||||
|
characteristic=CharSetting.DISPLAY_BRIGHTNESS,
|
||||||
|
mode=NumberMode.SLIDER,
|
||||||
|
native_min_value=1,
|
||||||
|
native_max_value=5,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.SLEEP_TIMEOUT,
|
||||||
|
translation_key=PinecilNumber.SLEEP_TIMEOUT,
|
||||||
|
value_fn=lambda _, settings: settings.get("sleep_timeout"),
|
||||||
|
characteristic=CharSetting.SLEEP_TIMEOUT,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=15,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.POWER_LIMIT,
|
||||||
|
translation_key=PinecilNumber.POWER_LIMIT,
|
||||||
|
value_fn=lambda _, settings: settings.get("power_limit"),
|
||||||
|
characteristic=CharSetting.POWER_LIMIT,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=12,
|
||||||
|
native_step=0.1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.CALIBRATION_OFFSET,
|
||||||
|
translation_key=PinecilNumber.CALIBRATION_OFFSET,
|
||||||
|
value_fn=lambda _, settings: settings.get("calibration_offset"),
|
||||||
|
characteristic=CharSetting.CALIBRATION_OFFSET,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=100,
|
||||||
|
native_max_value=2500,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfElectricPotential.MICROVOLT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.HALL_SENSITIVITY,
|
||||||
|
translation_key=PinecilNumber.HALL_SENSITIVITY,
|
||||||
|
value_fn=lambda _, settings: settings.get("hall_sensitivity"),
|
||||||
|
characteristic=CharSetting.HALL_SENSITIVITY,
|
||||||
|
mode=NumberMode.SLIDER,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=9,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.MIN_VOLTAGE_PER_CELL,
|
||||||
|
translation_key=PinecilNumber.MIN_VOLTAGE_PER_CELL,
|
||||||
|
value_fn=lambda _, settings: settings.get("min_voltage_per_cell"),
|
||||||
|
characteristic=CharSetting.MIN_VOLTAGE_PER_CELL,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=2.4,
|
||||||
|
native_max_value=3.8,
|
||||||
|
native_step=0.1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.ACCEL_SENSITIVITY,
|
||||||
|
translation_key=PinecilNumber.ACCEL_SENSITIVITY,
|
||||||
|
value_fn=lambda _, settings: settings.get("accel_sensitivity"),
|
||||||
|
characteristic=CharSetting.ACCEL_SENSITIVITY,
|
||||||
|
mode=NumberMode.SLIDER,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=9,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.KEEP_AWAKE_PULSE_POWER,
|
||||||
|
translation_key=PinecilNumber.KEEP_AWAKE_PULSE_POWER,
|
||||||
|
value_fn=lambda _, settings: settings.get("keep_awake_pulse_power"),
|
||||||
|
characteristic=CharSetting.KEEP_AWAKE_PULSE_POWER,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=0,
|
||||||
|
native_max_value=9.9,
|
||||||
|
native_step=0.1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.KEEP_AWAKE_PULSE_DELAY,
|
||||||
|
translation_key=PinecilNumber.KEEP_AWAKE_PULSE_DELAY,
|
||||||
|
value_fn=(
|
||||||
|
lambda _, settings: multiply(settings.get("keep_awake_pulse_delay"), 2.5)
|
||||||
|
),
|
||||||
|
characteristic=CharSetting.KEEP_AWAKE_PULSE_DELAY,
|
||||||
|
raw_value_fn=lambda value: value / 2.5,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=2.5,
|
||||||
|
native_max_value=22.5,
|
||||||
|
native_step=2.5,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.KEEP_AWAKE_PULSE_DURATION,
|
||||||
|
translation_key=PinecilNumber.KEEP_AWAKE_PULSE_DURATION,
|
||||||
|
value_fn=(
|
||||||
|
lambda _, settings: multiply(settings.get("keep_awake_pulse_duration"), 250)
|
||||||
|
),
|
||||||
|
characteristic=CharSetting.KEEP_AWAKE_PULSE_DURATION,
|
||||||
|
raw_value_fn=lambda value: value / 250,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=250,
|
||||||
|
native_max_value=2250,
|
||||||
|
native_step=250,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.VOLTAGE_DIV,
|
||||||
|
translation_key=PinecilNumber.VOLTAGE_DIV,
|
||||||
|
value_fn=(lambda _, settings: settings.get("voltage_div")),
|
||||||
|
characteristic=CharSetting.VOLTAGE_DIV,
|
||||||
|
raw_value_fn=lambda value: value,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=360,
|
||||||
|
native_max_value=900,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.TEMP_INCREMENT_SHORT,
|
||||||
|
translation_key=PinecilNumber.TEMP_INCREMENT_SHORT,
|
||||||
|
value_fn=(lambda _, settings: settings.get("temp_increment_short")),
|
||||||
|
characteristic=CharSetting.TEMP_INCREMENT_SHORT,
|
||||||
|
raw_value_fn=lambda value: value,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=1,
|
||||||
|
native_max_value=50,
|
||||||
|
native_step=1,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
),
|
||||||
|
IronOSNumberEntityDescription(
|
||||||
|
key=PinecilNumber.TEMP_INCREMENT_LONG,
|
||||||
|
translation_key=PinecilNumber.TEMP_INCREMENT_LONG,
|
||||||
|
value_fn=(lambda _, settings: settings.get("temp_increment_long")),
|
||||||
|
characteristic=CharSetting.TEMP_INCREMENT_LONG,
|
||||||
|
raw_value_fn=lambda value: value,
|
||||||
|
mode=NumberMode.BOX,
|
||||||
|
native_min_value=5,
|
||||||
|
native_max_value=90,
|
||||||
|
native_step=5,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -76,23 +349,56 @@ class IronOSNumberEntity(IronOSBaseEntity, NumberEntity):
|
|||||||
|
|
||||||
entity_description: IronOSNumberEntityDescription
|
entity_description: IronOSNumberEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: IronOSCoordinators,
|
||||||
|
entity_description: IronOSNumberEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the number entity."""
|
||||||
|
super().__init__(
|
||||||
|
coordinator.live_data, entity_description, entity_description.characteristic
|
||||||
|
)
|
||||||
|
|
||||||
|
self.settings = coordinator.settings
|
||||||
|
|
||||||
async def async_set_native_value(self, value: float) -> None:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Update the current value."""
|
"""Update the current value."""
|
||||||
|
if raw_value_fn := self.entity_description.raw_value_fn:
|
||||||
|
value = raw_value_fn(value)
|
||||||
try:
|
try:
|
||||||
await self.coordinator.device.write(self.entity_description.set_key, value)
|
await self.coordinator.device.write(
|
||||||
|
self.entity_description.characteristic, value
|
||||||
|
)
|
||||||
except CommunicationError as e:
|
except CommunicationError as e:
|
||||||
raise ServiceValidationError(
|
raise ServiceValidationError(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="submit_setting_failed",
|
translation_key="submit_setting_failed",
|
||||||
) from e
|
) from e
|
||||||
self.async_write_ha_state()
|
await self.settings.async_request_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float | int | None:
|
def native_value(self) -> float | int | None:
|
||||||
"""Return sensor state."""
|
"""Return sensor state."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(
|
||||||
|
self.coordinator.data, self.settings.data
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_max_value(self) -> float:
|
def native_max_value(self) -> float:
|
||||||
"""Return sensor state."""
|
"""Return sensor state."""
|
||||||
return self.entity_description.max_value_fn(self.coordinator.data)
|
|
||||||
|
if self.entity_description.max_value_fn is not None:
|
||||||
|
return self.entity_description.max_value_fn(self.coordinator.data)
|
||||||
|
|
||||||
|
return self.entity_description.native_max_value or DEFAULT_MAX_VALUE
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Run when entity about to be added to hass."""
|
||||||
|
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
self.async_on_remove(
|
||||||
|
self.settings.async_add_listener(
|
||||||
|
self._handle_coordinator_update, self.entity_description.characteristic
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await self.settings.async_request_refresh()
|
||||||
|
@ -141,7 +141,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=UnitOfElectricPotential.MICROVOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.MICROVOLT,
|
||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_display_precision=3,
|
suggested_display_precision=0,
|
||||||
value_fn=lambda data: data.tip_voltage,
|
value_fn=lambda data: data.tip_voltage,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
@ -181,7 +181,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up sensors from a config entry."""
|
"""Set up sensors from a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data.live_data
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
IronOSSensorEntity(coordinator, description)
|
IronOSSensorEntity(coordinator, description)
|
||||||
|
@ -23,6 +23,60 @@
|
|||||||
"number": {
|
"number": {
|
||||||
"setpoint_temperature": {
|
"setpoint_temperature": {
|
||||||
"name": "Setpoint temperature"
|
"name": "Setpoint temperature"
|
||||||
|
},
|
||||||
|
"sleep_temperature": {
|
||||||
|
"name": "Sleep temperature"
|
||||||
|
},
|
||||||
|
"sleep_timeout": {
|
||||||
|
"name": "Sleep timeout"
|
||||||
|
},
|
||||||
|
"qc_max_voltage": {
|
||||||
|
"name": "Quick Charge voltage"
|
||||||
|
},
|
||||||
|
"pd_timeout": {
|
||||||
|
"name": "Power Delivery timeout"
|
||||||
|
},
|
||||||
|
"boost_temp": {
|
||||||
|
"name": "Boost temperature"
|
||||||
|
},
|
||||||
|
"shutdown_timeout": {
|
||||||
|
"name": "Shutdown timeout"
|
||||||
|
},
|
||||||
|
"display_brightness": {
|
||||||
|
"name": "Display brightness"
|
||||||
|
},
|
||||||
|
"power_limit": {
|
||||||
|
"name": "Power limit"
|
||||||
|
},
|
||||||
|
"calibration_offset": {
|
||||||
|
"name": "Calibration offset"
|
||||||
|
},
|
||||||
|
"hall_sensitivity": {
|
||||||
|
"name": "Hall effect sensitivity"
|
||||||
|
},
|
||||||
|
"min_voltage_per_cell": {
|
||||||
|
"name": "Min. voltage per cell"
|
||||||
|
},
|
||||||
|
"accel_sensitivity": {
|
||||||
|
"name": "Motion sensitivity"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_power": {
|
||||||
|
"name": "Keep-awake pulse intensity"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_delay": {
|
||||||
|
"name": "Keep-awake pulse delay"
|
||||||
|
},
|
||||||
|
"keep_awake_pulse_duration": {
|
||||||
|
"name": "Keep-awake pulse duration"
|
||||||
|
},
|
||||||
|
"voltage_div": {
|
||||||
|
"name": "Voltage divider"
|
||||||
|
},
|
||||||
|
"temp_increment_short": {
|
||||||
|
"name": "Short-press temperature step"
|
||||||
|
},
|
||||||
|
"temp_increment_long": {
|
||||||
|
"name": "Long-press temperature step"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
@ -30,7 +30,7 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up IronOS update platform."""
|
"""Set up IronOS update platform."""
|
||||||
|
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data.live_data
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[IronOSUpdate(coordinator, hass.data[IRON_OS_KEY], UPDATE_DESCRIPTION)]
|
[IronOSUpdate(coordinator, hass.data[IRON_OS_KEY], UPDATE_DESCRIPTION)]
|
||||||
|
@ -5,7 +5,13 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
from bleak.backends.device import BLEDevice
|
from bleak.backends.device import BLEDevice
|
||||||
from habluetooth import BluetoothServiceInfoBleak
|
from habluetooth import BluetoothServiceInfoBleak
|
||||||
from pynecil import DeviceInfoResponse, LiveDataResponse, OperatingMode, PowerSource
|
from pynecil import (
|
||||||
|
DeviceInfoResponse,
|
||||||
|
LiveDataResponse,
|
||||||
|
OperatingMode,
|
||||||
|
PowerSource,
|
||||||
|
SettingsDataResponse,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.iron_os import DOMAIN
|
from homeassistant.components.iron_os import DOMAIN
|
||||||
@ -145,6 +151,27 @@ def mock_pynecil() -> Generator[AsyncMock]:
|
|||||||
device_sn="0000c0ffeec0ffee",
|
device_sn="0000c0ffeec0ffee",
|
||||||
name=DEFAULT_NAME,
|
name=DEFAULT_NAME,
|
||||||
)
|
)
|
||||||
|
client.get_settings.return_value = SettingsDataResponse(
|
||||||
|
sleep_temp=150,
|
||||||
|
sleep_timeout=5,
|
||||||
|
min_dc_voltage_cells=0,
|
||||||
|
min_volltage_per_cell=3.3,
|
||||||
|
qc_ideal_voltage=9.0,
|
||||||
|
accel_sensitivity=7,
|
||||||
|
shutdown_time=10,
|
||||||
|
keep_awake_pulse_power=0.5,
|
||||||
|
keep_awake_pulse_delay=4,
|
||||||
|
keep_awake_pulse_duration=1,
|
||||||
|
voltage_div=600,
|
||||||
|
boost_temp=420,
|
||||||
|
calibration_offset=900,
|
||||||
|
power_limit=12.0,
|
||||||
|
temp_increment_long=10,
|
||||||
|
temp_increment_short=1,
|
||||||
|
hall_sensitivity=7,
|
||||||
|
pd_negotiation_timeout=2.0,
|
||||||
|
display_brightness=3,
|
||||||
|
)
|
||||||
client.get_live_data.return_value = LiveDataResponse(
|
client.get_live_data.return_value = LiveDataResponse(
|
||||||
live_temp=298,
|
live_temp=298,
|
||||||
setpoint_temp=300,
|
setpoint_temp=300,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -502,7 +502,7 @@
|
|||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
'sensor': dict({
|
'sensor': dict({
|
||||||
'suggested_display_precision': 3,
|
'suggested_display_precision': 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
"""Test init of IronOS integration."""
|
"""Test init of IronOS integration."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from pynecil import CommunicationError
|
from pynecil import CommunicationError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_pynecil", "ble_device")
|
@pytest.mark.usefixtures("mock_pynecil", "ble_device")
|
||||||
@ -45,16 +48,42 @@ async def test_update_data_config_entry_not_ready(
|
|||||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ble_device")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
||||||
async def test_setup_config_entry_not_ready(
|
async def test_setup_config_entry_not_ready(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
mock_pynecil: AsyncMock,
|
mock_pynecil: AsyncMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test config entry not ready."""
|
"""Test config entry not ready."""
|
||||||
|
mock_pynecil.get_settings.side_effect = CommunicationError
|
||||||
mock_pynecil.get_device_info.side_effect = CommunicationError
|
mock_pynecil.get_device_info.side_effect = CommunicationError
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
freezer.tick(timedelta(seconds=60))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
||||||
|
async def test_settings_exception(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_pynecil: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test skipping of settings on exception."""
|
||||||
|
mock_pynecil.get_settings.side_effect = CommunicationError
|
||||||
|
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
async_fire_time_changed(hass, datetime.now() + timedelta(seconds=60))
|
||||||
|
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_UNKNOWN
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
"""Tests for the IronOS number platform."""
|
"""Tests for the IronOS number platform."""
|
||||||
|
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from pynecil import CharSetting, CommunicationError
|
from pynecil import CharSetting, CommunicationError
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
@ -18,11 +20,11 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, snapshot_platform
|
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
async def sensor_only() -> AsyncGenerator[None]:
|
async def number_only() -> AsyncGenerator[None]:
|
||||||
"""Enable only the number platform."""
|
"""Enable only the number platform."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.iron_os.PLATFORMS",
|
"homeassistant.components.iron_os.PLATFORMS",
|
||||||
@ -39,6 +41,7 @@ async def test_state(
|
|||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the IronOS number platform states."""
|
"""Test the IronOS number platform states."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
@ -47,14 +50,105 @@ async def test_state(
|
|||||||
|
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
freezer.tick(timedelta(seconds=60))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("entity_id", "characteristic", "value", "expected_value"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"number.pinecil_setpoint_temperature",
|
||||||
|
CharSetting.SETPOINT_TEMP,
|
||||||
|
300,
|
||||||
|
300,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_boost_temperature",
|
||||||
|
CharSetting.BOOST_TEMP,
|
||||||
|
420,
|
||||||
|
420,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_calibration_offset",
|
||||||
|
CharSetting.CALIBRATION_OFFSET,
|
||||||
|
600,
|
||||||
|
600,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_display_brightness",
|
||||||
|
CharSetting.DISPLAY_BRIGHTNESS,
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_hall_effect_sensitivity",
|
||||||
|
CharSetting.HALL_SENSITIVITY,
|
||||||
|
7,
|
||||||
|
7,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_keep_awake_pulse_delay",
|
||||||
|
CharSetting.KEEP_AWAKE_PULSE_DELAY,
|
||||||
|
10.0,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_keep_awake_pulse_duration",
|
||||||
|
CharSetting.KEEP_AWAKE_PULSE_DURATION,
|
||||||
|
500,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_keep_awake_pulse_intensity",
|
||||||
|
CharSetting.KEEP_AWAKE_PULSE_POWER,
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_long_press_temperature_step",
|
||||||
|
CharSetting.TEMP_INCREMENT_LONG,
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"number.pinecil_min_voltage_per_cell",
|
||||||
|
CharSetting.MIN_VOLTAGE_PER_CELL,
|
||||||
|
3.3,
|
||||||
|
3.3,
|
||||||
|
),
|
||||||
|
("number.pinecil_motion_sensitivity", CharSetting.ACCEL_SENSITIVITY, 7, 7),
|
||||||
|
(
|
||||||
|
"number.pinecil_power_delivery_timeout",
|
||||||
|
CharSetting.PD_NEGOTIATION_TIMEOUT,
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
),
|
||||||
|
("number.pinecil_power_limit", CharSetting.POWER_LIMIT, 12.0, 12.0),
|
||||||
|
("number.pinecil_quick_charge_voltage", CharSetting.QC_IDEAL_VOLTAGE, 9.0, 9.0),
|
||||||
|
(
|
||||||
|
"number.pinecil_short_press_temperature_step",
|
||||||
|
CharSetting.TEMP_INCREMENT_SHORT,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
("number.pinecil_shutdown_timeout", CharSetting.SHUTDOWN_TIME, 10, 10),
|
||||||
|
("number.pinecil_sleep_temperature", CharSetting.SLEEP_TEMP, 150, 150),
|
||||||
|
("number.pinecil_sleep_timeout", CharSetting.SLEEP_TIMEOUT, 5, 5),
|
||||||
|
("number.pinecil_voltage_divider", CharSetting.VOLTAGE_DIV, 600, 600),
|
||||||
|
],
|
||||||
|
)
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
||||||
async def test_set_value(
|
async def test_set_value(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
mock_pynecil: AsyncMock,
|
mock_pynecil: AsyncMock,
|
||||||
|
entity_id: str,
|
||||||
|
characteristic: CharSetting,
|
||||||
|
value: float,
|
||||||
|
expected_value: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the IronOS number platform set value service."""
|
"""Test the IronOS number platform set value service."""
|
||||||
|
|
||||||
@ -67,12 +161,12 @@ async def test_set_value(
|
|||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
NUMBER_DOMAIN,
|
NUMBER_DOMAIN,
|
||||||
SERVICE_SET_VALUE,
|
SERVICE_SET_VALUE,
|
||||||
service_data={ATTR_VALUE: 300},
|
service_data={ATTR_VALUE: value},
|
||||||
target={ATTR_ENTITY_ID: "number.pinecil_setpoint_temperature"},
|
target={ATTR_ENTITY_ID: entity_id},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert len(mock_pynecil.write.mock_calls) == 1
|
assert len(mock_pynecil.write.mock_calls) == 1
|
||||||
mock_pynecil.write.assert_called_once_with(CharSetting.SETPOINT_TEMP, 300)
|
mock_pynecil.write.assert_called_once_with(characteristic, expected_value)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user