Skip to content

Commit

Permalink
- Add support for environment sensors via Panel Server
Browse files Browse the repository at this point in the history
- Remove Alarm Valid
  • Loading branch information
Breina committed May 22, 2024
1 parent 04296fa commit e672a61
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 299 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This will enable monitor electric circuits in great detail.
* **Power**: active, apparent and power factor
* **Energy**: partial (resettable) and total
* **Demand**: active power, maximum active power (resettable) and timestamp of maximum active power
* **Environment**: Temperature, humidity and CO2
* **Alarm**: current state and its reasons
* **Diagnostics**: gateway status, LQI, RSSI, packet loss, connectivity status

Expand All @@ -39,7 +40,7 @@ This will enable monitor electric circuits in great detail.

* All PowerTags
* Acti9 Active
* ZBRTT1 (soon)
* All environment sensors (not HeatTag)

#### Further integrations

Expand Down
66 changes: 32 additions & 34 deletions custom_components/powertag_gateway/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import CONF_CLIENT, DOMAIN
from .entity_base import PowerTagEntity, GatewayEntity, setup_entities, gateway_device_info
from .powertag_features import FeatureClass
from .entity_base import WirelessDeviceEntity, GatewayEntity, setup_entities, gateway_device_info
from .device_features import FeatureClass
from .schneider_modbus import SchneiderModbus, LinkStatus, PanelHealth, TypeOfGateway

_LOGGER = logging.getLogger(__name__)


def list_binary_sensors() -> list[type[PowerTagEntity]]:
def list_binary_sensors() -> list[type[WirelessDeviceEntity]]:
return [
PowerTagWirelessCommunicationValid,
PowerTagRadioCommunicationValid,
PowerTagAlarmValid,
PowerTagGetAlarm
PowerTagAlarm,
AmbientTagAlarm
]


Expand All @@ -45,7 +45,7 @@ async def async_setup_entry(
async_add_entities(entities, update_before_add=False)


class PowerTagWirelessCommunicationValid(PowerTagEntity, BinarySensorEntity):
class PowerTagWirelessCommunicationValid(WirelessDeviceEntity, BinarySensorEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

Expand All @@ -59,14 +59,15 @@ async def async_update(self):
def supports_feature_set(feature_class: FeatureClass) -> bool:
return feature_class in [FeatureClass.A1, FeatureClass.A2, FeatureClass.P1, FeatureClass.F1, FeatureClass.F2,
FeatureClass.F3, FeatureClass.FL, FeatureClass.M0, FeatureClass.M1, FeatureClass.M2,
FeatureClass.M3, FeatureClass.R1, FeatureClass.C]
FeatureClass.M3, FeatureClass.R1, FeatureClass.C,
FeatureClass.TEMP0, FeatureClass.TEMP1, FeatureClass.CO2]

@staticmethod
def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagRadioCommunicationValid(PowerTagEntity, BinarySensorEntity):
class PowerTagRadioCommunicationValid(WirelessDeviceEntity, BinarySensorEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

Expand All @@ -80,21 +81,36 @@ async def async_update(self):
def supports_feature_set(feature_class: FeatureClass) -> bool:
return feature_class in [FeatureClass.A1, FeatureClass.A2, FeatureClass.P1, FeatureClass.F1, FeatureClass.F2,
FeatureClass.F3, FeatureClass.FL, FeatureClass.M0, FeatureClass.M1, FeatureClass.M2,
FeatureClass.M3, FeatureClass.R1, FeatureClass.C]
FeatureClass.M3, FeatureClass.R1, FeatureClass.C,
FeatureClass.TEMP0, FeatureClass.TEMP1, FeatureClass.CO2]

@staticmethod
def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagAlarmValid(PowerTagEntity, BinarySensorEntity):
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 valid")
super().__init__(client, modbus_index, tag_device, "alarm info")

async def async_update(self):
self._attr_is_on = not self._client.tag_is_alarm_valid(self._modbus_index)
alarm = self._client.tag_get_alarm(self._modbus_index)

if alarm.has_alarm != self._attr_is_on:
self._attr_is_on = alarm.has_alarm
self._attr_extra_state_attributes = {
"Voltage loss": alarm.voltage_loss,
"Current overload when voltage loss": alarm.current_overload_when_voltage_loss,
"Current short-circuit": alarm.current_short_circuit,
"Overload 45%": alarm.current_overload_45_percent,
"Load current loss": alarm.load_current_loss,
"Overvoltage 120%": alarm.overvoltage_120_percent,
"Undervoltage 80%": alarm.undervoltage_80_percent,
"Current 50%": alarm.current_50_percent,
"Current 80%": alarm.current_80_percent
}

@staticmethod
def supports_feature_set(feature_class: FeatureClass) -> bool:
Expand All @@ -107,42 +123,24 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagGetAlarm(PowerTagEntity, BinarySensorEntity):
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, "alarm info")
super().__init__(client, modbus_index, tag_device, "battery")
self.__product_range = self._client.tag_product_range(self._modbus_index)

async def async_update(self):
alarm = self._client.tag_get_alarm(self._modbus_index)
self._attr_is_on = alarm.has_alarm

if self.__product_range == "PowerTag":
self._attr_extra_state_attributes = {
"Voltage loss": alarm.alarm_voltage_loss,
"Overcurrent at voltage loss": alarm.alarm_current_overload,
"Overload 45%": alarm.alarm_overload_45_percent,
"Loadcurrent loss": alarm.alarm_load_current_loss,
"Overvoltage 120%": alarm.alarm_overvoltage,
"Undervoltage 80%": alarm.alarm_undervoltage
}
elif self.__product_range == "HeatTag":
self._attr_extra_state_attributes = {
"Alarm": alarm.alarm_heattag_alarm,
"Preventive maintenance on device": alarm.alarm_heattag_maintenance,
"Device replacement": alarm.alarm_heattag_replacement
}

@staticmethod
def supports_feature_set(feature_class: FeatureClass) -> bool:
return feature_class in [FeatureClass.A1, FeatureClass.A2, FeatureClass.P1, FeatureClass.F1, FeatureClass.F2,
FeatureClass.F3, FeatureClass.FL, FeatureClass.M0, FeatureClass.M1, FeatureClass.M2,
FeatureClass.M3, FeatureClass.R1, FeatureClass.C]
return feature_class in [FeatureClass.TEMP1, FeatureClass.CO2]

@staticmethod
def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]
return type_of_gateway in [TypeOfGateway.PANEL_SERVER]


class GatewayStatus(GatewayEntity, BinarySensorEntity):
Expand Down
18 changes: 9 additions & 9 deletions custom_components/powertag_gateway/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .entity_base import PowerTagEntity, setup_entities
from .powertag_features import FeatureClass
from .entity_base import WirelessDeviceEntity, setup_entities
from .device_features import FeatureClass
from .schneider_modbus import SchneiderModbus, TypeOfGateway

_LOGGER = logging.getLogger(__name__)


def list_buttons() -> list[type[PowerTagEntity]]:
def list_buttons() -> list[type[WirelessDeviceEntity]]:
return [
PowerTagResetPeakDemand,
PowerTagResetActiveEnergy,
Expand All @@ -34,7 +34,7 @@ async def async_setup_entry(
async_add_entities(entities, update_before_add=False)


class PowerTagResetPeakDemand(PowerTagEntity, ButtonEntity):
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")

Expand All @@ -58,7 +58,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagResetActiveEnergy(PowerTagEntity, ButtonEntity):
class PowerTagResetActiveEnergy(WirelessDeviceEntity, ButtonEntity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo):
super().__init__(client, modbus_index, tag_device, "reset active energy")

Expand All @@ -80,7 +80,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagResetActiveEnergyDelivered(PowerTagEntity, ButtonEntity):
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")

Expand All @@ -102,7 +102,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagResetActiveEnergyReceived(PowerTagEntity, ButtonEntity):
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")

Expand All @@ -124,7 +124,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.SMARTLINK, TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagResetReactiveEnergyDelivered(PowerTagEntity, ButtonEntity):
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")

Expand All @@ -148,7 +148,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
return type_of_gateway in [TypeOfGateway.POWERTAG_LINK, TypeOfGateway.PANEL_SERVER]


class PowerTagResetReactiveEnergyReceived(PowerTagEntity, ButtonEntity):
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")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class FeatureClass(Enum):
M3 = auto()
R1 = auto()
C = auto()
TEMP0 = auto
TEMP1 = auto()
CO2 = auto()


class UnknownDevice(IntegrationError):
Expand All @@ -38,7 +41,10 @@ def from_commercial_reference(commercial_reference: str) -> FeatureClass:
'^LV434022$': FeatureClass.M2,
'^LV434023$': FeatureClass.M3,
'^A9MEM1590|A9MEM1591|A9MEM1592|A9MEM1593|PLTR.$': FeatureClass.R1,
'^A9TAA....|A9TAB....|A9TDEC...|A9TDFC...|A9TDFD...|A9TPDD...|A9TPED...|A9TYAE...|A9TYBE...$': FeatureClass.C
'^A9TAA....|A9TAB....|A9TDEC...|A9TDFC...|A9TDFD...|A9TPDD...|A9TPED...|A9TYAE...|A9TYBE...$': FeatureClass.C,
'^EMS59440$': FeatureClass.TEMP0,
'^SED-TRH-G-5045|ZBRTT1|ESST010B0400|A9XST114|EMS59443$': FeatureClass.TEMP1,
'^SED-CO2-G-5045$': FeatureClass.CO2
}.items():
if re.match(regex, commercial_reference):
return result
Expand Down
13 changes: 7 additions & 6 deletions custom_components/powertag_gateway/entity_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@

from .const import CONF_CLIENT, DOMAIN
from .const import GATEWAY_DOMAIN, TAG_DOMAIN
from .powertag_features import FeatureClass, from_commercial_reference, UnknownDevice, from_wireless_device_type_code
from .device_features import FeatureClass, from_commercial_reference, UnknownDevice, from_wireless_device_type_code
from .schneider_modbus import SchneiderModbus, Phase, LineVoltage, PhaseSequence, TypeOfGateway

_LOGGER = logging.getLogger(__name__)


def gateway_device_info(client: SchneiderModbus, presentation_url: str) -> DeviceInfo:
serial = client.serial_number()
return DeviceInfo(
configuration_url=presentation_url,
identifiers={(GATEWAY_DOMAIN, client.serial_number())},
identifiers={(GATEWAY_DOMAIN, serial)},
hw_version=client.hardware_version(),
sw_version=client.firmware_version(),
manufacturer=client.manufacturer(),
model=client.product_code(),
name=client.name(),
serial_number=client.serial_number()
serial_number=serial
)


Expand Down Expand Up @@ -113,7 +114,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):
raise NotImplementedError()


class PowerTagEntity(Entity):
class WirelessDeviceEntity(Entity):
def __init__(self, client: SchneiderModbus, modbus_index: int, tag_device: DeviceInfo, entity_name: str):
self._client = client
self._modbus_index = modbus_index
Expand All @@ -134,7 +135,7 @@ def supports_gateway(type_of_gateway: TypeOfGateway):


def collect_entities(client: SchneiderModbus, entities: list[Entity], feature_class: FeatureClass, modbus_address: int,
powertag_entity: type[PowerTagEntity], tag_device: DeviceInfo, tag_phase_sequence: PhaseSequence):
powertag_entity: type[WirelessDeviceEntity], tag_device: DeviceInfo, tag_phase_sequence: PhaseSequence):
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 Down Expand Up @@ -171,7 +172,7 @@ def collect_entities(client: SchneiderModbus, entities: list[Entity], feature_cl
entities.append(powertag_entity(*args))


def setup_entities(hass: HomeAssistant, config_entry: ConfigEntry, powertag_entities: list[type[PowerTagEntity]]):
def setup_entities(hass: HomeAssistant, config_entry: ConfigEntry, powertag_entities: list[type[WirelessDeviceEntity]]):
data = hass.data[DOMAIN][config_entry.entry_id]
client = data[CONF_CLIENT]
presentation_url = data[CONF_INTERNAL_URL]
Expand Down
2 changes: 1 addition & 1 deletion custom_components/powertag_gateway/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/Breina/PowerTagGateway/issues",
"requirements": ["pymodbus==3.5.4"],
"version": "0.1.1"
"version": "0.4.0"
}
Loading

0 comments on commit e672a61

Please sign in to comment.