From 5f96e81c91c5ac39f4d9127f72818d45a20d025c Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:15:42 -0500 Subject: [PATCH 1/3] fix: make WebSocket path configurable for EMQX compatibility The hardcoded path "/" caused WebSocket handshakes to fail with EMQX, which serves MQTT over WebSocket at /mqtt. Add a ws_path config option (defaulting to /mqtt) so the integration works with both EMQX and broker deployments that use a different WebSocket path. Co-Authored-By: Claude Opus 4.7 --- custom_components/meshcore/config_flow.py | 2 ++ custom_components/meshcore/mqtt_uploader.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/custom_components/meshcore/config_flow.py b/custom_components/meshcore/config_flow.py index 6f77580..063cb96 100644 --- a/custom_components/meshcore/config_flow.py +++ b/custom_components/meshcore/config_flow.py @@ -1099,6 +1099,7 @@ async def async_step_mqtt_broker(self, user_input=None): "owner_email": user_input.get("owner_email", ""), "topic_status": user_input.get("topic_status", DEFAULT_MQTT_TOPIC_STATUS), "topic_events": user_input.get("topic_events", DEFAULT_MQTT_TOPIC_EVENTS), + "ws_path": user_input.get("ws_path", "/mqtt"), "iata": user_input.get("iata", legacy_global_iata), "token_ttl_seconds": user_input.get("token_ttl_seconds", legacy_global_ttl), "payload_mode": user_input.get("payload_mode", "packet"), @@ -1134,6 +1135,7 @@ async def async_step_mqtt_broker(self, user_input=None): ): vol.All(cv.positive_int, vol.Range(min=60, max=86400)), vol.Optional("topic_status", default=broker.get("topic_status", DEFAULT_MQTT_TOPIC_STATUS)): str, vol.Optional("topic_events", default=broker.get("topic_events", DEFAULT_MQTT_TOPIC_EVENTS)): str, + vol.Optional("ws_path", default=broker.get("ws_path", "/mqtt")): str, vol.Optional("iata", default=broker.get("iata", legacy_global_iata)): str, }) diff --git a/custom_components/meshcore/mqtt_uploader.py b/custom_components/meshcore/mqtt_uploader.py index b0d159f..103ca59 100644 --- a/custom_components/meshcore/mqtt_uploader.py +++ b/custom_components/meshcore/mqtt_uploader.py @@ -74,6 +74,7 @@ class BrokerConfig: client_id_prefix: str topic_status: str topic_packets: str + ws_path: str = "/mqtt" @property def name(self) -> str: @@ -304,6 +305,9 @@ def _load_brokers(self) -> list[BrokerConfig]: ), iata, ), + ws_path=str( + broker_settings.get("ws_path", "/mqtt") or "/mqtt" + ).strip(), ) if broker.is_letsmesh and iata in placeholder_iata_values: @@ -426,7 +430,7 @@ async def _async_create_client(self, broker: BrokerConfig): ) if broker.transport == "websockets": - client.ws_set_options(path="/", headers=None) + client.ws_set_options(path=broker.ws_path, headers=None) self.logger.info( "[%s] Ready (status_topic=%s packets_topic=%s auth_token=%s)", From 21f6e6a7ecc4c26b79ac5176af2ca37f57b13d1d Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:07:25 -0500 Subject: [PATCH 2/3] Update custom_components/meshcore/config_flow.py Co-authored-by: Andy Shinn --- custom_components/meshcore/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/meshcore/config_flow.py b/custom_components/meshcore/config_flow.py index 063cb96..6b005f7 100644 --- a/custom_components/meshcore/config_flow.py +++ b/custom_components/meshcore/config_flow.py @@ -1099,7 +1099,7 @@ async def async_step_mqtt_broker(self, user_input=None): "owner_email": user_input.get("owner_email", ""), "topic_status": user_input.get("topic_status", DEFAULT_MQTT_TOPIC_STATUS), "topic_events": user_input.get("topic_events", DEFAULT_MQTT_TOPIC_EVENTS), - "ws_path": user_input.get("ws_path", "/mqtt"), + "ws_path": user_input.get("ws_path", "/mqtt"), "iata": user_input.get("iata", legacy_global_iata), "token_ttl_seconds": user_input.get("token_ttl_seconds", legacy_global_ttl), "payload_mode": user_input.get("payload_mode", "packet"), From 4f79a85287abdf889cc63d23b3f05a8e4ebd3520 Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:57:27 -0500 Subject: [PATCH 3/3] Update to default to root (/) path vs. /mqtt Initial commits defaulted to /mqtt, these revert it to default to / which is the old behavior. --- README.md | 4 ++++ custom_components/meshcore/config_flow.py | 4 ++-- custom_components/meshcore/mqtt_uploader.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4646736..983d8cb 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ Auth-token mode is easy install by default: - Signing key is pulled from the connected node via `export_private_key()`. - If private key export is disabled/blocked on firmware, auth-token upload cannot start. +Support for MQTT brokers that serve MQTT over a different path than / + +- Example: EMQX requires the path to be /mqtt + ## Map Auto Uploader (map.meshcore.io) When enabled (off by default), the integration automatically uploads repeater and room server adverts to [map.meshcore.io](https://map.meshcore.io) when your Companion hears them. A standalone alternative is [map.meshcore.io-uploader](https://github.com/recrof/map.meshcore.io-uploader). Enable in Global Settings if you want Map Auto Uploader. diff --git a/custom_components/meshcore/config_flow.py b/custom_components/meshcore/config_flow.py index 6b005f7..62bf5db 100644 --- a/custom_components/meshcore/config_flow.py +++ b/custom_components/meshcore/config_flow.py @@ -1099,7 +1099,7 @@ async def async_step_mqtt_broker(self, user_input=None): "owner_email": user_input.get("owner_email", ""), "topic_status": user_input.get("topic_status", DEFAULT_MQTT_TOPIC_STATUS), "topic_events": user_input.get("topic_events", DEFAULT_MQTT_TOPIC_EVENTS), - "ws_path": user_input.get("ws_path", "/mqtt"), + "ws_path": user_input.get("ws_path", "/"), "iata": user_input.get("iata", legacy_global_iata), "token_ttl_seconds": user_input.get("token_ttl_seconds", legacy_global_ttl), "payload_mode": user_input.get("payload_mode", "packet"), @@ -1135,7 +1135,7 @@ async def async_step_mqtt_broker(self, user_input=None): ): vol.All(cv.positive_int, vol.Range(min=60, max=86400)), vol.Optional("topic_status", default=broker.get("topic_status", DEFAULT_MQTT_TOPIC_STATUS)): str, vol.Optional("topic_events", default=broker.get("topic_events", DEFAULT_MQTT_TOPIC_EVENTS)): str, - vol.Optional("ws_path", default=broker.get("ws_path", "/mqtt")): str, + vol.Optional("ws_path", default=broker.get("ws_path", "/")): str, vol.Optional("iata", default=broker.get("iata", legacy_global_iata)): str, }) diff --git a/custom_components/meshcore/mqtt_uploader.py b/custom_components/meshcore/mqtt_uploader.py index 103ca59..56fdb19 100644 --- a/custom_components/meshcore/mqtt_uploader.py +++ b/custom_components/meshcore/mqtt_uploader.py @@ -74,7 +74,7 @@ class BrokerConfig: client_id_prefix: str topic_status: str topic_packets: str - ws_path: str = "/mqtt" + ws_path: str = "/" @property def name(self) -> str: @@ -306,7 +306,7 @@ def _load_brokers(self) -> list[BrokerConfig]: iata, ), ws_path=str( - broker_settings.get("ws_path", "/mqtt") or "/mqtt" + broker_settings.get("ws_path", "/") or "/" ).strip(), )