Add tip connected detection to IronOS (#131946)

* Add binary platform and tip connected detection to IronOS

* suggested changes

* fix

* fix mypy

* revert accidental overwriting

* Remove binary sensor

* snapshot
This commit is contained in:
Manu 2024-12-09 10:50:37 +01:00 committed by GitHub
parent 5e8012f3f5
commit ee8f720253
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 17 deletions

View File

@ -92,6 +92,17 @@ class IronOSLiveDataCoordinator(IronOSBaseCoordinator[LiveDataResponse]):
except CommunicationError as e:
raise UpdateFailed("Cannot connect to device") from e
@property
def has_tip(self) -> bool:
"""Return True if the tip is connected."""
if (
self.data.max_tip_temp_ability is not None
and self.data.live_temp is not None
):
threshold = self.data.max_tip_temp_ability - 5
return self.data.live_temp <= threshold
return False
class IronOSFirmwareUpdateCoordinator(DataUpdateCoordinator[GitHubReleaseModel]):
"""IronOS coordinator for retrieving update information from github."""

View File

@ -28,6 +28,7 @@ from homeassistant.helpers.typing import StateType
from . import IronOSConfigEntry
from .const import OHM
from .coordinator import IronOSLiveDataCoordinator
from .entity import IronOSBaseEntity
# Coordinator is used to centralize the data updates
@ -57,7 +58,7 @@ class PinecilSensor(StrEnum):
class IronOSSensorEntityDescription(SensorEntityDescription):
"""IronOS sensor entity descriptions."""
value_fn: Callable[[LiveDataResponse], StateType]
value_fn: Callable[[LiveDataResponse, bool], StateType]
PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
@ -67,7 +68,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.live_temp,
value_fn=lambda data, has_tip: data.live_temp if has_tip else None,
),
IronOSSensorEntityDescription(
key=PinecilSensor.DC_VOLTAGE,
@ -75,7 +76,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.dc_voltage,
value_fn=lambda data, _: data.dc_voltage,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -84,7 +85,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.handle_temp,
value_fn=lambda data, _: data.handle_temp,
),
IronOSSensorEntityDescription(
key=PinecilSensor.PWMLEVEL,
@ -93,7 +94,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
suggested_display_precision=0,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.pwm_level,
value_fn=lambda data, _: data.pwm_level,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -101,14 +102,16 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
translation_key=PinecilSensor.POWER_SRC,
device_class=SensorDeviceClass.ENUM,
options=[item.name.lower() for item in PowerSource],
value_fn=lambda data: data.power_src.name.lower() if data.power_src else None,
value_fn=(
lambda data, _: data.power_src.name.lower() if data.power_src else None
),
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.TIP_RESISTANCE,
translation_key=PinecilSensor.TIP_RESISTANCE,
native_unit_of_measurement=OHM,
value_fn=lambda data: data.tip_resistance,
value_fn=lambda data, has_tip: data.tip_resistance if has_tip else None,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
),
@ -118,7 +121,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda data: data.uptime,
value_fn=lambda data, _: data.uptime,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -127,7 +130,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.movement_time,
value_fn=lambda data, _: data.movement_time,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -135,7 +138,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
translation_key=PinecilSensor.MAX_TIP_TEMP_ABILITY,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
value_fn=lambda data: data.max_tip_temp_ability,
value_fn=lambda data, has_tip: data.max_tip_temp_ability if has_tip else None,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -145,7 +148,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
value_fn=lambda data: data.tip_voltage,
value_fn=lambda data, has_tip: data.tip_voltage if has_tip else None,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -153,7 +156,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
translation_key=PinecilSensor.HALL_SENSOR,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda data: data.hall_sensor,
value_fn=lambda data, _: data.hall_sensor,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
@ -162,7 +165,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.ENUM,
options=[item.name.lower() for item in OperatingMode],
value_fn=(
lambda data: data.operating_mode.name.lower()
lambda data, _: data.operating_mode.name.lower()
if data.operating_mode
else None
),
@ -173,7 +176,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.estimated_power,
value_fn=lambda data, _: data.estimated_power,
),
)
@ -196,8 +199,11 @@ class IronOSSensorEntity(IronOSBaseEntity, SensorEntity):
"""Representation of a IronOS sensor entity."""
entity_description: IronOSSensorEntityDescription
coordinator: IronOSLiveDataCoordinator
@property
def native_value(self) -> StateType:
"""Return sensor state."""
return self.entity_description.value_fn(self.coordinator.data)
return self.entity_description.value_fn(
self.coordinator.data, self.coordinator.has_tip
)

View File

@ -4,13 +4,13 @@ from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from pynecil import CommunicationError
from pynecil import CommunicationError, LiveDataResponse
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.iron_os.coordinator import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -71,3 +71,34 @@ async def test_sensors_unavailable(
)
for entity_entry in entity_entries:
assert hass.states.get(entity_entry.entity_id).state == STATE_UNAVAILABLE
@pytest.mark.usefixtures(
"entity_registry_enabled_by_default", "ble_device", "mock_pynecil"
)
async def test_tip_detection(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pynecil: AsyncMock,
ble_device: MagicMock,
) -> None:
"""Test sensor state is unknown when tip is disconnected."""
mock_pynecil.get_live_data.return_value = LiveDataResponse(
live_temp=479,
max_tip_temp_ability=460,
)
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
entities = {
"sensor.pinecil_tip_temperature",
"sensor.pinecil_max_tip_temperature",
"sensor.pinecil_raw_tip_voltage",
"sensor.pinecil_tip_resistance",
}
for entity_id in entities:
assert hass.states.get(entity_id).state == STATE_UNKNOWN