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`)
This commit is contained in:
VideoCurio 2024-12-19 17:29:10 +01:00
parent 4af752028f
commit 56a128b776
3 changed files with 16 additions and 1 deletions

View File

@ -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

View File

@ -168,9 +168,11 @@ class MqttClient(Communicator): # type: ignore[misc]
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 @@ class MqttClient(Communicator): # type: ignore[misc]
qos=1,
retain=True,
)
if self.mqtt_config.transport == "websockets":
self.client.ws_set_options(path="/mqtt")
# register callbacks
callback_types = [

View File

@ -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 @@ class MqttConfig(FrigateBaseModel):
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