Skip to content

Commit 15d7472

Browse files
committed
Align with core integration
1 parent 349e7b0 commit 15d7472

13 files changed

Lines changed: 805 additions & 499 deletions

File tree

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,40 @@
1-
"""Support for Victron Venus binary sensors."""
1+
"""Support for Victron GX binary sensors."""
22

3-
import logging
43
from typing import Any
5-
from functools import cached_property
64

75
from victron_mqtt import (
86
Device as VictronVenusDevice,
97
Metric as VictronVenusMetric,
108
MetricKind,
9+
VictronEnum,
1110
)
1211

1312
from homeassistant.components.binary_sensor import BinarySensorEntity
14-
from homeassistant.config_entries import ConfigEntry
1513
from homeassistant.core import HomeAssistant, callback
1614
from homeassistant.helpers.device_registry import DeviceInfo
1715
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
1816

1917
from .const import SWITCH_ON
2018
from .entity import VictronBaseEntity
21-
from .hub import Hub, VictronGxConfigEntry
19+
from .hub import VictronGxConfigEntry
2220

23-
24-
_LOGGER = logging.getLogger(__name__)
21+
PARALLEL_UPDATES = 0 # There is no I/O in the entity itself.
2522

2623

2724
async def async_setup_entry(
2825
hass: HomeAssistant,
2926
config_entry: VictronGxConfigEntry,
3027
async_add_entities: AddConfigEntryEntitiesCallback,
3128
) -> None:
32-
"""Set up Victron Venus sensors from a config entry."""
33-
hub: Hub = config_entry.runtime_data
29+
"""Set up Victron GX binary sensors from a config entry."""
30+
hub = config_entry.runtime_data
3431

3532
def on_new_metric(
3633
device: VictronVenusDevice,
3734
metric: VictronVenusMetric,
3835
device_info: DeviceInfo,
3936
) -> None:
40-
"""Handle new sensor metric discovery."""
37+
"""Handle new binary sensor metric discovery."""
4138
assert hub._hub.installation_id is not None
4239
async_add_entities(
4340
[
@@ -51,7 +48,7 @@ def on_new_metric(
5148

5249

5350
class VictronBinarySensor(VictronBaseEntity, BinarySensorEntity):
54-
"""Implementation of a Victron Venus binary sensor."""
51+
"""Implementation of a Victron GX binary sensor."""
5552

5653
def __init__(
5754
self,
@@ -62,25 +59,21 @@ def __init__(
6259
installation_id: str,
6360
) -> None:
6461
"""Initialize the binary sensor."""
65-
self._attr_is_on = self._is_on(metric.value)
6662
super().__init__(
6763
device, metric, device_info, "binary_sensor", simple_naming, installation_id
6864
)
69-
70-
@staticmethod
71-
def _is_on(value: Any) -> bool:
72-
return str(value) == SWITCH_ON
65+
self._attr_is_on = self._is_on(metric.value)
7366

7467
@callback
75-
def _on_update_task(self, value: Any) -> None:
76-
new_val = self._is_on(value)
77-
if self._attr_is_on == new_val:
78-
return
79-
self._attr_is_on = new_val
68+
def _on_update_cb(self, value: Any) -> None:
69+
self._attr_is_on = self._is_on(value)
8070
self.async_write_ha_state()
8171

82-
@cached_property
83-
def is_on(self) -> bool:
84-
"""Return the current state of the binary sensor."""
85-
assert self._attr_is_on is not None
86-
return self._attr_is_on
72+
@staticmethod
73+
def _is_on(value: Any) -> bool | None:
74+
"""Convert a Victron switch value to a boolean."""
75+
return (
76+
value.id == SWITCH_ON
77+
if value is not None and isinstance(value, VictronEnum)
78+
else None
79+
)

custom_components/victron_mqtt/button.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Support for Victron Venus buttons."""
1+
"""Support for Victron GX button entities."""
22

33
import logging
44
from typing import Any
@@ -11,32 +11,33 @@
1111
)
1212

1313
from homeassistant.components.button import ButtonEntity
14-
from homeassistant.config_entries import ConfigEntry
1514
from homeassistant.core import HomeAssistant, callback
1615
from homeassistant.helpers.device_registry import DeviceInfo
1716
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
1817

1918
from .const import SWITCH_ON
2019
from .entity import VictronBaseEntity
21-
from .hub import Hub
20+
from .hub import VictronGxConfigEntry
2221

2322
_LOGGER = logging.getLogger(__name__)
2423

24+
PARALLEL_UPDATES = 0 # There is no I/O in the entity itself.
25+
2526

2627
async def async_setup_entry(
2728
hass: HomeAssistant,
28-
config_entry: ConfigEntry,
29+
config_entry: VictronGxConfigEntry,
2930
async_add_entities: AddConfigEntryEntitiesCallback,
3031
) -> None:
31-
"""Set up Victron Venus sensors from a config entry."""
32-
hub: Hub = config_entry.runtime_data
32+
"""Set up Victron GX button entities from a config entry."""
33+
hub = config_entry.runtime_data
3334

3435
def on_new_metric(
3536
device: VictronVenusDevice,
3637
metric: VictronVenusMetric,
3738
device_info: DeviceInfo,
3839
) -> None:
39-
"""Handle new sensor metric discovery."""
40+
"""Handle new button metric discovery."""
4041
assert isinstance(metric, VictronVenusWritableMetric)
4142
assert hub._hub.installation_id is not None
4243
async_add_entities(
@@ -55,7 +56,7 @@ def on_new_metric(
5556

5657

5758
class VictronButton(VictronBaseEntity, ButtonEntity):
58-
"""Implementation of a Victron Venus button using ButtonEntity."""
59+
"""Implementation of a Victron GX button entity."""
5960

6061
def __init__(
6162
self,
@@ -71,11 +72,11 @@ def __init__(
7172
)
7273

7374
@callback
74-
def _on_update_task(self, value: Any) -> None:
75+
def _on_update_cb(self, value: Any) -> None:
7576
pass
7677

7778
def press(self) -> None:
7879
"""Press the button."""
79-
_LOGGER.info("Pressing button: %s", self._attr_unique_id)
8080
assert isinstance(self._metric, VictronVenusWritableMetric)
81+
_LOGGER.debug("Pressing button: %s", self._attr_unique_id)
8182
self._metric.set(SWITCH_ON)

custom_components/victron_mqtt/const.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Constants for the victron_mqtt integration."""
2+
from victron_mqtt import MetricNature
3+
4+
from homeassistant.components.sensor import SensorStateClass
25

36
# Integration specific values (custom / builtin Home Assistant)
47
DOMAIN = "victron_mqtt"
58
DEFAULT_SIMPLE_NAMING = True
6-
ENTITY_PREFIX = "victron_mqtt"
79

810
# generic config values
911
CONF_INSTALLATION_ID = "installation_id"
@@ -32,10 +34,12 @@
3234
ATTR_VALUE = "value"
3335

3436
# Not using GenericOnOff as some switches use different enums.
35-
# It has to be with value "On" to be on and "Off" to be off.
36-
SWITCH_ON = "On"
37-
SWITCH_OFF = "Off"
37+
# It has to be with id "on" to be on and "off" to be off.
38+
SWITCH_ON = "on"
39+
SWITCH_OFF = "off"
40+
3841

39-
# Entity IDs which needs special treatment
40-
ENTITIES_CATEGORY_DIAGNOSTIC = ["system_heartbeat", "solarcharger_device_off_reason"]
41-
ENTITIES_DISABLE_BY_DEFAULT = ["system_heartbeat", "solarcharger_device_off_reason"]
42+
METRIC_NATURE_TO_STATE_CLASS: dict[MetricNature, SensorStateClass] = {
43+
MetricNature.CUMULATIVE: SensorStateClass.TOTAL_INCREASING,
44+
MetricNature.INSTANTANEOUS: SensorStateClass.MEASUREMENT,
45+
}
Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
1-
"""Common code for Victron Venus integration."""
1+
"""Base entity for entities in victron_gx integration."""
22

33
from abc import abstractmethod
4-
import logging
54
from typing import Any
65

7-
from victron_mqtt import (
8-
Device as VictronVenusDevice,
9-
Metric as VictronVenusMetric,
10-
MetricKind,
11-
MetricNature,
12-
MetricType,
13-
)
6+
from victron_mqtt import Device as VictronVenusDevice, Metric as VictronVenusMetric
147

15-
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
168
from homeassistant.const import EntityCategory
179
from homeassistant.core import callback
1810
from homeassistant.helpers.device_registry import DeviceInfo
1911
from homeassistant.helpers.entity import Entity
2012

21-
from .const import ENTITIES_CATEGORY_DIAGNOSTIC, ENTITIES_DISABLE_BY_DEFAULT, ENTITY_PREFIX
13+
# Entities that should be marked as diagnostic
14+
ENTITIES_CATEGORY_DIAGNOSTIC = ["system_heartbeat", "solarcharger_device_off_reason"]
15+
# Entities that should be disabled by default
16+
ENTITIES_DISABLE_BY_DEFAULT = ["system_heartbeat", "solarcharger_device_off_reason"]
2217

23-
_LOGGER = logging.getLogger(__name__)
18+
ENTITY_PREFIX = "victron_mqtt"
2419

2520

2621
class VictronBaseEntity(Entity):
27-
"""Implementation of a Victron Venus base entity."""
22+
"""Implementation of a Victron GX base entity."""
2823

2924
_attr_should_poll = False
3025
_attr_has_entity_name = True
@@ -42,7 +37,6 @@ def __init__(
4237
self._device = device
4338
self._metric = metric
4439
self._attr_device_info = device_info
45-
self._metric = metric
4640
if simple_naming:
4741
entity_id = f"{entity_platform}.{ENTITY_PREFIX}_{metric.unique_id}"
4842
else:
@@ -51,19 +45,9 @@ def __init__(
5145
self._attr_suggested_display_precision = metric.precision
5246
self._attr_translation_key = metric.generic_short_id.replace("{", "").replace(
5347
"}", ""
54-
) # same as in merge_topics.py
48+
)
5549
self._attr_translation_placeholders = metric.key_values
5650

57-
# Some attributes are relevant only for certain metric kinds
58-
if metric.metric_kind in [MetricKind.SENSOR, MetricKind.NUMBER]:
59-
self._attr_device_class = self._map_metric_to_device_class(metric)
60-
self._attr_state_class = self._map_metric_to_stateclass(metric)
61-
# Only set native_unit_of_measurement when a device_class is present.
62-
# Entities without a device_class get their display unit from
63-
# the translation files instead (set by merge_topics.py).
64-
if self._attr_device_class is not None:
65-
self._attr_native_unit_of_measurement = metric.unit_of_measurement
66-
6751
self._attr_entity_category = (
6852
EntityCategory.DIAGNOSTIC
6953
if metric.generic_short_id in ENTITIES_CATEGORY_DIAGNOSTIC
@@ -73,76 +57,22 @@ def __init__(
7357
metric.generic_short_id not in ENTITIES_DISABLE_BY_DEFAULT
7458
)
7559

76-
77-
def __repr__(self) -> str:
78-
"""Return a string representation of the entity."""
79-
return (
80-
f"VictronBaseEntity(device={self._device.name}, "
81-
f"unique_id={self._attr_unique_id}, "
82-
f"metric={self._metric.short_id}, "
83-
f"translation_key={self._attr_translation_key}, "
84-
f"translation_placeholders={self._attr_translation_placeholders})"
85-
)
86-
8760
@callback
8861
@abstractmethod
89-
def _on_update_task(self, value: Any) -> None:
62+
def _on_update_cb(self, value: Any) -> None:
9063
"""Handle the metric update. Must be implemented by subclasses."""
9164

9265
@callback
9366
def _on_update(self, _: VictronVenusMetric, value: Any) -> None:
94-
self._on_update_task(value)
67+
self._on_update_cb(value)
9568

9669
async def async_added_to_hass(self) -> None:
9770
"""Run when entity about to be added to hass."""
9871
await super().async_added_to_hass()
99-
# Now we can safely register for updates as the entity is fully registered with Home Assistant
10072
self._metric.on_update = self._on_update
10173

10274
async def async_will_remove_from_hass(self) -> None:
10375
"""Run when entity will be removed from hass."""
104-
# Remove our update callback by setting a no-op function
76+
# Unregister update callback
10577
self._metric.on_update = None
10678
await super().async_will_remove_from_hass()
107-
108-
def _map_metric_to_device_class(
109-
self, metric: VictronVenusMetric
110-
) -> SensorDeviceClass | None:
111-
match metric.metric_type:
112-
case MetricType.POWER:
113-
return SensorDeviceClass.POWER
114-
case MetricType.APPARENT_POWER:
115-
return SensorDeviceClass.APPARENT_POWER
116-
case MetricType.ENERGY:
117-
return SensorDeviceClass.ENERGY
118-
case MetricType.VOLTAGE:
119-
return SensorDeviceClass.VOLTAGE
120-
case MetricType.CURRENT:
121-
return SensorDeviceClass.CURRENT
122-
case MetricType.FREQUENCY:
123-
return SensorDeviceClass.FREQUENCY
124-
case MetricType.ELECTRIC_STORAGE_PERCENTAGE:
125-
return SensorDeviceClass.BATTERY
126-
case MetricType.TEMPERATURE:
127-
return SensorDeviceClass.TEMPERATURE
128-
case MetricType.SPEED:
129-
return SensorDeviceClass.SPEED
130-
case MetricType.LIQUID_VOLUME:
131-
return SensorDeviceClass.VOLUME_STORAGE
132-
case MetricType.DURATION:
133-
return SensorDeviceClass.DURATION
134-
case MetricType.TIME:
135-
# Sadly, there is no SensorDeviceClass for time
136-
return None
137-
case _:
138-
return None
139-
140-
def _map_metric_to_stateclass(
141-
self, metric: VictronVenusMetric
142-
) -> SensorStateClass | None:
143-
if metric.metric_nature == MetricNature.CUMULATIVE:
144-
return SensorStateClass.TOTAL
145-
if metric.metric_nature == MetricNature.INSTANTANEOUS:
146-
return SensorStateClass.MEASUREMENT
147-
148-
return None

0 commit comments

Comments
 (0)