Add files via upload

main 0.6.0
nero150 2026-03-18 10:20:26 +01:00 committed by GitHub
parent b9abb725e7
commit 0826f7b897
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 196 additions and 25 deletions

View File

@ -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**

View File

@ -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,
)

View File

@ -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

View File

@ -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": [],

View File

@ -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)

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {