Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 94 additions & 12 deletions custom_components/victron_mqtt/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
CannotConnectError,
DeviceType,
Hub as VictronVenusHub,
OperationMode
OperationMode,
PairingError,
PairingToken,
request_pairing_token,
)
import voluptuous as vol

Expand Down Expand Up @@ -62,6 +65,7 @@

TO_REDACT = {CONF_USERNAME, CONF_PASSWORD}


ENTRY_TITLE_FORMAT = "Victron OS {installation_id} ({host}:{port})"
DEFAULT_SSL_PORT = 8883

Expand Down Expand Up @@ -148,9 +152,8 @@ def default_port_for(use_ssl: bool) -> int:

STEP_SSDP_AUTH_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_USERNAME, default=""): str,
vol.Optional(CONF_PASSWORD, default=""): str,
vol.Optional(CONF_SSL, default=False): bool,
vol.Optional(CONF_SSL, default=True): bool,
}
)

Expand Down Expand Up @@ -200,6 +203,7 @@ def __init__(self) -> None:
self.installation_id: str | None = None
self.friendly_name: str | None = None
self.model_name: str | None = None
self.mqtt_token_pairing: bool = False

async def async_step_user(
self, user_input: dict[str, Any] | None = None
Expand Down Expand Up @@ -327,13 +331,15 @@ async def async_step_ssdp(
self.installation_id = discovery_info.upnp["X_VrmPortalId"]
self.model_name = discovery_info.upnp["modelName"]
self.friendly_name = discovery_info.upnp["friendlyName"]
self.mqtt_token_pairing = discovery_info.upnp.get("X_MqttTokenPairing") == "1"
_LOGGER.debug(
"SSDP: hostname=%s, serial=%s, installation_id=%s, model_name=%s, friendly_name=%s",
"SSDP: hostname=%s, serial=%s, installation_id=%s, model_name=%s, friendly_name=%s, mqtt_token_pairing=%s",
self.hostname,
self.serial,
self.installation_id,
self.model_name,
self.friendly_name,
self.mqtt_token_pairing,
)

await self.async_set_unique_id(self.installation_id)
Expand Down Expand Up @@ -361,19 +367,38 @@ async def async_step_ssdp_confirm(
if user_input is not None:
data: dict[str, Any] = {
CONF_HOST: self.hostname,
CONF_PORT: DEFAULT_PORT,
CONF_PORT: DEFAULT_SSL_PORT,
CONF_SERIAL: self.serial,
CONF_INSTALLATION_ID: self.installation_id,
CONF_MODEL: self.model_name,
CONF_SSL: False,
CONF_SSL: True,
CONF_SIMPLE_NAMING: DEFAULT_SIMPLE_NAMING,
}
try:
await validate_input(data)
except AuthenticationError:
if self.mqtt_token_pairing:
return await self.async_step_ssdp_token_pairing()
return await self.async_step_ssdp_auth()
except CannotConnectError:
return self.async_abort(reason="cannot_connect")
# SSL failed, fall back to plain MQTT on port 1883
data[CONF_PORT] = DEFAULT_PORT
data[CONF_SSL] = False
try:
await validate_input(data)
except AuthenticationError:
if self.mqtt_token_pairing:
return await self.async_step_ssdp_token_pairing()
return await self.async_step_ssdp_auth()
except CannotConnectError:
if self.mqtt_token_pairing:
return await self.async_step_ssdp_token_pairing()
return self.async_abort(reason="cannot_connect")
except Exception:
_LOGGER.exception(
"Unexpected error validating SSDP discovery for Victron MQTT"
)
return self.async_abort(reason="unknown")
except Exception:
_LOGGER.exception(
"Unexpected error validating SSDP discovery for Victron MQTT"
Expand All @@ -384,7 +409,7 @@ async def async_step_ssdp_confirm(
title=build_title(
self.installation_id,
self.hostname,
DEFAULT_PORT,
data[CONF_PORT],
self.friendly_name,
),
data=data,
Expand All @@ -403,6 +428,64 @@ async def async_step_ssdp_confirm(
},
)

async def async_step_ssdp_token_pairing(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle automatic token pairing with the GX device."""
assert self.hostname is not None
assert self.installation_id is not None

errors: dict[str, str] = {}

if user_input is not None:
try:
credentials: PairingToken = await request_pairing_token(
self.hostname, self.installation_id
)
except PairingError:
errors["base"] = "pairing_failed"
except Exception:
_LOGGER.exception("Failed to connect to GX device for token pairing")
errors["base"] = "cannot_connect"
else:
data: dict[str, Any] = {
CONF_HOST: self.hostname,
CONF_PORT: DEFAULT_SSL_PORT,
CONF_SERIAL: self.serial,
CONF_INSTALLATION_ID: self.installation_id,
CONF_MODEL: self.model_name,
CONF_USERNAME: credentials.token_name,
CONF_PASSWORD: credentials.password,
CONF_SSL: True,
CONF_SIMPLE_NAMING: DEFAULT_SIMPLE_NAMING,
}
try:
await validate_input(data)
except AuthenticationError:
errors["base"] = "invalid_auth"
except CannotConnectError:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected error validating paired credentials")
errors["base"] = "unknown"
else:
return self.async_create_entry(
title=build_title(
self.installation_id,
self.hostname,
DEFAULT_SSL_PORT,
self.friendly_name,
),
data=data,
)

self._set_confirm_only()
return self.async_show_form(
step_id="ssdp_token_pairing",
errors=errors,
description_placeholders={CONF_HOST: self.hostname},
)

async def async_step_ssdp_auth(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand All @@ -419,16 +502,15 @@ async def async_step_ssdp_auth(
)
data: dict[str, Any] = {
CONF_HOST: self.hostname,
CONF_PORT: default_port_for(user_input.get(CONF_SSL, False)),
CONF_PORT: default_port_for(user_input.get(CONF_SSL, True)),
CONF_SERIAL: self.serial,
CONF_INSTALLATION_ID: self.installation_id,
CONF_MODEL: self.model_name,
CONF_USERNAME: user_input.get(CONF_USERNAME) or None,
CONF_USERNAME: "remoteconsole",
CONF_PASSWORD: user_input.get(CONF_PASSWORD) or None,
CONF_SSL: user_input.get(CONF_SSL, False),
CONF_SSL: user_input.get(CONF_SSL, True),
CONF_SIMPLE_NAMING: DEFAULT_SIMPLE_NAMING,
}

try:
await validate_input(data)
except AuthenticationError:
Expand Down
4 changes: 4 additions & 0 deletions custom_components/victron_mqtt/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
{
"manufacturer": "Victron Energy",
"X_MqttOnLan": "1"
},
{
"manufacturer": "Victron Energy",
"X_MqttTokenPairing": "1"
}
],
"version": "2026.8.1"
Expand Down
10 changes: 7 additions & 3 deletions custom_components/victron_mqtt/translations/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"error": {
"cannot_connect": "No s'ha pogut connectar a l'amfitrió",
"invalid_auth": "Problema d'autenticació",
"pairing_failed": "L'emparellament ha fallat. Assegureu-vos que el mode d'emparellament estigui actiu al dispositiu GX i torneu-ho a provar.",
"unknown": "Error desconegut"
},
"step": {
Expand Down Expand Up @@ -35,11 +36,14 @@
},
"ssdp_auth": {
"data": {
"username": "Nom d'usuari",
"password": "Contrasenya",
"password": "Contrasenya GX",
"ssl": "Usa SSL"
},
"description": "Es requereix autenticació per a {host}."
"description": "Introduïu la **contrasenya GX** del dispositiu GX a {host}."
},
"ssdp_token_pairing": {
"title": "Emparellar amb el dispositiu GX",
"description": "Activeu el mode d'emparellament al dispositiu GX abans de prémer Enviar:\n\n- A la interfície del GX: **Configuració** > **Integracions** > **Dispositius MQTT** > **Mode d'emparellament**\n- En dispositius GX sense pantalla integrada: Premeu ràpidament dues vegades el botó integrat\n\nEl mode d'emparellament està actiu durant 120 segons."
}
}
},
Expand Down
10 changes: 7 additions & 3 deletions custom_components/victron_mqtt/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"error": {
"cannot_connect": "Verbindung zum Host ist fehlgeschlagen",
"invalid_auth": "Authentifizierungsfehler",
"pairing_failed": "Token-Pairing fehlgeschlagen. Stellen Sie sicher, dass der Pairing-Modus auf dem GX-Gerät aktiv ist, und versuchen Sie es erneut.",
"unknown": "Unbekannter Fehler"
},
"step": {
Expand Down Expand Up @@ -35,11 +36,14 @@
},
"ssdp_auth": {
"data": {
"username": "Benutzername",
"password": "Passwort",
"password": "GX-Passwort",
"ssl": "SSL verwenden"
},
"description": "Authentifizierung ist für {host} erforderlich."
"description": "Geben Sie das **GX-Passwort** für das GX-Gerät unter {host} ein."
},
"ssdp_token_pairing": {
"title": "Mit GX-Gerät koppeln",
"description": "Aktivieren Sie den Pairing-Modus auf dem GX-Gerät, bevor Sie auf Absenden klicken:\n\n- Über die GX-Benutzeroberfläche: **Einstellungen** > **Integrationen** > **MQTT-Geräte** > **Pairing-Modus**\n- Bei GX-Geräten ohne eingebauten Bildschirm: Drücken Sie schnell zweimal die eingebaute Taste\n\nDer Pairing-Modus ist 120 Sekunden lang aktiv."
}
}
},
Expand Down
12 changes: 8 additions & 4 deletions custom_components/victron_mqtt/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@
"error": {
"cannot_connect": "Failed to connect to host",
"invalid_auth": "Authentication issue",
"pairing_failed": "Token pairing failed. Ensure pairing mode is active on the GX device and try again.",
"unknown": "Unknown failure"
},
"step": {
"ssdp_auth": {
"data": {
"password": "Password",
"ssl": "Use SSL",
"username": "Username"
"password": "GX Password",
"ssl": "Use SSL"
},
"description": "Authentication is required for {host}."
"description": "Enter the **GX Password** for the GX device at {host}."
},
"ssdp_confirm": {
"description": "Do you want to set up {name}?"
},
"ssdp_token_pairing": {
"title": "Pair with GX device",
"description": "Enable pairing mode on the GX device before pressing Submit:\n\n- Via GX user interface: **Settings** > **Integrations** > **MQTT Devices** > **Pairing mode**\n- On GX devices without a built-in screen: Quickly double-press the built-in button\n\nPairing mode is active for 120 seconds."
},
"user": {
"data": {
"elevated_tracing": "Elevate tracing for topic",
Expand Down
10 changes: 7 additions & 3 deletions custom_components/victron_mqtt/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"error": {
"cannot_connect": "Échec de la connexion à l'hôte",
"invalid_auth": "Problème d'authentification",
"pairing_failed": "L'appairage a échoué. Assurez-vous que le mode d'appairage est actif sur l'appareil GX et réessayez.",
"unknown": "Échec inconnu"
},
"step": {
Expand Down Expand Up @@ -35,11 +36,14 @@
},
"ssdp_auth": {
"data": {
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"password": "Mot de passe GX",
"ssl": "Utiliser SSL"
},
"description": "Une authentification est requise pour {host}."
"description": "Entrez le **mot de passe GX** de l'appareil GX à {host}."
},
"ssdp_token_pairing": {
"title": "Appairer avec l'appareil GX",
"description": "Activez le mode d'appairage sur l'appareil GX avant de cliquer sur Envoyer :\n\n- Via l'interface du GX : **Paramètres** > **Intégrations** > **Appareils MQTT** > **Mode d'appairage**\n- Sur les appareils GX sans écran intégré : Appuyez rapidement deux fois sur le bouton intégré\n\nLe mode d'appairage est actif pendant 120 secondes."
}
}
},
Expand Down
10 changes: 7 additions & 3 deletions custom_components/victron_mqtt/translations/sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"error": {
"cannot_connect": "Nepodarilo sa pripojiť k hostiteľovi",
"invalid_auth": "Problém s autentifikáciou",
"pairing_failed": "Párovanie zlyhalo. Uistite sa, že je režim párovania na zariadení GX aktívny, a skúste to znova.",
"unknown": "Neznáma chyba"
},
"step": {
Expand Down Expand Up @@ -39,11 +40,14 @@
},
"ssdp_auth": {
"data": {
"username": "Používateľské meno",
"password": "Heslo",
"password": "Heslo GX",
"ssl": "Použiť SSL"
},
"description": "Pre {host} je potrebná autentifikácia."
"description": "Zadajte **heslo GX** pre zariadenie GX na {host}."
},
"ssdp_token_pairing": {
"title": "Spárovať so zariadením GX",
"description": "Pred stlačením Odoslať aktivujte režim párovania na zariadení GX:\n\n- Cez rozhranie GX: **Nastavenia** > **Integrácie** > **Zariadenia MQTT** > **Režim párovania**\n- Na zariadeniach GX bez vstavanej obrazovky: Rýchlo dvakrát stlačte vstavané tlačidlo\n\nRežim párovania je aktívny 120 sekúnd."
}
}
},
Expand Down
Loading
Loading