3737 WaterHeaterEntity ,
3838 WaterHeaterEntityFeature ,
3939)
40- from homeassistant .const import UnitOfEnergy , UnitOfPressure , UnitOfTemperature , UnitOfTime
40+ from homeassistant .const import (
41+ UnitOfEnergy ,
42+ UnitOfPressure ,
43+ UnitOfTemperature ,
44+ UnitOfTime ,
45+ UnitOfVolume ,
46+ )
4147from homeassistant .util import dt as dt_util
4248from homeassistant .core import callback
4349from homeassistant .exceptions import ConfigEntryAuthFailed , HomeAssistantError
@@ -479,6 +485,27 @@ def _thermostat_valve_field(data: dict[str, Any], valve_id: int, field: str) ->
479485 return row .get (field )
480486
481487
488+ def _thermostat_valve_battery (data : dict [str , Any ], valve_id : int ) -> Any :
489+ """Return a normalized battery state for a thermostat valve."""
490+ raw = _thermostat_valve_field (data , valve_id , "battery" )
491+ if isinstance (raw , str ) and raw .strip ().lower () == "ok" :
492+ return "OK"
493+ return raw
494+
495+
496+ def _thermostat_valve_zone_name (data : dict [str , Any ], valve_id : int ) -> Any :
497+ """Return a zone display name for a thermostat valve when available."""
498+ raw_zone = _thermostat_valve_field (data , valve_id , "zone" )
499+ if raw_zone is None :
500+ return None
501+
502+ zone_id = f"zn{ raw_zone } "
503+ zone_name = _decode_zone_name (_val (data , f"/zones/{ zone_id } /name" ))
504+ if isinstance (zone_name , str ) and zone_name .strip ():
505+ return zone_name
506+ return raw_zone
507+
508+
482509def _thermostat_valve_device_info (
483510 uuid : str ,
484511 data : dict [str , Any ],
@@ -516,6 +543,7 @@ def _pointtapi_thermostat_valve_sensor_descriptions(
516543 # No SIGNAL_STRENGTH device class: HA only accepts dB/dBm for
517544 # it, and /devices/list reports link quality as a percentage.
518545 native_unit_of_measurement = "%" ,
546+ icon = "mdi:signal" ,
519547 entity_category = EntityCategory .DIAGNOSTIC ,
520548 value_fn = lambda d , vid = valve_id : _thermostat_valve_field (d , vid , "signal" ),
521549 available_fn = lambda d , vid = valve_id : _thermostat_valve_row_by_id (d , vid ) is not None ,
@@ -524,16 +552,17 @@ def _pointtapi_thermostat_valve_sensor_descriptions(
524552 BoschPoinTTAPISensorEntityDescription (
525553 key = f"/devices/list/thermostat_valve/{ valve_id } /battery" ,
526554 translation_key = "thermostat_valve_battery" ,
555+ icon = "mdi:battery" ,
527556 entity_category = EntityCategory .DIAGNOSTIC ,
528- value_fn = lambda d , vid = valve_id : _thermostat_valve_field (d , vid , "battery" ),
557+ value_fn = lambda d , vid = valve_id : _thermostat_valve_battery (d , vid ),
529558 available_fn = lambda d , vid = valve_id : _thermostat_valve_row_by_id (d , vid ) is not None ,
530559 device_info_fn = lambda u , d , lang = None , vid = valve_id : _thermostat_valve_device_info (u , d , vid , lang ),
531560 ),
532561 BoschPoinTTAPISensorEntityDescription (
533562 key = f"/devices/list/thermostat_valve/{ valve_id } /zone" ,
534563 translation_key = "thermostat_valve_zone" ,
535564 entity_category = EntityCategory .DIAGNOSTIC ,
536- value_fn = lambda d , vid = valve_id : _thermostat_valve_field (d , vid , "zone" ),
565+ value_fn = lambda d , vid = valve_id : _thermostat_valve_zone_name (d , vid ),
537566 available_fn = lambda d , vid = valve_id : _thermostat_valve_row_by_id (d , vid ) is not None ,
538567 device_info_fn = lambda u , d , lang = None , vid = valve_id : _thermostat_valve_device_info (u , d , vid , lang ),
539568 ),
@@ -637,6 +666,12 @@ def _start_of_today() -> Any:
637666 return dt_util .start_of_local_day ()
638667
639668
669+ def _start_of_month () -> Any :
670+ """Return start of the current month in local timezone for last_reset."""
671+ now = dt_util .now ()
672+ return now .replace (day = 1 , hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
673+
674+
640675# ── Hourly gas usage helper functions ────────────────────────────────────────
641676
642677
@@ -1089,17 +1124,30 @@ def _pointtapi_electricity_average_sensor_descriptions(
10891124 return ()
10901125
10911126 candidates = (
1092- ("/energy/electricity/dayAverage" , "electricity_day_average" ),
1093- ("/energy/electricity/monthAverage" , "electricity_month_average" ),
1127+ (
1128+ "/energy/electricity/dayAverage" ,
1129+ "electricity_day_average" ,
1130+ _start_of_today ,
1131+ ),
1132+ (
1133+ "/energy/electricity/monthAverage" ,
1134+ "electricity_month_average" ,
1135+ _start_of_month ,
1136+ ),
10941137 )
10951138
10961139 descriptions : list [BoschPoinTTAPISensorEntityDescription ] = []
1097- for path , translation_key in candidates :
1140+ for path , translation_key , last_reset_fn in candidates :
10981141 if isinstance (data .get (path ), dict ) and _path_available (data , path ):
10991142 descriptions .append (
11001143 BoschPoinTTAPISensorEntityDescription (
11011144 key = path ,
11021145 translation_key = translation_key ,
1146+ device_class = SensorDeviceClass .ENERGY ,
1147+ native_unit_of_measurement = UnitOfEnergy .KILO_WATT_HOUR ,
1148+ state_class = SensorStateClass .TOTAL ,
1149+ value_fn = lambda d , p = path : _val (d , p ),
1150+ last_reset_fn = last_reset_fn ,
11031151 )
11041152 )
11051153 return tuple (descriptions )
@@ -1410,6 +1458,15 @@ def _pointtapi_sensor_descriptions(
14101458 ),
14111459 ]
14121460
1461+ if isinstance (data .get ("/gateway/zigbee/versionFirmware" ), dict ):
1462+ descriptions .append (
1463+ BoschPoinTTAPISensorEntityDescription (
1464+ key = "/gateway/zigbee/versionFirmware" ,
1465+ translation_key = "zigbee_firmware_version" ,
1466+ entity_category = EntityCategory .DIAGNOSTIC ,
1467+ )
1468+ )
1469+
14131470 if _gateway_ui_has_eco_reference (data ):
14141471 descriptions .append (
14151472 BoschPoinTTAPISensorEntityDescription (
@@ -1510,105 +1567,131 @@ def last_reset(self) -> Any:
15101567# ── Number entities (boost settings) ─────────────────────────────────────────
15111568
15121569
1513- POINTTAPI_NUMBER_DESCRIPTIONS : tuple [NumberEntityDescription , ...] = (
1514- NumberEntityDescription (
1515- key = "/heatingCircuits/hc1/boostTemperature" ,
1516- translation_key = "boost_temperature" ,
1517- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1518- native_min_value = 5.0 ,
1519- native_max_value = 30.0 ,
1520- native_step = 0.5 ,
1521- ),
1522- NumberEntityDescription (
1523- key = "/heatingCircuits/hc1/boostDuration" ,
1524- translation_key = "boost_duration" ,
1525- native_unit_of_measurement = UnitOfTime .HOURS ,
1526- native_min_value = 0.5 ,
1527- native_max_value = 24.0 ,
1528- native_step = 0.5 ,
1529- ),
1530- # ── Heating circuit configuration (2b) ───────────────────────────────────
1531- NumberEntityDescription (
1532- key = "/heatingCircuits/hc1/maxSupply" ,
1533- translation_key = "max_supply_temperature" ,
1534- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1535- native_min_value = 25.0 ,
1536- native_max_value = 90.0 ,
1537- native_step = 1.0 ,
1538- entity_category = EntityCategory .CONFIG ,
1539- ),
1540- NumberEntityDescription (
1541- key = "/heatingCircuits/hc1/minSupply" ,
1542- translation_key = "min_supply_temperature" ,
1543- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1544- native_min_value = 10.0 ,
1545- native_max_value = 90.0 ,
1546- native_step = 1.0 ,
1547- entity_category = EntityCategory .CONFIG ,
1548- ),
1549- NumberEntityDescription (
1550- key = "/heatingCircuits/hc1/nightThreshold" ,
1551- translation_key = "night_setback_threshold" ,
1552- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1553- native_min_value = 5.0 ,
1554- native_max_value = 30.0 ,
1555- native_step = 0.5 ,
1556- entity_category = EntityCategory .CONFIG ,
1557- ),
1558- NumberEntityDescription (
1559- key = "/heatingCircuits/hc1/suWiThreshold" ,
1560- translation_key = "summer_winter_threshold" ,
1561- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1562- native_min_value = 10.0 ,
1563- native_max_value = 30.0 ,
1564- native_step = 0.5 ,
1565- entity_category = EntityCategory .CONFIG ,
1566- ),
1567- NumberEntityDescription (
1568- key = "/heatingCircuits/hc1/roomInfluence" ,
1569- translation_key = "room_influence" ,
1570- native_min_value = 0.0 ,
1571- native_max_value = 3.0 ,
1572- native_step = 1.0 ,
1573- entity_category = EntityCategory .CONFIG ,
1574- ),
1575- NumberEntityDescription (
1576- key = "/system/sensors/temperatures/offset" ,
1577- translation_key = "temperature_calibration_offset" ,
1578- native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1579- native_min_value = - 5.0 ,
1580- native_max_value = 5.0 ,
1581- native_step = 0.5 ,
1582- entity_category = EntityCategory .CONFIG ,
1583- ),
1584- NumberEntityDescription (
1585- key = "/energy/gas/annualGoal" ,
1586- translation_key = "annual_gas_goal" ,
1587- native_unit_of_measurement = UnitOfEnergy .KILO_WATT_HOUR ,
1588- native_min_value = 0.0 ,
1589- native_max_value = 1000000.0 ,
1590- native_step = 1.0 ,
1591- entity_category = EntityCategory .CONFIG ,
1592- ),
1593- # ── v1.0.0 comfort controls (constraints from boost-probe-notes.md) ───────
1594- NumberEntityDescription (
1595- key = "/dhwCircuits/dhw1/extraDhwDuration" ,
1596- translation_key = "extra_hot_water_duration" ,
1597- native_unit_of_measurement = UnitOfTime .MINUTES ,
1598- native_min_value = 15.0 ,
1599- native_max_value = 2880.0 ,
1600- native_step = 15.0 ,
1601- ),
1602- NumberEntityDescription (
1603- key = "/dhwCircuits/dhw1/thermalDisinfect/time" ,
1604- translation_key = "thermal_disinfect_time" ,
1605- native_unit_of_measurement = UnitOfTime .MINUTES ,
1606- native_min_value = 0.0 ,
1607- native_max_value = 1439.0 ,
1608- native_step = 1.0 ,
1609- entity_category = EntityCategory .CONFIG ,
1610- ),
1611- )
1570+ def _pointtapi_number_descriptions (
1571+ data : dict [str , Any ] | None = None ,
1572+ ) -> tuple [NumberEntityDescription , ...]:
1573+ """Return POINTTAPI number descriptions, plus optional yearly energy goals."""
1574+ descriptions : list [NumberEntityDescription ] = [
1575+ NumberEntityDescription (
1576+ key = "/heatingCircuits/hc1/boostTemperature" ,
1577+ translation_key = "boost_temperature" ,
1578+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1579+ native_min_value = 5.0 ,
1580+ native_max_value = 30.0 ,
1581+ native_step = 0.5 ,
1582+ ),
1583+ NumberEntityDescription (
1584+ key = "/heatingCircuits/hc1/boostDuration" ,
1585+ translation_key = "boost_duration" ,
1586+ native_unit_of_measurement = UnitOfTime .HOURS ,
1587+ native_min_value = 0.5 ,
1588+ native_max_value = 24.0 ,
1589+ native_step = 0.5 ,
1590+ ),
1591+ # ── Heating circuit configuration (2b) ───────────────────────────────────
1592+ NumberEntityDescription (
1593+ key = "/heatingCircuits/hc1/maxSupply" ,
1594+ translation_key = "max_supply_temperature" ,
1595+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1596+ native_min_value = 25.0 ,
1597+ native_max_value = 90.0 ,
1598+ native_step = 1.0 ,
1599+ entity_category = EntityCategory .CONFIG ,
1600+ ),
1601+ NumberEntityDescription (
1602+ key = "/heatingCircuits/hc1/minSupply" ,
1603+ translation_key = "min_supply_temperature" ,
1604+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1605+ native_min_value = 10.0 ,
1606+ native_max_value = 90.0 ,
1607+ native_step = 1.0 ,
1608+ entity_category = EntityCategory .CONFIG ,
1609+ ),
1610+ NumberEntityDescription (
1611+ key = "/heatingCircuits/hc1/nightThreshold" ,
1612+ translation_key = "night_setback_threshold" ,
1613+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1614+ native_min_value = 5.0 ,
1615+ native_max_value = 30.0 ,
1616+ native_step = 0.5 ,
1617+ entity_category = EntityCategory .CONFIG ,
1618+ ),
1619+ NumberEntityDescription (
1620+ key = "/heatingCircuits/hc1/suWiThreshold" ,
1621+ translation_key = "summer_winter_threshold" ,
1622+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1623+ native_min_value = 10.0 ,
1624+ native_max_value = 30.0 ,
1625+ native_step = 0.5 ,
1626+ entity_category = EntityCategory .CONFIG ,
1627+ ),
1628+ NumberEntityDescription (
1629+ key = "/heatingCircuits/hc1/roomInfluence" ,
1630+ translation_key = "room_influence" ,
1631+ native_min_value = 0.0 ,
1632+ native_max_value = 3.0 ,
1633+ native_step = 1.0 ,
1634+ entity_category = EntityCategory .CONFIG ,
1635+ ),
1636+ NumberEntityDescription (
1637+ key = "/system/sensors/temperatures/offset" ,
1638+ translation_key = "temperature_calibration_offset" ,
1639+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
1640+ native_min_value = - 5.0 ,
1641+ native_max_value = 5.0 ,
1642+ native_step = 0.5 ,
1643+ entity_category = EntityCategory .CONFIG ,
1644+ ),
1645+ # ── v1.0.0 comfort controls (constraints from boost-probe-notes.md) ───────
1646+ NumberEntityDescription (
1647+ key = "/dhwCircuits/dhw1/extraDhwDuration" ,
1648+ translation_key = "extra_hot_water_duration" ,
1649+ native_unit_of_measurement = UnitOfTime .MINUTES ,
1650+ native_min_value = 15.0 ,
1651+ native_max_value = 2880.0 ,
1652+ native_step = 15.0 ,
1653+ ),
1654+ NumberEntityDescription (
1655+ key = "/dhwCircuits/dhw1/thermalDisinfect/time" ,
1656+ translation_key = "thermal_disinfect_time" ,
1657+ native_unit_of_measurement = UnitOfTime .MINUTES ,
1658+ native_min_value = 0.0 ,
1659+ native_max_value = 1439.0 ,
1660+ native_step = 1.0 ,
1661+ entity_category = EntityCategory .CONFIG ,
1662+ ),
1663+ ]
1664+
1665+ if isinstance ((data or {}).get ("/energy/gas/annualGoal" ), dict ):
1666+ descriptions .append (
1667+ NumberEntityDescription (
1668+ key = "/energy/gas/annualGoal" ,
1669+ translation_key = "annual_gas_goal" ,
1670+ native_unit_of_measurement = UnitOfEnergy .KILO_WATT_HOUR ,
1671+ native_min_value = 0.0 ,
1672+ native_max_value = 1000000.0 ,
1673+ native_step = 1.0 ,
1674+ entity_category = EntityCategory .CONFIG ,
1675+ )
1676+ )
1677+
1678+ if isinstance ((data or {}).get ("/energy/electricity/annualGoal" ), dict ):
1679+ descriptions .append (
1680+ NumberEntityDescription (
1681+ key = "/energy/electricity/annualGoal" ,
1682+ translation_key = "annual_electricity_goal" ,
1683+ native_unit_of_measurement = UnitOfEnergy .KILO_WATT_HOUR ,
1684+ native_min_value = 0.0 ,
1685+ native_max_value = 1000000.0 ,
1686+ native_step = 1.0 ,
1687+ entity_category = EntityCategory .CONFIG ,
1688+ )
1689+ )
1690+
1691+ return tuple (descriptions )
1692+
1693+
1694+ POINTTAPI_NUMBER_DESCRIPTIONS : tuple [NumberEntityDescription , ...] = _pointtapi_number_descriptions ()
16121695
16131696
16141697class BoschPoinTTAPINumberEntity (
@@ -2073,6 +2156,8 @@ class BoschPoinTTAPISwitchEntityDescription(SwitchEntityDescription):
20732156 BoschPoinTTAPISwitchEntityDescription (
20742157 key = "/dhwCircuits/dhw1/thermalDisinfect/state" ,
20752158 translation_key = "thermal_disinfect" ,
2159+ on_value = "on" ,
2160+ off_value = "off" ,
20762161 device_id_suffix = "dhw1" ,
20772162 device_name_override = "Water heater" ,
20782163 ),
0 commit comments