parent
b9abb725e7
commit
0826f7b897
|
|
@ -35,7 +35,7 @@ Elektroměr posílá DLMS/COSEM PUSH zprávy každých **60 sekund**. Integrace
|
||||||
## Instalace přes HACS
|
## Instalace přes HACS
|
||||||
|
|
||||||
1. Otevři HACS → **Integrace** → tři tečky vpravo nahoře → **Vlastní repozitáře**
|
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
|
3. Najdi „XT211 HAN" a nainstaluj
|
||||||
4. Restartuj Home Assistant
|
4. Restartuj Home Assistant
|
||||||
5. **Nastavení → Zařízení a služby → Přidat integraci → XT211 HAN**
|
5. **Nastavení → Zařízení a služby → Přidat integraci → XT211 HAN**
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,27 @@ from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
STEP_CONNECTION_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_HOST): str,
|
vol.Required(CONF_HOST): str,
|
||||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
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:
|
async def _test_connection(host: str, port: int) -> str | None:
|
||||||
"""Try to open a TCP connection. Returns error string or None on success."""
|
"""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):
|
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
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._connection_data: dict[str, Any] = {}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1 – connection (host / port / name)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
|
|
@ -56,9 +102,7 @@ class XT211HANConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
host = user_input[CONF_HOST]
|
host = user_input[CONF_HOST]
|
||||||
port = user_input[CONF_PORT]
|
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}")
|
await self.async_set_unique_id(f"{host}:{port}")
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
|
@ -66,20 +110,33 @@ class XT211HANConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
if error:
|
if error:
|
||||||
errors["base"] = error
|
errors["base"] = error
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(
|
self._connection_data = user_input
|
||||||
title=f"{name} ({host}:{port})",
|
return await self.async_step_meter()
|
||||||
data={
|
|
||||||
CONF_HOST: host,
|
|
||||||
CONF_PORT: port,
|
|
||||||
CONF_NAME: name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=STEP_USER_DATA_SCHEMA,
|
data_schema=STEP_CONNECTION_SCHEMA,
|
||||||
errors=errors,
|
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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,24 @@ DOMAIN = "xt211_han"
|
||||||
CONF_HOST = "host"
|
CONF_HOST = "host"
|
||||||
CONF_PORT = "port"
|
CONF_PORT = "port"
|
||||||
CONF_NAME = "name"
|
CONF_NAME = "name"
|
||||||
|
CONF_PHASES = "phases"
|
||||||
|
CONF_HAS_FVE = "has_fve"
|
||||||
|
CONF_TARIFFS = "tariffs"
|
||||||
|
CONF_RELAY_COUNT = "relay_count"
|
||||||
|
|
||||||
DEFAULT_PORT = 8899
|
DEFAULT_PORT = 8899
|
||||||
DEFAULT_NAME = "XT211 HAN"
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"domain": "xt211_han",
|
"domain": "xt211_han",
|
||||||
"name": "XT211 HAN (RS485 via Ethernet)",
|
"name": "XT211 HAN (RS485 via Ethernet)",
|
||||||
"version": "0.5.1",
|
"version": "0.6.0",
|
||||||
"documentation": "https://github.com/nero150/xt211-han-ha",
|
"documentation": "https://github.com/nero150/xt211-han-ha",
|
||||||
"issue_tracker": "https://github.com/nero150/xt211-han-ha/issues",
|
"issue_tracker": "https://github.com/nero150/xt211-han-ha/issues",
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,16 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
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 .coordinator import XT211Coordinator
|
||||||
from .dlms_parser import OBIS_DESCRIPTIONS
|
from .dlms_parser import OBIS_DESCRIPTIONS
|
||||||
|
|
||||||
|
|
@ -91,13 +100,70 @@ async def async_setup_entry(
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> 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]
|
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 = []
|
entities: list = []
|
||||||
registered_obis: set[str] = set()
|
registered_obis: set[str] = set()
|
||||||
|
|
||||||
for obis, meta in OBIS_DESCRIPTIONS.items():
|
for obis, meta in OBIS_DESCRIPTIONS.items():
|
||||||
|
if obis not in enabled_obis:
|
||||||
|
continue
|
||||||
registered_obis.add(obis)
|
registered_obis.add(obis)
|
||||||
if obis in BINARY_OBIS:
|
if obis in BINARY_OBIS:
|
||||||
entities.append(XT211BinarySensorEntity(coordinator, entry, obis, meta))
|
entities.append(XT211BinarySensorEntity(coordinator, entry, obis, meta))
|
||||||
|
|
@ -115,7 +181,7 @@ async def async_setup_entry(
|
||||||
return
|
return
|
||||||
new: list = []
|
new: list = []
|
||||||
for obis, data in coordinator.data.items():
|
for obis, data in coordinator.data.items():
|
||||||
if obis in registered_obis:
|
if obis in registered_obis or obis not in enabled_obis:
|
||||||
continue
|
continue
|
||||||
registered_obis.add(obis)
|
registered_obis.add(obis)
|
||||||
_LOGGER.info("XT211: discovered new OBIS code %s – adding entity", obis)
|
_LOGGER.info("XT211: discovered new OBIS code %s – adding entity", obis)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,23 @@
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"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.",
|
"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": {
|
"data": {
|
||||||
"host": "IP adresa adaptéru",
|
"host": "IP adresa adaptéru",
|
||||||
"port": "TCP port",
|
"port": "TCP port",
|
||||||
"name": "Název zařízení"
|
"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": {
|
"error": {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,23 @@
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"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.",
|
"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": {
|
"data": {
|
||||||
"host": "IP adresa adaptéru",
|
"host": "IP adresa adaptéru",
|
||||||
"port": "TCP port",
|
"port": "TCP port",
|
||||||
"name": "Název zařízení"
|
"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": {
|
"error": {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,23 @@
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"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.",
|
"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": {
|
"data": {
|
||||||
"host": "Adapter IP address",
|
"host": "Adapter IP address",
|
||||||
"port": "TCP port",
|
"port": "TCP port",
|
||||||
"name": "Device name"
|
"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": {
|
"error": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue