Skip to content

Commit

Permalink
Expand unique_id to not cause collisions when the first 10 characters…
Browse files Browse the repository at this point in the history
… of the serial is identical
  • Loading branch information
Breina committed Nov 9, 2024
1 parent e1eddb3 commit 13ab2b5
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 137 deletions.
38 changes: 28 additions & 10 deletions custom_components/powertag_gateway/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
"""PowerTag Link Gateway integration"""
import logging
from enum import Enum, auto

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform, CONF_HOST, CONF_PORT, CONF_INTERNAL_URL
from homeassistant.const import Platform, CONF_HOST, CONF_PORT, \
CONF_INTERNAL_URL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from pymodbus.exceptions import ConnectionException

from .const import CONF_CLIENT, DOMAIN, CONF_TYPE_OF_GATEWAY
from .const import CONF_CLIENT, DOMAIN, CONF_TYPE_OF_GATEWAY, \
CONF_DEVICE_UNIQUE_ID_VERSION
from .schneider_modbus import SchneiderModbus, TypeOfGateway

PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR]

_LOGGER = logging.getLogger(__name__)


class UniqueIdVersion(Enum):
V0 = auto()
V1 = auto()


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up EcoStruxure PowerTag Link Gateway from a config entry."""
Expand All @@ -18,8 +30,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
host = entry.data.get(CONF_HOST)
port = entry.data.get(CONF_PORT)
presentation_url = entry.data.get(CONF_INTERNAL_URL)
type_of_gateway_string = entry.data.get(CONF_TYPE_OF_GATEWAY, TypeOfGateway.POWERTAG_LINK.value)
type_of_gateway = [t for t in TypeOfGateway if t.value == type_of_gateway_string][0]
type_of_gateway_string = (
entry.data.get(CONF_TYPE_OF_GATEWAY, TypeOfGateway.POWERTAG_LINK.value))

type_of_gateway = \
[t for t in TypeOfGateway if t.value == type_of_gateway_string][0]

unique_id_version = entry.data.get(CONF_DEVICE_UNIQUE_ID_VERSION)
if not unique_id_version:
_LOGGER.warning("Using older version of device's unique ID, "
"may cause conflicts with duplicate serials.")
unique_id_version = UniqueIdVersion.V0

try:
client = SchneiderModbus(host, type_of_gateway, port)
Expand All @@ -28,14 +49,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

hass.data[DOMAIN][entry.entry_id] = {
CONF_CLIENT: client,
CONF_INTERNAL_URL: presentation_url
CONF_INTERNAL_URL: presentation_url,
CONF_DEVICE_UNIQUE_ID_VERSION: unique_id_version
}

for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
entry, platform
)
)
await hass.config_entries.async_forward_entry_setup(entry, platform)

return True
18 changes: 9 additions & 9 deletions custom_components/powertag_gateway/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from homeassistant.helpers.entity import EntityCategory, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import CONF_CLIENT, DOMAIN
from . import CONF_CLIENT, DOMAIN, UniqueIdVersion
from .device_features import FeatureClass
from .entity_base import WirelessDeviceEntity, GatewayEntity, setup_entities, gateway_device_info
from .schneider_modbus import SchneiderModbus, LinkStatus, PanelHealth, TypeOfGateway
Expand Down Expand Up @@ -49,8 +49,8 @@ class PowerTagWirelessCommunicationValid(WirelessDeviceEntity, BinarySensorEntit
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "wireless communication valid")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "wireless communication valid", unique_id_version)

async def async_update(self):
self._attr_is_on = self._client.tag_wireless_communication_valid(self._modbus_index)
Expand All @@ -71,8 +71,8 @@ class PowerTagRadioCommunicationValid(WirelessDeviceEntity, BinarySensorEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "radio communication valid")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "radio communication valid", unique_id_version)

async def async_update(self):
self._attr_is_on = self._client.tag_radio_communication_valid(self._modbus_index)
Expand All @@ -92,8 +92,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
class PowerTagAlarm(WirelessDeviceEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.PROBLEM

def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "alarm info")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "alarm info", unique_id_version)

async def async_update(self):
alarm = self._client.tag_get_alarm(self._modbus_index)
Expand Down Expand Up @@ -126,8 +126,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:
class AmbientTagAlarm(WirelessDeviceEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.PROBLEM

def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "battery")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "battery", unique_id_version)
self.__product_range = self._client.tag_product_range(self._modbus_index)

async def async_update(self):
Expand Down
25 changes: 13 additions & 12 deletions custom_components/powertag_gateway/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import UniqueIdVersion
from .device_features import FeatureClass
from .entity_base import WirelessDeviceEntity, setup_entities
from .schneider_modbus import SchneiderModbus, TypeOfGateway
Expand Down Expand Up @@ -35,8 +36,8 @@ async def async_setup_entry(


class PowerTagResetPeakDemand(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset peak demand")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset peak demand", unique_id_version)

def press(self) -> None:
self.reset()
Expand All @@ -57,8 +58,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class PowerTagResetActiveEnergyDelivered(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset active energy delivered")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset active energy delivered", unique_id_version)

def press(self) -> None:
self.reset()
Expand All @@ -81,8 +82,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class PowerTagResetActiveEnergyReceived(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset active energy received")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset active energy received", unique_id_version)

def press(self) -> None:
self.reset()
Expand All @@ -105,8 +106,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class PowerTagResetReactiveEnergyDelivered(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset reactive energy delivered")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset reactive energy delivered", unique_id_version)

def press(self) -> None:
self.reset()
Expand All @@ -129,8 +130,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class PowerTagResetReactiveEnergyReceived(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset reactive energy received")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset reactive energy received", unique_id_version)

def press(self) -> None:
self.reset()
Expand All @@ -153,8 +154,8 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class PowerTagResetApparentEnergy(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset apparent energy")
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, unique_id_version: UniqueIdVersion):
super().__init__(client, modbus_index, tag_device, "reset apparent energy", unique_id_version)

def press(self) -> None:
self.reset()
Expand Down
12 changes: 8 additions & 4 deletions custom_components/powertag_gateway/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_DEVICE, CONF_INTERNAL_URL
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_DEVICE, \
CONF_INTERNAL_URL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from pymodbus.exceptions import ConnectionException

from . import UniqueIdVersion
from .const import (
DEFAULT_MODBUS_PORT,
CONF_MANUAL_INPUT,
DPWS_MODEL_NAME,
DPWS_PRESENTATION_URL,
DPWS_FRIENDLY_NAME,
DPWS_SERIAL_NUMBER,
DOMAIN, CONF_TYPE_OF_GATEWAY
DOMAIN, CONF_TYPE_OF_GATEWAY, CONF_DEVICE_UNIQUE_ID_VERSION
)
from .schneider_modbus import SchneiderModbus, TypeOfGateway, LinkStatus, PanelHealth
from .schneider_modbus import SchneiderModbus, TypeOfGateway, LinkStatus, \
PanelHealth
from .soap_communication import Soapy, dpws_discovery

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -248,7 +251,8 @@ async def async_step_connect(self, user_input=None) -> FlowResult:
CONF_HOST: self.host,
CONF_PORT: self.port,
CONF_INTERNAL_URL: self.presentation_url,
CONF_TYPE_OF_GATEWAY: self.type_of_gateway
CONF_TYPE_OF_GATEWAY: self.type_of_gateway,
CONF_DEVICE_UNIQUE_ID_VERSION: UniqueIdVersion.V1
},
)

Expand Down
1 change: 1 addition & 0 deletions custom_components/powertag_gateway/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

CONF_MANUAL_INPUT = 'Manually configure EnergyTag Link Gateway'
CONF_TYPE_OF_GATEWAY = 'type_of_gateway'
CONF_DEVICE_UNIQUE_ID_VERSION = 'device_unique_id_version'

CONF_CLIENT = 'client'

Expand Down
24 changes: 19 additions & 5 deletions custom_components/powertag_gateway/entity_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity, DeviceInfo

from .const import CONF_CLIENT, DOMAIN
from . import UniqueIdVersion
from .const import CONF_CLIENT, DOMAIN, CONF_DEVICE_UNIQUE_ID_VERSION
from .const import GATEWAY_DOMAIN, TAG_DOMAIN
from .device_features import FeatureClass, from_commercial_reference, UnknownDevice, from_wireless_device_type_code
from .schneider_modbus import SchneiderModbus, Phase, LineVoltage, PhaseSequence, TypeOfGateway
Expand Down Expand Up @@ -115,15 +116,22 @@ def supports_gateway(type_of_gateway: TypeOfGateway) -> bool:


class WirelessDeviceEntity(Entity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, entity_name: str):
def __init__(self, client: SchneiderModbus, modbus_index: int,
tag_device: DeviceInfo, entity_name: str,
unique_id_version: UniqueIdVersion):

self._client = client
self._modbus_index = modbus_index

self._attr_device_info = tag_device
self._attr_name = f"{tag_device['name']} {entity_name}"

serial = client.tag_serial_number(modbus_index)
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{entity_name}"

if unique_id_version == UniqueIdVersion.V1:
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{entity_name}{modbus_index}"
else:
self._attr_unique_id = f"{TAG_DOMAIN}{serial}{entity_name}"

@staticmethod
def supports_feature_set(feature_class: FeatureClass) -> bool:
Expand All @@ -139,7 +147,7 @@ def supports_firmware_version(firmware_version: str) -> bool:


def collect_entities(client: SchneiderModbus, entities: list[Entity], feature_class: FeatureClass, modbus_address: int,
powertag_entity: type[WirelessDeviceEntity], tag_device: DeviceInfo, tag_phase_sequence: PhaseSequence):
powertag_entity: type[WirelessDeviceEntity], tag_device: DeviceInfo, tag_phase_sequence: PhaseSequence, device_unique_id_version: UniqueIdVersion):
params_raw = inspect.signature(powertag_entity.__init__).parameters
params = [name for name in params_raw.items() if name[0] != "self" and name[0] != "kwargs"]
args = []
Expand All @@ -164,6 +172,8 @@ def collect_entities(client: SchneiderModbus, entities: list[Entity], feature_cl
assert not enumerate_param
enumerate_param = (len(args), phase_sequence_to_line_voltages(tag_phase_sequence, feature_class))
args.append(None)
elif typey == UniqueIdVersion:
args.append(device_unique_id_version)
else:
raise AssertionError("Dev fucked up, please create a GitHub issue. :(")
if enumerate_param:
Expand All @@ -180,6 +190,8 @@ def setup_entities(hass: HomeAssistant, config_entry: ConfigEntry, powertag_enti
data = hass.data[DOMAIN][config_entry.entry_id]
client = data[CONF_CLIENT]
presentation_url = data[CONF_INTERNAL_URL]
device_unique_id_version = data[CONF_DEVICE_UNIQUE_ID_VERSION]

entities = []
gateway_device = gateway_device_info(client, presentation_url)
for i in range(1, 100):
Expand Down Expand Up @@ -237,7 +249,9 @@ def setup_entities(hass: HomeAssistant, config_entry: ConfigEntry, powertag_enti
and entity.supports_firmware_version(tag_device['sw_version'])
]:
collect_entities(
client, entities, feature_class, modbus_address, powertag_entity, tag_device, tag_phase_sequence
client, entities, feature_class, modbus_address,
powertag_entity, tag_device, tag_phase_sequence,
device_unique_id_version
)

_LOGGER.info(f"Done with device at address {modbus_address}: {device_name}")
Expand Down
Loading

0 comments on commit 13ab2b5

Please sign in to comment.