From 79e1f67666a6d530bbc80401e960de1d4eafe50d Mon Sep 17 00:00:00 2001 From: rhammen <75572839+rhammen@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:14:01 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(config=5Fflow):=20=F0=9F=90=9B=20explai?= =?UTF-8?q?n=20which=20entry=20an=20already-configured=20abort=20collided?= =?UTF-8?q?=20with?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split the serial lookup out of the unique-id check in `_set_unique_id_or_abort`, so the abort log names the real cause instead of the host: the previous message ("Device already configured: ") pointed at the host while the match was actually on the serial-derived unique_id, sending users hunting for a duplicate host in .storage. - Add `_abort_already_configured`, which resolves the conflicting config entry and reports it via a new `already_configured_serial` abort reason with host/serial/existing entry placeholders. Since the unique_id is the heat pump's serial number, a second endpoint aborting here means both addresses reach the same controller - typically a duplicate port forward. - Fall back to the generic `already_configured` reason when no stored entry matches the serial (the conflict then came from an in-progress flow). - Add the new string to en/de/nl/cs/pl; the generic `already_configured` string stays placeholder-free for the core-raised DHCP abort path. - The `LuxtronikSerialNumberError` handler was only reachable via the evaluation order of `coordinator.unique_id` inside the shared `try`; it now guards that lookup explicitly. Refs #724 Co-Authored-By: Claude Opus 5 --- custom_components/luxtronik2/config_flow.py | 73 ++++++++++++++++-- .../luxtronik2/translations/cs.json | 1 + .../luxtronik2/translations/de.json | 1 + .../luxtronik2/translations/en.json | 1 + .../luxtronik2/translations/nl.json | 1 + .../luxtronik2/translations/pl.json | 1 + tests/test_config_flow.py | 77 ++++++++++++++++++- 7 files changed, 144 insertions(+), 11 deletions(-) diff --git a/custom_components/luxtronik2/config_flow.py b/custom_components/luxtronik2/config_flow.py index 38b6e3a9..899a50cc 100644 --- a/custom_components/luxtronik2/config_flow.py +++ b/custom_components/luxtronik2/config_flow.py @@ -92,23 +92,80 @@ async def _set_unique_id_or_abort( Returns None if the flow should proceed to create/update the entry. """ + host = config[CONF_HOST] + port = config[CONF_PORT] try: - await self.async_set_unique_id(coordinator.unique_id) - self._abort_if_unique_id_configured() - return None - except AbortFlow: - LOGGER.debug("Device already configured: %s", config[CONF_HOST]) - return self.async_abort(reason="already_configured") + serial_unique_id = coordinator.unique_id except LuxtronikSerialNumberError as err: - LOGGER.error("Could not identify device at %s: %s", config[CONF_HOST], err) + LOGGER.error("Could not identify device at %s: %s", host, err) return self.async_abort( reason="cannot_identify", description_placeholders={ - "host": config[CONF_HOST], + "host": host, "error": str(err), }, ) + try: + await self.async_set_unique_id(serial_unique_id) + self._abort_if_unique_id_configured() + return None + except AbortFlow: + return self._abort_already_configured(serial_unique_id, host, port) + + def _abort_already_configured( + self, serial_unique_id: str, host: str, port: int + ) -> ConfigFlowResult: + """Abort with the *reason* the device counts as already configured. + + The unique ID is the heat pump's serial number, so a second endpoint + aborting here means it reported a serial that an existing entry + already uses - i.e. it is the same physical controller reached a + second way (a duplicate port forward is the usual cause, see issue + #724), not a second pump. Naming the conflicting entry turns an + otherwise unexplained "This device is already configured." into + something the user can act on. + """ + conflict = next( + ( + entry + for entry in self._async_current_entries() + if entry.unique_id == serial_unique_id + ), + None, + ) + if conflict is None: + LOGGER.debug( + "Device at %s:%s (serial '%s') is already configured", + host, + port, + serial_unique_id, + ) + return self.async_abort(reason="already_configured") + + conflict_host = conflict.data.get(CONF_HOST) + conflict_port = conflict.data.get(CONF_PORT) + LOGGER.warning( + "Device at %s:%s reports serial '%s', which is already configured as " + "'%s' (%s:%s). Both addresses reach the same heat pump - check for a " + "duplicate port forward if you expected two separate units", + host, + port, + serial_unique_id, + conflict.title, + conflict_host, + conflict_port, + ) + return self.async_abort( + reason="already_configured_serial", + description_placeholders={ + "host": f"{host}:{port}", + "serial": serial_unique_id, + "existing_host": f"{conflict_host}:{conflict_port}", + "existing_title": conflict.title, + }, + ) + def _create_entry( self, config: dict[str, Any], coordinator: LuxtronikCoordinator ) -> ConfigFlowResult: diff --git a/custom_components/luxtronik2/translations/cs.json b/custom_components/luxtronik2/translations/cs.json index 8aca8bc4..2d1dea44 100644 --- a/custom_components/luxtronik2/translations/cs.json +++ b/custom_components/luxtronik2/translations/cs.json @@ -1084,6 +1084,7 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit k Luxtronik na adrese {host}.\\nChyba: {connect_error}", "cannot_identify": "P\u0159ipojeno k za\u0159\u00edzen\u00ed na adrese {host}, ale nepoda\u0159ilo se p\u0159e\u010d\u00edst jeho s\u00e9riov\u00e9 \u010d\u00edslo pro identifikaci.\\nChyba: {error}", "already_configured": "Toto za\u0159\u00edzen\u00ed je ji\u017e nakonfigurov\u00e1no.", + "already_configured_serial": "Tepeln\u00e9 \u010derpadlo na {host} hl\u00e1s\u00ed s\u00e9riov\u00e9 \u010d\u00edslo {serial}, kter\u00e9 je ji\u017e nakonfigurov\u00e1no jako '{existing_title}' ({existing_host}). Ob\u011b adresy vedou ke stejn\u00e9mu tepeln\u00e9mu \u010derpadlu - pokud jste o\u010dek\u00e1vali dv\u011b samostatn\u00e9 jednotky, zkontrolujte duplicitn\u00ed p\u0159esm\u011brov\u00e1n\u00ed port\u016f.", "reconfigure_successful": "Konfigurace \u00fasp\u011b\u0161n\u011b aktualizov\u00e1na.", "unique_id_mismatch": "Za\u0159\u00edzen\u00ed na nov\u00e9 adrese m\u00e1 jinou identitu ne\u017e aktu\u00e1ln\u011b nakonfigurovan\u00e9 za\u0159\u00edzen\u00ed.", "already_in_progress": "Konfigura\u010dn\u00ed proces ji\u017e prob\u00edh\u00e1.", diff --git a/custom_components/luxtronik2/translations/de.json b/custom_components/luxtronik2/translations/de.json index badf7144..2ac39eb5 100644 --- a/custom_components/luxtronik2/translations/de.json +++ b/custom_components/luxtronik2/translations/de.json @@ -1084,6 +1084,7 @@ "cannot_connect": "Verbindung zu Luxtronik unter {host} fehlgeschlagen.\\nFehler: {connect_error}", "cannot_identify": "Verbindung zum Ger\u00e4t unter {host} hergestellt, aber die Seriennummer zur Identifizierung konnte nicht gelesen werden.\\nFehler: {error}", "already_configured": "Dieses Ger\u00e4t ist bereits konfiguriert.", + "already_configured_serial": "Die W\u00e4rmepumpe unter {host} meldet die Seriennummer {serial}, die bereits als '{existing_title}' ({existing_host}) konfiguriert ist. Beide Adressen erreichen dieselbe W\u00e4rmepumpe - pr\u00fcfe bei zwei erwarteten Ger\u00e4ten die Portweiterleitungen auf Duplikate.", "reconfigure_successful": "Konfiguration erfolgreich aktualisiert.", "unique_id_mismatch": "Das Ger\u00e4t an der neuen Adresse hat eine andere Identit\u00e4t als das aktuell konfigurierte Ger\u00e4t.", "already_in_progress": "Der Konfigurationsvorgang ist bereits aktiv.", diff --git a/custom_components/luxtronik2/translations/en.json b/custom_components/luxtronik2/translations/en.json index b9b93177..88022bf1 100644 --- a/custom_components/luxtronik2/translations/en.json +++ b/custom_components/luxtronik2/translations/en.json @@ -1084,6 +1084,7 @@ "cannot_connect": "Failed to connect to Luxtronik at {host}.\\nError: {connect_error}", "cannot_identify": "Connected to the device at {host}, but could not read its serial number to identify it.\\nError: {error}", "already_configured": "This device is already configured.", + "already_configured_serial": "The heat pump at {host} reports serial number {serial}, which is already configured as '{existing_title}' ({existing_host}). Both addresses reach the same heat pump - if you expected two separate units, check for a duplicate port forward.", "reconfigure_successful": "Configuration updated successfully.", "unique_id_mismatch": "The device at the new address has a different identity than the currently configured device.", "already_in_progress": "The configuration flow is already in progress.", diff --git a/custom_components/luxtronik2/translations/nl.json b/custom_components/luxtronik2/translations/nl.json index 372d751c..7601656c 100644 --- a/custom_components/luxtronik2/translations/nl.json +++ b/custom_components/luxtronik2/translations/nl.json @@ -1084,6 +1084,7 @@ "cannot_connect": "De verbinding met Luxtronik op {host} is mislukt.\nFout: {connect_error}", "cannot_identify": "Verbonden met het apparaat op {host}, maar het serienummer kon niet worden gelezen om het te identificeren.\nFout: {error}", "already_configured": "Dit apparaat is al geconfigureerd.", + "already_configured_serial": "De warmtepomp op {host} meldt serienummer {serial}, dat al is geconfigureerd als '{existing_title}' ({existing_host}). Beide adressen bereiken dezelfde warmtepomp - controleer de port forwarding op duplicaten als je twee afzonderlijke units verwachtte.", "reconfigure_successful": "Configuratie succesvol bijgewerkt.", "unique_id_mismatch": "Het apparaat op het nieuwe adres heeft een andere identiteit dan het momenteel geconfigureerde apparaat.", "already_in_progress": "De configuratiestroom is al actief.", diff --git a/custom_components/luxtronik2/translations/pl.json b/custom_components/luxtronik2/translations/pl.json index 1ac137a2..43911c4e 100644 --- a/custom_components/luxtronik2/translations/pl.json +++ b/custom_components/luxtronik2/translations/pl.json @@ -1084,6 +1084,7 @@ "cannot_connect": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z Luxtronik pod adresem {host}.\\nB\u0142\u0105d: {connect_error}", "cannot_identify": "Po\u0142\u0105czono z urz\u0105dzeniem pod adresem {host}, ale nie uda\u0142o si\u0119 odczyta\u0107 jego numeru seryjnego w celu identyfikacji.\\nB\u0142\u0105d: {error}", "already_configured": "To urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured_serial": "Pompa ciep\u0142a pod adresem {host} zg\u0142asza numer seryjny {serial}, kt\u00f3ry jest ju\u017c skonfigurowany jako '{existing_title}' ({existing_host}). Oba adresy prowadz\u0105 do tej samej pompy ciep\u0142a - je\u015bli spodziewano si\u0119 dw\u00f3ch osobnych urz\u0105dze\u0144, sprawd\u017a przekierowanie port\u00f3w pod k\u0105tem duplikat\u00f3w.", "reconfigure_successful": "Konfiguracja zosta\u0142a pomy\u015blnie zaktualizowana.", "unique_id_mismatch": "Urz\u0105dzenie pod nowym adresem ma inn\u0105 to\u017csamo\u015b\u0107 ni\u017c aktualnie skonfigurowane urz\u0105dzenie.", "already_in_progress": "Proces konfiguracji jest ju\u017c w toku.", diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index a50a2c6e..6268eace 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -112,14 +112,25 @@ def test_create_entry_preserves_existing_prefix(self): # =========================================================================== +def _mock_entry(unique_id: str, host: str, port: int, title: str = "entry"): + entry = MagicMock() + entry.unique_id = unique_id + entry.title = title + entry.data = {CONF_HOST: host, CONF_PORT: port} + return entry + + class TestSetUniqueIdOrAbort: @pytest.mark.asyncio async def test_sets_unique_id(self): flow = LuxtronikFlowHandler() flow.async_set_unique_id = AsyncMock() flow._abort_if_unique_id_configured = MagicMock() + flow._async_current_entries = MagicMock(return_value=[]) coord = _mock_coordinator() - result = await flow._set_unique_id_or_abort(coord, {CONF_HOST: "1.2.3.4"}) + result = await flow._set_unique_id_or_abort( + coord, {CONF_HOST: "1.2.3.4", CONF_PORT: 8889} + ) assert result is None flow.async_set_unique_id.assert_awaited_once_with(coord.unique_id) @@ -130,8 +141,11 @@ async def test_returns_abort_result_on_already_configured(self): flow._abort_if_unique_id_configured = MagicMock( side_effect=AbortFlow("already_configured") ) + flow._async_current_entries = MagicMock(return_value=[]) coord = _mock_coordinator() - result = await flow._set_unique_id_or_abort(coord, {CONF_HOST: "1.2.3.4"}) + result = await flow._set_unique_id_or_abort( + coord, {CONF_HOST: "1.2.3.4", CONF_PORT: 8889} + ) assert result is not None assert result["reason"] == "already_configured" @@ -143,10 +157,67 @@ async def test_returns_abort_result_on_serial_number_error(self): type(coord).unique_id = PropertyMock( side_effect=LuxtronikSerialNumberError("no serial number") ) - result = await flow._set_unique_id_or_abort(coord, {CONF_HOST: "1.2.3.4"}) + result = await flow._set_unique_id_or_abort( + coord, {CONF_HOST: "1.2.3.4", CONF_PORT: 8889} + ) assert result is not None assert result["reason"] == "cannot_identify" + @pytest.mark.asyncio + async def test_serial_conflict_abort_names_the_existing_entry(self): + """Issue #724: a second endpoint whose serial is already configured is + the same pump reached twice, so the abort must say which entry it + collided with instead of the bare 'already configured'.""" + flow = LuxtronikFlowHandler() + flow.async_set_unique_id = AsyncMock() + flow._abort_if_unique_id_configured = MagicMock( + side_effect=AbortFlow("already_configured") + ) + coord = _mock_coordinator() + flow._async_current_entries = MagicMock( + return_value=[ + _mock_entry( + coord.unique_id, + "name1.mooo.com", + 8452, + title="Luxtronik @ name1.mooo.com:8452", + ) + ] + ) + + result = await flow._set_unique_id_or_abort( + coord, {CONF_HOST: "name1.mooo.com", CONF_PORT: 8450} + ) + + assert result is not None + assert result["reason"] == "already_configured_serial" + placeholders = result["description_placeholders"] + assert placeholders["host"] == "name1.mooo.com:8450" + assert placeholders["serial"] == coord.unique_id + assert placeholders["existing_host"] == "name1.mooo.com:8452" + assert placeholders["existing_title"] == "Luxtronik @ name1.mooo.com:8452" + + @pytest.mark.asyncio + async def test_abort_falls_back_when_no_entry_matches_the_serial(self): + """The conflict can come from an in-progress flow rather than an entry; + without a matching entry there is nothing specific to report.""" + flow = LuxtronikFlowHandler() + flow.async_set_unique_id = AsyncMock() + flow._abort_if_unique_id_configured = MagicMock( + side_effect=AbortFlow("already_configured") + ) + flow._async_current_entries = MagicMock( + return_value=[_mock_entry("some_other_serial", "1.2.3.4", 8889)] + ) + coord = _mock_coordinator() + + result = await flow._set_unique_id_or_abort( + coord, {CONF_HOST: "5.6.7.8", CONF_PORT: 8889} + ) + + assert result is not None + assert result["reason"] == "already_configured" + # =========================================================================== # Instance isolation (I8) From 4916fc0dd77f20578f2a530dea45e6d8cac31baa Mon Sep 17 00:00:00 2001 From: rhammen <75572839+rhammen@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:22:49 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(translations):=20=F0=9F=90=9B=20unquote?= =?UTF-8?q?=20the=20entry-name=20placeholder=20in=20already=5Fconfigured?= =?UTF-8?q?=5Fserial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hassfest rejects placeholders wrapped in single quotes ("the string should not contain placeholders inside single quotes"), which failed CI on '{existing_title}'. - Drop the surrounding quotes in en/de/nl/cs/pl; the entry title still reads clearly followed by its address in parentheses. Co-Authored-By: Claude Opus 5 --- custom_components/luxtronik2/translations/cs.json | 2 +- custom_components/luxtronik2/translations/de.json | 2 +- custom_components/luxtronik2/translations/en.json | 2 +- custom_components/luxtronik2/translations/nl.json | 2 +- custom_components/luxtronik2/translations/pl.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/luxtronik2/translations/cs.json b/custom_components/luxtronik2/translations/cs.json index 2d1dea44..86176410 100644 --- a/custom_components/luxtronik2/translations/cs.json +++ b/custom_components/luxtronik2/translations/cs.json @@ -1084,7 +1084,7 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit k Luxtronik na adrese {host}.\\nChyba: {connect_error}", "cannot_identify": "P\u0159ipojeno k za\u0159\u00edzen\u00ed na adrese {host}, ale nepoda\u0159ilo se p\u0159e\u010d\u00edst jeho s\u00e9riov\u00e9 \u010d\u00edslo pro identifikaci.\\nChyba: {error}", "already_configured": "Toto za\u0159\u00edzen\u00ed je ji\u017e nakonfigurov\u00e1no.", - "already_configured_serial": "Tepeln\u00e9 \u010derpadlo na {host} hl\u00e1s\u00ed s\u00e9riov\u00e9 \u010d\u00edslo {serial}, kter\u00e9 je ji\u017e nakonfigurov\u00e1no jako '{existing_title}' ({existing_host}). Ob\u011b adresy vedou ke stejn\u00e9mu tepeln\u00e9mu \u010derpadlu - pokud jste o\u010dek\u00e1vali dv\u011b samostatn\u00e9 jednotky, zkontrolujte duplicitn\u00ed p\u0159esm\u011brov\u00e1n\u00ed port\u016f.", + "already_configured_serial": "Tepeln\u00e9 \u010derpadlo na {host} hl\u00e1s\u00ed s\u00e9riov\u00e9 \u010d\u00edslo {serial}, kter\u00e9 je ji\u017e nakonfigurov\u00e1no jako {existing_title} ({existing_host}). Ob\u011b adresy vedou ke stejn\u00e9mu tepeln\u00e9mu \u010derpadlu - pokud jste o\u010dek\u00e1vali dv\u011b samostatn\u00e9 jednotky, zkontrolujte duplicitn\u00ed p\u0159esm\u011brov\u00e1n\u00ed port\u016f.", "reconfigure_successful": "Konfigurace \u00fasp\u011b\u0161n\u011b aktualizov\u00e1na.", "unique_id_mismatch": "Za\u0159\u00edzen\u00ed na nov\u00e9 adrese m\u00e1 jinou identitu ne\u017e aktu\u00e1ln\u011b nakonfigurovan\u00e9 za\u0159\u00edzen\u00ed.", "already_in_progress": "Konfigura\u010dn\u00ed proces ji\u017e prob\u00edh\u00e1.", diff --git a/custom_components/luxtronik2/translations/de.json b/custom_components/luxtronik2/translations/de.json index 2ac39eb5..31a06aad 100644 --- a/custom_components/luxtronik2/translations/de.json +++ b/custom_components/luxtronik2/translations/de.json @@ -1084,7 +1084,7 @@ "cannot_connect": "Verbindung zu Luxtronik unter {host} fehlgeschlagen.\\nFehler: {connect_error}", "cannot_identify": "Verbindung zum Ger\u00e4t unter {host} hergestellt, aber die Seriennummer zur Identifizierung konnte nicht gelesen werden.\\nFehler: {error}", "already_configured": "Dieses Ger\u00e4t ist bereits konfiguriert.", - "already_configured_serial": "Die W\u00e4rmepumpe unter {host} meldet die Seriennummer {serial}, die bereits als '{existing_title}' ({existing_host}) konfiguriert ist. Beide Adressen erreichen dieselbe W\u00e4rmepumpe - pr\u00fcfe bei zwei erwarteten Ger\u00e4ten die Portweiterleitungen auf Duplikate.", + "already_configured_serial": "Die W\u00e4rmepumpe unter {host} meldet die Seriennummer {serial}, die bereits als {existing_title} ({existing_host}) konfiguriert ist. Beide Adressen erreichen dieselbe W\u00e4rmepumpe - pr\u00fcfe bei zwei erwarteten Ger\u00e4ten die Portweiterleitungen auf Duplikate.", "reconfigure_successful": "Konfiguration erfolgreich aktualisiert.", "unique_id_mismatch": "Das Ger\u00e4t an der neuen Adresse hat eine andere Identit\u00e4t als das aktuell konfigurierte Ger\u00e4t.", "already_in_progress": "Der Konfigurationsvorgang ist bereits aktiv.", diff --git a/custom_components/luxtronik2/translations/en.json b/custom_components/luxtronik2/translations/en.json index 88022bf1..82c43492 100644 --- a/custom_components/luxtronik2/translations/en.json +++ b/custom_components/luxtronik2/translations/en.json @@ -1084,7 +1084,7 @@ "cannot_connect": "Failed to connect to Luxtronik at {host}.\\nError: {connect_error}", "cannot_identify": "Connected to the device at {host}, but could not read its serial number to identify it.\\nError: {error}", "already_configured": "This device is already configured.", - "already_configured_serial": "The heat pump at {host} reports serial number {serial}, which is already configured as '{existing_title}' ({existing_host}). Both addresses reach the same heat pump - if you expected two separate units, check for a duplicate port forward.", + "already_configured_serial": "The heat pump at {host} reports serial number {serial}, which is already configured as {existing_title} ({existing_host}). Both addresses reach the same heat pump - if you expected two separate units, check for a duplicate port forward.", "reconfigure_successful": "Configuration updated successfully.", "unique_id_mismatch": "The device at the new address has a different identity than the currently configured device.", "already_in_progress": "The configuration flow is already in progress.", diff --git a/custom_components/luxtronik2/translations/nl.json b/custom_components/luxtronik2/translations/nl.json index 7601656c..9d04bd51 100644 --- a/custom_components/luxtronik2/translations/nl.json +++ b/custom_components/luxtronik2/translations/nl.json @@ -1084,7 +1084,7 @@ "cannot_connect": "De verbinding met Luxtronik op {host} is mislukt.\nFout: {connect_error}", "cannot_identify": "Verbonden met het apparaat op {host}, maar het serienummer kon niet worden gelezen om het te identificeren.\nFout: {error}", "already_configured": "Dit apparaat is al geconfigureerd.", - "already_configured_serial": "De warmtepomp op {host} meldt serienummer {serial}, dat al is geconfigureerd als '{existing_title}' ({existing_host}). Beide adressen bereiken dezelfde warmtepomp - controleer de port forwarding op duplicaten als je twee afzonderlijke units verwachtte.", + "already_configured_serial": "De warmtepomp op {host} meldt serienummer {serial}, dat al is geconfigureerd als {existing_title} ({existing_host}). Beide adressen bereiken dezelfde warmtepomp - controleer de port forwarding op duplicaten als je twee afzonderlijke units verwachtte.", "reconfigure_successful": "Configuratie succesvol bijgewerkt.", "unique_id_mismatch": "Het apparaat op het nieuwe adres heeft een andere identiteit dan het momenteel geconfigureerde apparaat.", "already_in_progress": "De configuratiestroom is al actief.", diff --git a/custom_components/luxtronik2/translations/pl.json b/custom_components/luxtronik2/translations/pl.json index 43911c4e..eb638fdd 100644 --- a/custom_components/luxtronik2/translations/pl.json +++ b/custom_components/luxtronik2/translations/pl.json @@ -1084,7 +1084,7 @@ "cannot_connect": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z Luxtronik pod adresem {host}.\\nB\u0142\u0105d: {connect_error}", "cannot_identify": "Po\u0142\u0105czono z urz\u0105dzeniem pod adresem {host}, ale nie uda\u0142o si\u0119 odczyta\u0107 jego numeru seryjnego w celu identyfikacji.\\nB\u0142\u0105d: {error}", "already_configured": "To urz\u0105dzenie jest ju\u017c skonfigurowane.", - "already_configured_serial": "Pompa ciep\u0142a pod adresem {host} zg\u0142asza numer seryjny {serial}, kt\u00f3ry jest ju\u017c skonfigurowany jako '{existing_title}' ({existing_host}). Oba adresy prowadz\u0105 do tej samej pompy ciep\u0142a - je\u015bli spodziewano si\u0119 dw\u00f3ch osobnych urz\u0105dze\u0144, sprawd\u017a przekierowanie port\u00f3w pod k\u0105tem duplikat\u00f3w.", + "already_configured_serial": "Pompa ciep\u0142a pod adresem {host} zg\u0142asza numer seryjny {serial}, kt\u00f3ry jest ju\u017c skonfigurowany jako {existing_title} ({existing_host}). Oba adresy prowadz\u0105 do tej samej pompy ciep\u0142a - je\u015bli spodziewano si\u0119 dw\u00f3ch osobnych urz\u0105dze\u0144, sprawd\u017a przekierowanie port\u00f3w pod k\u0105tem duplikat\u00f3w.", "reconfigure_successful": "Konfiguracja zosta\u0142a pomy\u015blnie zaktualizowana.", "unique_id_mismatch": "Urz\u0105dzenie pod nowym adresem ma inn\u0105 to\u017csamo\u015b\u0107 ni\u017c aktualnie skonfigurowane urz\u0105dzenie.", "already_in_progress": "Proces konfiguracji jest ju\u017c w toku.",