diff --git a/custom_components/sinapsi_alfa/api.py b/custom_components/sinapsi_alfa/api.py index 1825a29..6c1b8f1 100644 --- a/custom_components/sinapsi_alfa/api.py +++ b/custom_components/sinapsi_alfa/api.py @@ -15,6 +15,7 @@ from pymodbus.payload import BinaryPayloadDecoder from .const import MANUFACTURER, MODEL, SENSOR_ENTITIES +from .helpers import unix_timestamp_to_iso8601_local_tz _LOGGER = logging.getLogger(__name__) @@ -256,19 +257,37 @@ def read_modbus_alfa(self): decoder = BinaryPayloadDecoder.fromRegisters( read_data.registers, byteorder=Endian.BIG ) + + # decode the register based on type if reg_type == "uint16": value = round(float(decoder.decode_16bit_uint()), 2) elif reg_type == "uint32": value = round(float(decoder.decode_32bit_uint()), 2) _LOGGER.debug(f"(read_modbus_alfa) Raw Value: {value}") + # Alfa provides power/energy data in W/Wh, we want kW/kWh if reg_dev_class in [ SensorDeviceClass.ENERGY, SensorDeviceClass.POWER, ]: value = round(float(value / 1000), 2) + # if not power/energy type, it's an integer else: value = int(value) + + # if distacco is 65535 set it to 0 + if reg_key == "tempo_residuo_distacco": + if value == 65535: + value = 0 + # if data_evento is > 4294967294 then no event + if reg_key == "data_evento": + if value > 4294967294: + value = "None" + else: + # convert timestamp to ISO8601 + value = unix_timestamp_to_iso8601_local_tz( + value + self.data["tempo_residuo_distacco"] + ) self.data[reg_key] = value _LOGGER.debug(f"(read_modbus_alfa) Data: {self.data[reg_key]}") except Exception as modbus_error: diff --git a/custom_components/sinapsi_alfa/config_flow.py b/custom_components/sinapsi_alfa/config_flow.py index 8edca97..425faa4 100644 --- a/custom_components/sinapsi_alfa/config_flow.py +++ b/custom_components/sinapsi_alfa/config_flow.py @@ -3,9 +3,7 @@ https://github.com/alexdelprete/ha-sinapsi-alfa """ -import ipaddress import logging -import re import voluptuous as vol from homeassistant import config_entries @@ -26,20 +24,11 @@ DEFAULT_SCAN_INTERVAL, DOMAIN, ) +from .helpers import host_valid _LOGGER = logging.getLogger(__name__) -def host_valid(host): - """Return True if hostname or IP address is valid.""" - try: - if ipaddress.ip_address(host).version == (4 or 6): - return True - except ValueError: - disallowed = re.compile(r"[^a-zA-Z\d\-]") - return all(x and not disallowed.search(x) for x in host.split(".")) - - @callback def get_host_from_config(hass: HomeAssistant): """Return the hosts already configured.""" diff --git a/custom_components/sinapsi_alfa/const.py b/custom_components/sinapsi_alfa/const.py index 2848212..9db578f 100644 --- a/custom_components/sinapsi_alfa/const.py +++ b/custom_components/sinapsi_alfa/const.py @@ -253,16 +253,6 @@ "modbus_type": "uint16", "modbus_addr": 203, }, - { - "name": "Data Evento", - "key": "data_evento", - "icon": "mdi:calendar-outline", - "device_class": None, - "state_class": None, - "unit": None, - "modbus_type": "uint32", - "modbus_addr": 780, - }, { "name": "Tempo Residuo Distacco", "key": "tempo_residuo_distacco", @@ -273,6 +263,16 @@ "modbus_type": "uint16", "modbus_addr": 782, }, + { + "name": "Data Evento", + "key": "data_evento", + "icon": "mdi:calendar-outline", + "device_class": None, + "state_class": None, + "unit": None, + "modbus_type": "uint32", + "modbus_addr": 780, + }, { "name": "Potenza Consumata", "key": "potenza_consumata", diff --git a/custom_components/sinapsi_alfa/helpers.py b/custom_components/sinapsi_alfa/helpers.py new file mode 100644 index 0000000..66dd0fc --- /dev/null +++ b/custom_components/sinapsi_alfa/helpers.py @@ -0,0 +1,58 @@ +"""Helper functions for Sinapsi Alfa. + +https://github.com/alexdelprete/ha-sinapsi-alfa +""" + +import ipaddress +import re +import time +from datetime import datetime, timedelta, timezone + + +def host_valid(host): + """Return True if hostname or IP address is valid.""" + try: + if ipaddress.ip_address(host).version == (4 or 6): + return True + except ValueError: + disallowed = re.compile(r"[^a-zA-Z\d\-]") + return all(x and not disallowed.search(x) for x in host.split(".")) + + +def get_local_timezone_offset() -> float: + """Get local timezone offset.""" + # Get the current time in seconds since the epoch + current_time = time.time() + + # Get the local timezone offset in seconds + local_timezone_offset_seconds = ( + -time.timezone + if (time.localtime(current_time).tm_isdst == 0) + else -time.altzone + ) + + # Convert the offset to hours + local_timezone_offset_hours = local_timezone_offset_seconds / 3600 + + return local_timezone_offset_hours + + +def unix_timestamp_to_iso8601_local_tz(unix_timestamp: int) -> str: + """Convert timestamp to ISO8601.""" + + # Convert Unix timestamp to datetime object in UTC + dt_utc = datetime.utcfromtimestamp(unix_timestamp).replace(tzinfo=timezone.utc) # noqa: UP017 + + # Get the local timezone offset + local_timezone_offset_hours = get_local_timezone_offset() + + # Create a timezone object with the local offset + local_timezone = timezone(timedelta(hours=local_timezone_offset_hours)) + + # Convert UTC datetime to local datetime + dt_local = dt_utc.astimezone(local_timezone) + + # Format local datetime object as ISO8601 string + iso8601_format = dt_local.isoformat() + + return iso8601_format