- NumberDesc gains native_min_fn/native_max_fn/step_fn hooks (mirroring the existing unit_fn pattern) so an entity's slider bounds can track the live rep instead of staying pinned to whatever unit the descriptor was written against. Oven setpoint was hardcoded to Celsius bounds (30-270), which silently capped issue #44's Fahrenheit range at 270F -- below a normal 350F bake temp. Verified Fahrenheit bounds (175-550, step 5) come from that dump's /mode/vs/0 Bake modeSpec. - Wire oven.OVEN_SPEC into the oven registry, not just range -- it was only reachable from range before, so a standalone oven reporting /oven/spec/vs/0 would have false-tripped the coverage-gap repair. - Fix a docstring in test_golden_regression.py left over from the cooktop.py -> range.py rename.
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
"""Number platform for Local Things."""
|
|
from __future__ import annotations
|
|
|
|
from homeassistant.components.number import NumberEntity, NumberMode
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from .registry.entities import NumberDesc
|
|
|
|
from .const import DOMAIN
|
|
from .coordinator import LocalThingsCoordinator
|
|
from .entity import LocalThingsEntity, _is_included
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
|
async_add_entities(
|
|
LocalThingsNumber(coordinator, b)
|
|
for b in coordinator.bound
|
|
if isinstance(b.desc, NumberDesc) and _is_included(b, coordinator)
|
|
)
|
|
|
|
|
|
class LocalThingsNumber(LocalThingsEntity, NumberEntity):
|
|
|
|
_attr_mode = NumberMode.SLIDER
|
|
|
|
def __init__(self, coordinator: LocalThingsCoordinator, bound) -> None:
|
|
super().__init__(coordinator, bound)
|
|
desc: NumberDesc = bound.desc
|
|
self._attr_native_unit_of_measurement = desc.unit
|
|
self._attr_device_class = desc.device_class
|
|
if desc.native_min is not None:
|
|
self._attr_native_min_value = desc.native_min
|
|
if desc.native_max is not None:
|
|
self._attr_native_max_value = desc.native_max
|
|
if desc.step is not None:
|
|
self._attr_native_step = desc.step
|
|
|
|
@property
|
|
def native_unit_of_measurement(self):
|
|
desc: NumberDesc = self._bound.desc
|
|
if desc.unit_fn is not None:
|
|
return desc.unit_fn(self.coordinator.resource(self._bound.href))
|
|
return self._attr_native_unit_of_measurement
|
|
|
|
def _range_from_resource(self) -> list | None:
|
|
desc: NumberDesc = self._bound.desc
|
|
if not desc.range_field:
|
|
return None
|
|
r = self.coordinator.resource(self._bound.href).get(desc.range_field)
|
|
return r if (isinstance(r, (list, tuple)) and len(r) == 2) else None
|
|
|
|
@property
|
|
def native_min_value(self) -> float:
|
|
desc: NumberDesc = self._bound.desc
|
|
if desc.native_min_fn is not None:
|
|
return desc.native_min_fn(self.coordinator.resource(self._bound.href))
|
|
r = self._range_from_resource()
|
|
if r is not None:
|
|
return float(r[0])
|
|
if hasattr(self, '_attr_native_min_value'):
|
|
return self._attr_native_min_value
|
|
return super().native_min_value
|
|
|
|
@property
|
|
def native_max_value(self) -> float:
|
|
desc: NumberDesc = self._bound.desc
|
|
if desc.native_max_fn is not None:
|
|
return desc.native_max_fn(self.coordinator.resource(self._bound.href))
|
|
r = self._range_from_resource()
|
|
if r is not None:
|
|
return float(r[1])
|
|
if hasattr(self, '_attr_native_max_value'):
|
|
return self._attr_native_max_value
|
|
return super().native_max_value
|
|
|
|
@property
|
|
def native_step(self) -> float:
|
|
desc: NumberDesc = self._bound.desc
|
|
if desc.step_fn is not None:
|
|
return desc.step_fn(self.coordinator.resource(self._bound.href))
|
|
if hasattr(self, '_attr_native_step'):
|
|
return self._attr_native_step
|
|
return super().native_step
|
|
|
|
@property
|
|
def native_value(self):
|
|
return (self.coordinator.data or {}).get(self._state_key)
|
|
|
|
async def async_set_native_value(self, value: float) -> None:
|
|
await self.coordinator.async_send_command(self._bound, value)
|