From 56a128b7767729417a1a845a3bcb4bcbd7ec6588 Mon Sep 17 00:00:00 2001 From: VideoCurio Date: Thu, 19 Dec 2024 17:29:10 +0100 Subject: [PATCH] Add websockets transport mechanism to MQTT ## Proposed change Add websockets transport mechanism to MQTT. An MQTT server is not always reachable only as raw TCP on port 1881; it can also be accessed as a WebSocket server, sometimes even behind a reverse proxy with TLS. Paho MQTT supports this feature, as does Mosquitto. The default transport option was set to 'tcp', so this change will not affect an already configured Frigate app. The configuration change is validated using a call to a pydantic library field validator. This patch has been tested with Mosquitto 1.6 and 2.0, as well as with a server behind a reverse proxy on port 443 (requiring a TLS connection). The docs/configuration/reference.md file has also been updated to reflect this change. ## Type of change - [ ] Dependency upgrade - [ ] Bugfix (non-breaking change which fixes an issue) - [x] New feature - [ ] Breaking change (fix/feature causing existing functionality to break) - [ ] Code quality improvements to existing code - [x] Documentation Update ## Additional information - This PR fixes or closes issue: fixes #15600 - This PR is related to issue: ## Checklist - [x] The code change is tested and works locally. - [ ] Local tests pass. **Your PR cannot be merged unless tests pass** - [x] There is no commented out code in this PR. - [x] The code has been formatted using Ruff (`ruff format frigate`) --- docs/docs/configuration/reference.md | 3 +++ frigate/comms/mqtt.py | 4 ++++ frigate/config/mqtt.py | 10 +++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index bb7ae49a3c..8fad3a08df 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -19,6 +19,9 @@ mqtt: host: mqtt.server.com # Optional: port (default: shown below) port: 1883 + # Optional: MQTT transport mechanism (default: shown below) + # NOTE: must be tcp (raw MQTT) or websockets. + transport: tcp # Optional: topic prefix (default: shown below) # NOTE: must be unique if you are running multiple instances topic_prefix: frigate diff --git a/frigate/comms/mqtt.py b/frigate/comms/mqtt.py index 5a85a710b6..14a77b5d3a 100644 --- a/frigate/comms/mqtt.py +++ b/frigate/comms/mqtt.py @@ -168,9 +168,11 @@ def _on_disconnect( def _start(self) -> None: """Start mqtt client.""" + logger.info("MQTT transport mechanism: %s" % str(self.mqtt_config.transport)) self.client = mqtt.Client( callback_api_version=CallbackAPIVersion.VERSION2, client_id=self.mqtt_config.client_id, + transport=self.mqtt_config.transport, ) self.client.on_connect = self._on_connect self.client.on_disconnect = self._on_disconnect @@ -180,6 +182,8 @@ def _start(self) -> None: qos=1, retain=True, ) + if self.mqtt_config.transport == "websockets": + self.client.ws_set_options(path="/mqtt") # register callbacks callback_types = [ diff --git a/frigate/config/mqtt.py b/frigate/config/mqtt.py index 1f3bb025dd..6367000200 100644 --- a/frigate/config/mqtt.py +++ b/frigate/config/mqtt.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import Field, ValidationInfo, model_validator +from pydantic import Field, ValidationInfo, model_validator, field_validator from typing_extensions import Self from frigate.const import FREQUENCY_STATS_POINTS @@ -15,6 +15,7 @@ class MqttConfig(FrigateBaseModel): enabled: bool = Field(default=True, title="Enable MQTT Communication.") host: str = Field(default="", title="MQTT Host") port: int = Field(default=1883, title="MQTT Port") + transport: str = Field(default="tcp", title="MQTT Transport Mechanism") topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix") client_id: str = Field(default="frigate", title="MQTT Client ID") stats_interval: int = Field( @@ -36,3 +37,10 @@ def user_requires_pass(self, info: ValidationInfo) -> Self: if (self.user is None) != (self.password is None): raise ValueError("Password must be provided with username.") return self + + @field_validator("transport") + @classmethod + def check_transport_mechanism(cls, v: str) -> str: + if v != "tcp" and v != "websockets": + raise ValueError("MQTT transport could only be tcp or websockets.") + return v