diff --git a/README.md b/README.md index 9664cf1..1d9e734 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Elektroměr posílá DLMS/COSEM PUSH zprávy každých **60 sekund**. Integrace ## Instalace přes HACS 1. Otevři HACS → **Integrace** → tři tečky vpravo nahoře → **Vlastní repozitáře** -2. Přidej URL tohoto repozitáře, kategorie: **Integration** (https://github.com/nero150/CEZ_rele_box) +2. Přidej URL tohoto repozitáře, kategorie: **Integration** 3. Najdi „XT211 HAN" a nainstaluj 4. Restartuj Home Assistant 5. **Nastavení → Zařízení a služby → Přidat integraci → XT211 HAN** diff --git a/custom_components/xt211_han/config_flow.py b/custom_components/xt211_han/config_flow.py index 859c47c..5acd895 100644 --- a/custom_components/xt211_han/config_flow.py +++ b/custom_components/xt211_han/config_flow.py @@ -12,11 +12,27 @@ from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME from homeassistant.data_entry_flow import FlowResult -from .const import DOMAIN, DEFAULT_PORT, DEFAULT_NAME +from .const import ( + DOMAIN, + DEFAULT_PORT, + DEFAULT_NAME, + CONF_PHASES, + CONF_HAS_FVE, + CONF_TARIFFS, + CONF_RELAY_COUNT, + PHASES_1, + PHASES_3, + TARIFFS_1, + TARIFFS_2, + TARIFFS_4, + RELAYS_0, + RELAYS_4, + RELAYS_6, +) _LOGGER = logging.getLogger(__name__) -STEP_USER_DATA_SCHEMA = vol.Schema( +STEP_CONNECTION_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): str, vol.Required(CONF_PORT, default=DEFAULT_PORT): int, @@ -24,6 +40,29 @@ STEP_USER_DATA_SCHEMA = vol.Schema( } ) +STEP_METER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PHASES, default=PHASES_3): vol.In( + {PHASES_1: "Jednofázový", PHASES_3: "Třífázový"} + ), + vol.Required(CONF_HAS_FVE, default=False): bool, + vol.Required(CONF_TARIFFS, default=TARIFFS_2): vol.In( + { + TARIFFS_1: "Jeden tarif (pouze T1)", + TARIFFS_2: "Dva tarify (T1 + T2)", + TARIFFS_4: "Čtyři tarify (T1 – T4)", + } + ), + vol.Required(CONF_RELAY_COUNT, default=RELAYS_4): vol.In( + { + RELAYS_0: "Žádné relé", + RELAYS_4: "WM-RelayBox (R1 – R4)", + RELAYS_6: "WM-RelayBox rozšířený (R1 – R6)", + } + ), + } +) + async def _test_connection(host: str, port: int) -> str | None: """Try to open a TCP connection. Returns error string or None on success.""" @@ -44,10 +83,17 @@ async def _test_connection(host: str, port: int) -> str | None: class XT211HANConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle the config flow for XT211 HAN.""" + """Handle the config flow for XT211 HAN – two steps.""" VERSION = 1 + def __init__(self) -> None: + self._connection_data: dict[str, Any] = {} + + # ------------------------------------------------------------------ + # Step 1 – connection (host / port / name) + # ------------------------------------------------------------------ + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -56,9 +102,7 @@ class XT211HANConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: host = user_input[CONF_HOST] port = user_input[CONF_PORT] - name = user_input.get(CONF_NAME, DEFAULT_NAME) - # Prevent duplicate entries await self.async_set_unique_id(f"{host}:{port}") self._abort_if_unique_id_configured() @@ -66,20 +110,33 @@ class XT211HANConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if error: errors["base"] = error else: - return self.async_create_entry( - title=f"{name} ({host}:{port})", - data={ - CONF_HOST: host, - CONF_PORT: port, - CONF_NAME: name, - }, - ) + self._connection_data = user_input + return await self.async_step_meter() return self.async_show_form( step_id="user", - data_schema=STEP_USER_DATA_SCHEMA, + data_schema=STEP_CONNECTION_SCHEMA, errors=errors, - description_placeholders={ - "default_port": str(DEFAULT_PORT), - }, + ) + + # ------------------------------------------------------------------ + # Step 2 – meter configuration + # ------------------------------------------------------------------ + + async def async_step_meter( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + if user_input is not None: + data = {**self._connection_data, **user_input} + name = data.get(CONF_NAME, DEFAULT_NAME) + host = data[CONF_HOST] + port = data[CONF_PORT] + return self.async_create_entry( + title=f"{name} ({host}:{port})", + data=data, + ) + + return self.async_show_form( + step_id="meter", + data_schema=STEP_METER_SCHEMA, ) diff --git a/custom_components/xt211_han/const.py b/custom_components/xt211_han/const.py index 46255bc..40a0f25 100644 --- a/custom_components/xt211_han/const.py +++ b/custom_components/xt211_han/const.py @@ -5,6 +5,24 @@ DOMAIN = "xt211_han" CONF_HOST = "host" CONF_PORT = "port" CONF_NAME = "name" +CONF_PHASES = "phases" +CONF_HAS_FVE = "has_fve" +CONF_TARIFFS = "tariffs" +CONF_RELAY_COUNT = "relay_count" DEFAULT_PORT = 8899 DEFAULT_NAME = "XT211 HAN" + +# Phases +PHASES_1 = "1" +PHASES_3 = "3" + +# Tariff counts +TARIFFS_1 = 1 +TARIFFS_2 = 2 +TARIFFS_4 = 4 + +# Relay counts +RELAYS_0 = 0 +RELAYS_4 = 4 +RELAYS_6 = 6 diff --git a/custom_components/xt211_han/manifest.json b/custom_components/xt211_han/manifest.json index 4b6dad4..26ab4d1 100644 --- a/custom_components/xt211_han/manifest.json +++ b/custom_components/xt211_han/manifest.json @@ -1,7 +1,7 @@ { "domain": "xt211_han", "name": "XT211 HAN (RS485 via Ethernet)", - "version": "0.5.1", + "version": "0.6.0", "documentation": "https://github.com/nero150/xt211-han-ha", "issue_tracker": "https://github.com/nero150/xt211-han-ha/issues", "dependencies": [], diff --git a/custom_components/xt211_han/sensor.py b/custom_components/xt211_han/sensor.py index 6967cd1..499c4bb 100644 --- a/custom_components/xt211_han/sensor.py +++ b/custom_components/xt211_han/sensor.py @@ -32,7 +32,16 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import ( + DOMAIN, + CONF_PHASES, + CONF_HAS_FVE, + CONF_TARIFFS, + CONF_RELAY_COUNT, + PHASES_3, + TARIFFS_2, + RELAYS_4, +) from .coordinator import XT211Coordinator from .dlms_parser import OBIS_DESCRIPTIONS @@ -91,13 +100,70 @@ async def async_setup_entry( entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up all XT211 HAN entities from a config entry.""" + """Set up all XT211 HAN entities from a config entry, filtered by meter config.""" coordinator: XT211Coordinator = hass.data[DOMAIN][entry.entry_id] + phases = entry.data.get(CONF_PHASES, PHASES_3) + has_fve = entry.data.get(CONF_HAS_FVE, True) + tariffs = int(entry.data.get(CONF_TARIFFS, TARIFFS_2)) + relay_count = int(entry.data.get(CONF_RELAY_COUNT, RELAYS_4)) + + # Build set of OBIS codes to include based on user config + enabled_obis: set[str] = set() + + # Always include: device name, serial, tariff, consumer message, disconnector, limiter + enabled_obis.update({ + "0-0:42.0.0.255", + "0-0:96.1.0.255", + "0-0:96.14.0.255", + "0-0:96.13.0.255", + "0-0:96.3.10.255", + "0-0:17.0.0.255", + }) + + # Relays – according to relay_count + relay_obis = { + 1: "0-1:96.3.10.255", + 2: "0-2:96.3.10.255", + 3: "0-3:96.3.10.255", + 4: "0-4:96.3.10.255", + 5: "0-5:96.3.10.255", + 6: "0-6:96.3.10.255", + } + for i in range(1, relay_count + 1): + enabled_obis.add(relay_obis[i]) + + # Instant power import – total always included + enabled_obis.add("1-0:1.7.0.255") + if phases == PHASES_3: + enabled_obis.update({"1-0:21.7.0.255", "1-0:41.7.0.255", "1-0:61.7.0.255"}) + + # Instant power export – only with FVE + if has_fve: + enabled_obis.add("1-0:2.7.0.255") + if phases == PHASES_3: + enabled_obis.update({"1-0:22.7.0.255", "1-0:42.7.0.255", "1-0:62.7.0.255"}) + + # Cumulative energy import – total + tariffs + enabled_obis.add("1-0:1.8.0.255") + for t in range(1, tariffs + 1): + enabled_obis.add(f"1-0:1.8.{t}.255") + + # Cumulative energy export – only with FVE + if has_fve: + enabled_obis.add("1-0:2.8.0.255") + + _LOGGER.debug( + "XT211 config: phases=%s fve=%s tariffs=%d relays=%d → %d entities", + phases, has_fve, tariffs, relay_count, len(enabled_obis), + ) + entities: list = [] registered_obis: set[str] = set() for obis, meta in OBIS_DESCRIPTIONS.items(): + if obis not in enabled_obis: + continue registered_obis.add(obis) if obis in BINARY_OBIS: entities.append(XT211BinarySensorEntity(coordinator, entry, obis, meta)) @@ -115,7 +181,7 @@ async def async_setup_entry( return new: list = [] for obis, data in coordinator.data.items(): - if obis in registered_obis: + if obis in registered_obis or obis not in enabled_obis: continue registered_obis.add(obis) _LOGGER.info("XT211: discovered new OBIS code %s – adding entity", obis) diff --git a/custom_components/xt211_han/strings.json b/custom_components/xt211_han/strings.json index 138db1a..f23079d 100644 --- a/custom_components/xt211_han/strings.json +++ b/custom_components/xt211_han/strings.json @@ -2,13 +2,23 @@ "config": { "step": { "user": { - "title": "Nastavení XT211 HAN adaptéru", + "title": "Nastavení připojení", "description": "Zadej IP adresu a port RS485-to-Ethernet adaptéru (např. PUSR USR-DR134). Výchozí port pro TCP server mód je 8899.", "data": { "host": "IP adresa adaptéru", "port": "TCP port", "name": "Název zařízení" } + }, + "meter": { + "title": "Konfigurace elektroměru", + "description": "Upřesni parametry tvého elektroměru. Podle toho se zobrazí jen relevantní entity.", + "data": { + "phases": "Typ elektroměru", + "has_fve": "Mám fotovoltaiku (FVE) / export energie", + "tariffs": "Počet tarifů", + "relay_count": "Relé boxy (WM-RelayBox)" + } } }, "error": { diff --git a/custom_components/xt211_han/translations/cs.json b/custom_components/xt211_han/translations/cs.json index 138db1a..f23079d 100644 --- a/custom_components/xt211_han/translations/cs.json +++ b/custom_components/xt211_han/translations/cs.json @@ -2,13 +2,23 @@ "config": { "step": { "user": { - "title": "Nastavení XT211 HAN adaptéru", + "title": "Nastavení připojení", "description": "Zadej IP adresu a port RS485-to-Ethernet adaptéru (např. PUSR USR-DR134). Výchozí port pro TCP server mód je 8899.", "data": { "host": "IP adresa adaptéru", "port": "TCP port", "name": "Název zařízení" } + }, + "meter": { + "title": "Konfigurace elektroměru", + "description": "Upřesni parametry tvého elektroměru. Podle toho se zobrazí jen relevantní entity.", + "data": { + "phases": "Typ elektroměru", + "has_fve": "Mám fotovoltaiku (FVE) / export energie", + "tariffs": "Počet tarifů", + "relay_count": "Relé boxy (WM-RelayBox)" + } } }, "error": { diff --git a/custom_components/xt211_han/translations/en.json b/custom_components/xt211_han/translations/en.json index 1b3fffe..00a4dd5 100644 --- a/custom_components/xt211_han/translations/en.json +++ b/custom_components/xt211_han/translations/en.json @@ -2,13 +2,23 @@ "config": { "step": { "user": { - "title": "XT211 HAN Adapter Setup", + "title": "Connection Setup", "description": "Enter the IP address and port of your RS485-to-Ethernet adapter (e.g. PUSR USR-DR134). The default TCP server port is 8899.", "data": { "host": "Adapter IP address", "port": "TCP port", "name": "Device name" } + }, + "meter": { + "title": "Meter Configuration", + "description": "Specify your meter parameters. Only relevant entities will be shown.", + "data": { + "phases": "Meter type", + "has_fve": "I have solar panels (FVE) / energy export", + "tariffs": "Number of tariffs", + "relay_count": "Relay boxes (WM-RelayBox)" + } } }, "error": {