commit f85beb29e177f640cbb236724376de76cec3ffa1 Author: Flavien Haas Date: Fri Apr 25 20:01:50 2025 +0200 first commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ed00a87 --- /dev/null +++ b/__init__.py @@ -0,0 +1,16 @@ +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from .const import DOMAIN + +async def async_setup(hass: HomeAssistant, config: ConfigType): + return True + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "sensor") + ) + return True + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + return await hass.config_entries.async_forward_entry_unload(entry, "sensor") diff --git a/config_flow.py b/config_flow.py new file mode 100644 index 0000000..956374f --- /dev/null +++ b/config_flow.py @@ -0,0 +1,21 @@ +from homeassistant import config_entries +import voluptuous as vol + +from .const import DOMAIN, CONF_IEEE, CONF_INTERVAL, DEFAULT_INTERVAL + +class LinkyTarifConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + async def async_step_user(self, user_input=None): + errors = {} + + if user_input is not None: + return self.async_create_entry(title="Linky Tarif", data={ + CONF_IEEE: user_input[CONF_IEEE], + }, options={ + CONF_INTERVAL: user_input.get(CONF_INTERVAL, DEFAULT_INTERVAL) + }) + + schema = vol.Schema({ + vol.Required(CONF_IEEE): str, + vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): int, + }) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/const.py b/const.py new file mode 100644 index 0000000..62de2dc --- /dev/null +++ b/const.py @@ -0,0 +1,4 @@ +DOMAIN = "linky_tarif" +CONF_IEEE = "ieee" +CONF_INTERVAL = "interval" +DEFAULT_INTERVAL = 300 diff --git a/coordinator.py b/coordinator.py new file mode 100644 index 0000000..2cbec9f --- /dev/null +++ b/coordinator.py @@ -0,0 +1,46 @@ +from datetime import timedelta +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from zigpy import types +import logging + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +class LinkyTarifCoordinator(DataUpdateCoordinator): + def __init__(self, hass: HomeAssistant, ieee: str, interval: int): + super().__init__( + hass, + _LOGGER, + name="Linky Tarif Coordinator", + update_interval=timedelta(seconds=interval), + ) + self._ieee = ieee + + async def _async_update_data(self): + zha_storage = self.hass.data["zha"] + app_ctrl = zha_storage.gateway.application + + device = app_ctrl.get_device(types.EUI64.convert(self._ieee)) + if not device: + raise UpdateFailed(f"Device with IEEE {self._ieee} not found") + + endpoint = device.endpoints.get(1) + if not endpoint: + raise UpdateFailed("Endpoint 1 not found") + + cluster = endpoint[0xFF66] + if not cluster: + raise UpdateFailed("Cluster 0xFF66 not found") + + try: + res = await cluster.read_attributes([0x0010]) + value = res.get(0x0010) + return { + "tariff": value, + "last_update": self.hass.helpers.event.dt_util.utcnow().isoformat(), + } + except Exception as e: + raise UpdateFailed(f"Error reading tariff: {e}") diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..4a2064f Binary files /dev/null and b/icon.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..a0407b7 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "linky_tarif", + "name": "Linky Tarif Sensor", + "version": "1.0.0", + "config_flow": true, + "documentation": "https://github.com/yourusername/linky_tarif", + "requirements": [], + "dependencies": ["zha"], + "codeowners": ["@yourusername"] +} diff --git a/sensor.py b/sensor.py new file mode 100644 index 0000000..f15e159 --- /dev/null +++ b/sensor.py @@ -0,0 +1,36 @@ +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity import DeviceInfo + +from .coordinator import LinkyTarifCoordinator +from .const import DOMAIN, CONF_IEEE, CONF_INTERVAL + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback): + coordinator = LinkyTarifCoordinator( + hass, + ieee=entry.data[CONF_IEEE], + interval=entry.options.get(CONF_INTERVAL, 300), + ) + await coordinator.async_config_entry_first_refresh() + async_add_entities([LinkyTarifSensor(coordinator)]) + +class LinkyTarifSensor(SensorEntity): + def __init__(self, coordinator: LinkyTarifCoordinator): + self.coordinator = coordinator + self._attr_unique_id = f"linky_tarif_{coordinator._ieee}" + self._attr_name = "Linky Tarif Period" + self._attr_icon = "mdi:flash" + self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, coordinator._ieee)}) + + @property + def native_value(self): + return self.coordinator.data.get("tariff") if self.coordinator.data else None + + @property + def extra_state_attributes(self): + return {"last_update": self.coordinator.data.get("last_update")} if self.coordinator.data else {} + + async def async_update(self): + await self.coordinator.async_request_refresh() diff --git a/strings.json b/strings.json new file mode 100644 index 0000000..eac31d7 --- /dev/null +++ b/strings.json @@ -0,0 +1,13 @@ +{ + "domain": "linky_tarif", + "name": "Linky Tarif", + "config_flow": { + "title": "Linky Tarif", + "description": "Monitors Linky energy meter tariff periods using ZHA.", + "step": { + "user": { + "title": "Configure Linky Tarif" + } + } + } +} diff --git a/translations/en.json b/translations/en.json new file mode 100644 index 0000000..ae15dc2 --- /dev/null +++ b/translations/en.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "title": "Configure Linky Tarif", + "description": "Enter the IEEE address of your ZLinky_TIC device.", + "data": { + "ieee": "Device IEEE address", + "interval": "Polling interval (seconds)" + } + } + } + } +}