import json
import logging
import threading
from wsgiref.simple_server import make_server

import paho.mqtt.client as mqtt
from ws4py.server.wsgirefserver import (
    WebSocketWSGIHandler,
    WebSocketWSGIRequestHandler,
    WSGIServer,
)
from ws4py.server.wsgiutils import WebSocketWSGIApplication
from ws4py.websocket import WebSocket

from frigate.config import FrigateConfig
from frigate.util import restart_frigate

logger = logging.getLogger(__name__)


def create_mqtt_client(config: FrigateConfig, camera_metrics):
    mqtt_config = config.mqtt

    def on_recordings_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_recordings_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        record_settings = config.cameras[camera_name].record

        if payload == "ON":
            if not record_settings.enabled:
                logger.info(f"Turning on recordings for {camera_name} via mqtt")
                record_settings.enabled = True
        elif payload == "OFF":
            if record_settings.enabled:
                logger.info(f"Turning off recordings for {camera_name} via mqtt")
                record_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_snapshots_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_snapshots_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        snapshots_settings = config.cameras[camera_name].snapshots

        if payload == "ON":
            if not snapshots_settings.enabled:
                logger.info(f"Turning on snapshots for {camera_name} via mqtt")
                snapshots_settings.enabled = True
        elif payload == "OFF":
            if snapshots_settings.enabled:
                logger.info(f"Turning off snapshots for {camera_name} via mqtt")
                snapshots_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_detect_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_detect_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        detect_settings = config.cameras[camera_name].detect

        if payload == "ON":
            if not camera_metrics[camera_name]["detection_enabled"].value:
                logger.info(f"Turning on detection for {camera_name} via mqtt")
                camera_metrics[camera_name]["detection_enabled"].value = True
                detect_settings.enabled = True

                if not camera_metrics[camera_name]["motion_enabled"].value:
                    logger.info(
                        f"Turning on motion for {camera_name} due to detection being enabled."
                    )
                    camera_metrics[camera_name]["motion_enabled"].value = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["detection_enabled"].value:
                logger.info(f"Turning off detection for {camera_name} via mqtt")
                camera_metrics[camera_name]["detection_enabled"].value = False
                detect_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_motion_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        if payload == "ON":
            if not camera_metrics[camera_name]["motion_enabled"].value:
                logger.info(f"Turning on motion for {camera_name} via mqtt")
                camera_metrics[camera_name]["motion_enabled"].value = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["detection_enabled"].value:
                logger.error(
                    f"Turning off motion is not allowed when detection is enabled."
                )
                return

            if camera_metrics[camera_name]["motion_enabled"].value:
                logger.info(f"Turning off motion for {camera_name} via mqtt")
                camera_metrics[camera_name]["motion_enabled"].value = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_improve_contrast_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_improve_contrast_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        if payload == "ON":
            if not camera_metrics[camera_name]["improve_contrast_enabled"].value:
                logger.info(f"Turning on improve contrast for {camera_name} via mqtt")
                camera_metrics[camera_name]["improve_contrast_enabled"].value = True
                motion_settings.improve_contrast = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["improve_contrast_enabled"].value:
                logger.info(f"Turning off improve contrast for {camera_name} via mqtt")
                camera_metrics[camera_name]["improve_contrast_enabled"].value = False
                motion_settings.improve_contrast = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_threshold_command(client, userdata, message):
        try:
            payload = int(message.payload.decode())
        except ValueError:
            logger.warning(
                f"Received unsupported value at {message.topic}: {message.payload.decode()}"
            )
            return

        logger.debug(f"on_motion_threshold_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        logger.info(f"Setting motion threshold for {camera_name} via mqtt: {payload}")
        camera_metrics[camera_name]["motion_threshold"].value = payload
        motion_settings.threshold = payload

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_contour_area_command(client, userdata, message):
        try:
            payload = int(message.payload.decode())
        except ValueError:
            logger.warning(
                f"Received unsupported value at {message.topic}: {message.payload.decode()}"
            )
            return

        logger.debug(f"on_motion_contour_area_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        logger.info(
            f"Setting motion contour area for {camera_name} via mqtt: {payload}"
        )
        camera_metrics[camera_name]["motion_contour_area"].value = payload
        motion_settings.contour_area = payload

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_restart_command(client, userdata, message):
        restart_frigate()

    def on_connect(client, userdata, flags, rc):
        threading.current_thread().name = "mqtt"
        if rc != 0:
            if rc == 3:
                logger.error(
                    "Unable to connect to MQTT server: MQTT Server unavailable"
                )
            elif rc == 4:
                logger.error(
                    "Unable to connect to MQTT server: MQTT Bad username or password"
                )
            elif rc == 5:
                logger.error("Unable to connect to MQTT server: MQTT Not authorized")
            else:
                logger.error(
                    "Unable to connect to MQTT server: Connection refused. Error code: "
                    + str(rc)
                )

        logger.debug("MQTT connected")
        client.subscribe(f"{mqtt_config.topic_prefix}/#")
        client.publish(mqtt_config.topic_prefix + "/available", "online", retain=True)

    client = mqtt.Client(client_id=mqtt_config.client_id)
    client.on_connect = on_connect
    client.will_set(
        mqtt_config.topic_prefix + "/available", payload="offline", qos=1, retain=True
    )

    # register callbacks
    for name in config.cameras.keys():
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/recordings/set", on_recordings_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/snapshots/set", on_snapshots_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/detect/set", on_detect_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion/set", on_motion_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/improve_contrast/set",
            on_improve_contrast_command,
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion_threshold/set",
            on_motion_threshold_command,
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/set",
            on_motion_contour_area_command,
        )

    client.message_callback_add(
        f"{mqtt_config.topic_prefix}/restart", on_restart_command
    )

    if not mqtt_config.tls_ca_certs is None:
        if (
            not mqtt_config.tls_client_cert is None
            and not mqtt_config.tls_client_key is None
        ):
            client.tls_set(
                mqtt_config.tls_ca_certs,
                mqtt_config.tls_client_cert,
                mqtt_config.tls_client_key,
            )
        else:
            client.tls_set(mqtt_config.tls_ca_certs)
    if not mqtt_config.tls_insecure is None:
        client.tls_insecure_set(mqtt_config.tls_insecure)
    if not mqtt_config.user is None:
        client.username_pw_set(mqtt_config.user, password=mqtt_config.password)
    try:
        client.connect(mqtt_config.host, mqtt_config.port, 60)
    except Exception as e:
        logger.error(f"Unable to connect to MQTT server: {e}")
        raise

    client.loop_start()

    for name in config.cameras.keys():
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/recordings/state",
            "ON" if config.cameras[name].record.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/snapshots/state",
            "ON" if config.cameras[name].snapshots.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/detect/state",
            "ON" if config.cameras[name].detect.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion/state",
            "ON",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/improve_contrast/state",
            "ON" if config.cameras[name].motion.improve_contrast else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion_threshold/state",
            config.cameras[name].motion.threshold,
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/state",
            config.cameras[name].motion.contour_area,
            retain=True,
        )

    return client


class MqttSocketRelay:
    def __init__(self, mqtt_client, topic_prefix):
        self.mqtt_client = mqtt_client
        self.topic_prefix = topic_prefix

    def start(self):
        class MqttWebSocket(WebSocket):
            topic_prefix = self.topic_prefix
            mqtt_client = self.mqtt_client

            def received_message(self, message):
                try:
                    json_message = json.loads(message.data.decode("utf-8"))
                    json_message = {
                        "topic": f"{self.topic_prefix}/{json_message['topic']}",
                        "payload": json_message.get("payload"),
                        "retain": json_message.get("retain", False),
                    }
                except Exception as e:
                    logger.warning("Unable to parse websocket message as valid json.")
                    return

                logger.debug(
                    f"Publishing mqtt message from websockets at {json_message['topic']}."
                )
                self.mqtt_client.publish(
                    json_message["topic"],
                    json_message["payload"],
                    retain=json_message["retain"],
                )

        # start a websocket server on 5002
        WebSocketWSGIHandler.http_version = "1.1"
        self.websocket_server = make_server(
            "127.0.0.1",
            5002,
            server_class=WSGIServer,
            handler_class=WebSocketWSGIRequestHandler,
            app=WebSocketWSGIApplication(handler_cls=MqttWebSocket),
        )
        self.websocket_server.initialize_websockets_manager()
        self.websocket_thread = threading.Thread(
            target=self.websocket_server.serve_forever
        )

        def send(client, userdata, message):
            """Sends mqtt messages to clients."""
            try:
                logger.debug(f"Received mqtt message on {message.topic}.")
                ws_message = json.dumps(
                    {
                        "topic": message.topic.replace(f"{self.topic_prefix}/", ""),
                        "payload": message.payload.decode(),
                    }
                )
            except Exception as e:
                # if the payload can't be decoded don't relay to clients
                logger.debug(
                    f"MQTT payload for {message.topic} wasn't text. Skipping..."
                )
                return

            self.websocket_server.manager.broadcast(ws_message)

        self.mqtt_client.message_callback_add(f"{self.topic_prefix}/#", send)

        self.websocket_thread.start()

    def stop(self):
        self.websocket_server.manager.close_all()
        self.websocket_server.manager.stop()
        self.websocket_server.manager.join()
        self.websocket_server.shutdown()
        self.websocket_thread.join()

frigate/mqtt.py
import json
import logging
import threading
from wsgiref.simple_server import make_server

import paho.mqtt.client as mqtt
from ws4py.server.wsgirefserver import (
    WebSocketWSGIHandler,
    WebSocketWSGIRequestHandler,
    WSGIServer,
)
from ws4py.server.wsgiutils import WebSocketWSGIApplication
from ws4py.websocket import WebSocket

from frigate.config import FrigateConfig
from frigate.util import restart_frigate

logger = logging.getLogger(__name__)


def create_mqtt_client(config: FrigateConfig, camera_metrics):
    mqtt_config = config.mqtt

    def on_recordings_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_recordings_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        record_settings = config.cameras[camera_name].record

        if payload == "ON":
            if not record_settings.enabled:
                logger.info(f"Turning on recordings for {camera_name} via mqtt")
                record_settings.enabled = True
        elif payload == "OFF":
            if record_settings.enabled:
                logger.info(f"Turning off recordings for {camera_name} via mqtt")
                record_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_snapshots_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_snapshots_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        snapshots_settings = config.cameras[camera_name].snapshots

        if payload == "ON":
            if not snapshots_settings.enabled:
                logger.info(f"Turning on snapshots for {camera_name} via mqtt")
                snapshots_settings.enabled = True
        elif payload == "OFF":
            if snapshots_settings.enabled:
                logger.info(f"Turning off snapshots for {camera_name} via mqtt")
                snapshots_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_detect_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_detect_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        detect_settings = config.cameras[camera_name].detect

        if payload == "ON":
            if not camera_metrics[camera_name]["detection_enabled"].value:
                logger.info(f"Turning on detection for {camera_name} via mqtt")
                camera_metrics[camera_name]["detection_enabled"].value = True
                detect_settings.enabled = True

                if not camera_metrics[camera_name]["motion_enabled"].value:
                    logger.info(
                        f"Turning on motion for {camera_name} due to detection being enabled."
                    )
                    camera_metrics[camera_name]["motion_enabled"].value = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["detection_enabled"].value:
                logger.info(f"Turning off detection for {camera_name} via mqtt")
                camera_metrics[camera_name]["detection_enabled"].value = False
                detect_settings.enabled = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_motion_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        if payload == "ON":
            if not camera_metrics[camera_name]["motion_enabled"].value:
                logger.info(f"Turning on motion for {camera_name} via mqtt")
                camera_metrics[camera_name]["motion_enabled"].value = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["detection_enabled"].value:
                logger.error(
                    f"Turning off motion is not allowed when detection is enabled."
                )
                return

            if camera_metrics[camera_name]["motion_enabled"].value:
                logger.info(f"Turning off motion for {camera_name} via mqtt")
                camera_metrics[camera_name]["motion_enabled"].value = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_improve_contrast_command(client, userdata, message):
        payload = message.payload.decode()
        logger.debug(f"on_improve_contrast_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        if payload == "ON":
            if not camera_metrics[camera_name]["improve_contrast_enabled"].value:
                logger.info(f"Turning on improve contrast for {camera_name} via mqtt")
                camera_metrics[camera_name]["improve_contrast_enabled"].value = True
                motion_settings.improve_contrast = True
        elif payload == "OFF":
            if camera_metrics[camera_name]["improve_contrast_enabled"].value:
                logger.info(f"Turning off improve contrast for {camera_name} via mqtt")
                camera_metrics[camera_name]["improve_contrast_enabled"].value = False
                motion_settings.improve_contrast = False
        else:
            logger.warning(f"Received unsupported value at {message.topic}: {payload}")

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_threshold_command(client, userdata, message):
        try:
            payload = int(message.payload.decode())
        except ValueError:
            logger.warning(
                f"Received unsupported value at {message.topic}: {message.payload.decode()}"
            )
            return

        logger.debug(f"on_motion_threshold_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        logger.info(f"Setting motion threshold for {camera_name} via mqtt: {payload}")
        camera_metrics[camera_name]["motion_threshold"].value = payload
        motion_settings.threshold = payload

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_motion_contour_area_command(client, userdata, message):
        try:
            payload = int(message.payload.decode())
        except ValueError:
            logger.warning(
                f"Received unsupported value at {message.topic}: {message.payload.decode()}"
            )
            return

        logger.debug(f"on_motion_contour_area_toggle: {message.topic} {payload}")

        camera_name = message.topic.split("/")[-3]

        motion_settings = config.cameras[camera_name].motion

        logger.info(
            f"Setting motion contour area for {camera_name} via mqtt: {payload}"
        )
        camera_metrics[camera_name]["motion_contour_area"].value = payload
        motion_settings.contour_area = payload

        state_topic = f"{message.topic[:-4]}/state"
        client.publish(state_topic, payload, retain=True)

    def on_restart_command(client, userdata, message):
        restart_frigate()

    def on_connect(client, userdata, flags, rc):
        threading.current_thread().name = "mqtt"
        if rc != 0:
            if rc == 3:
                logger.error(
                    "Unable to connect to MQTT server: MQTT Server unavailable"
                )
            elif rc == 4:
                logger.error(
                    "Unable to connect to MQTT server: MQTT Bad username or password"
                )
            elif rc == 5:
                logger.error("Unable to connect to MQTT server: MQTT Not authorized")
            else:
                logger.error(
                    "Unable to connect to MQTT server: Connection refused. Error code: "
                    + str(rc)
                )

        logger.debug("MQTT connected")
        client.subscribe(f"{mqtt_config.topic_prefix}/#")
        client.publish(mqtt_config.topic_prefix + "/available", "online", retain=True)

    client = mqtt.Client(client_id=mqtt_config.client_id)
    client.on_connect = on_connect
    client.will_set(
        mqtt_config.topic_prefix + "/available", payload="offline", qos=1, retain=True
    )

    # register callbacks
    for name in config.cameras.keys():
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/recordings/set", on_recordings_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/snapshots/set", on_snapshots_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/detect/set", on_detect_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion/set", on_motion_command
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/improve_contrast/set",
            on_improve_contrast_command,
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion_threshold/set",
            on_motion_threshold_command,
        )
        client.message_callback_add(
            f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/set",
            on_motion_contour_area_command,
        )

    client.message_callback_add(
        f"{mqtt_config.topic_prefix}/restart", on_restart_command
    )

    if not mqtt_config.tls_ca_certs is None:
        if (
            not mqtt_config.tls_client_cert is None
            and not mqtt_config.tls_client_key is None
        ):
            client.tls_set(
                mqtt_config.tls_ca_certs,
                mqtt_config.tls_client_cert,
                mqtt_config.tls_client_key,
            )
        else:
            client.tls_set(mqtt_config.tls_ca_certs)
    if not mqtt_config.tls_insecure is None:
        client.tls_insecure_set(mqtt_config.tls_insecure)
    if not mqtt_config.user is None:
        client.username_pw_set(mqtt_config.user, password=mqtt_config.password)
    try:
        client.connect(mqtt_config.host, mqtt_config.port, 60)
    except Exception as e:
        logger.error(f"Unable to connect to MQTT server: {e}")
        raise

    client.loop_start()

    for name in config.cameras.keys():
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/recordings/state",
            "ON" if config.cameras[name].record.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/snapshots/state",
            "ON" if config.cameras[name].snapshots.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/detect/state",
            "ON" if config.cameras[name].detect.enabled else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion/state",
            "ON",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/improve_contrast/state",
            "ON" if config.cameras[name].motion.improve_contrast else "OFF",
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion_threshold/state",
            config.cameras[name].motion.threshold,
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion_contour_area/state",
            config.cameras[name].motion.contour_area,
            retain=True,
        )
        client.publish(
            f"{mqtt_config.topic_prefix}/{name}/motion",
            "OFF",
            retain=False,
        )

    return client


class MqttSocketRelay:
    def __init__(self, mqtt_client, topic_prefix):
        self.mqtt_client = mqtt_client
        self.topic_prefix = topic_prefix

    def start(self):
        class MqttWebSocket(WebSocket):
            topic_prefix = self.topic_prefix
            mqtt_client = self.mqtt_client

            def received_message(self, message):
                try:
                    json_message = json.loads(message.data.decode("utf-8"))
                    json_message = {
                        "topic": f"{self.topic_prefix}/{json_message['topic']}",
                        "payload": json_message.get("payload"),
                        "retain": json_message.get("retain", False),
                    }
                except Exception as e:
                    logger.warning("Unable to parse websocket message as valid json.")
                    return

                logger.debug(
                    f"Publishing mqtt message from websockets at {json_message['topic']}."
                )
                self.mqtt_client.publish(
                    json_message["topic"],
                    json_message["payload"],
                    retain=json_message["retain"],
                )

        # start a websocket server on 5002
        WebSocketWSGIHandler.http_version = "1.1"
        self.websocket_server = make_server(
            "127.0.0.1",
            5002,
            server_class=WSGIServer,
            handler_class=WebSocketWSGIRequestHandler,
            app=WebSocketWSGIApplication(handler_cls=MqttWebSocket),
        )
        self.websocket_server.initialize_websockets_manager()
        self.websocket_thread = threading.Thread(
            target=self.websocket_server.serve_forever
        )

        def send(client, userdata, message):
            """Sends mqtt messages to clients."""
            try:
                logger.debug(f"Received mqtt message on {message.topic}.")
                ws_message = json.dumps(
                    {
                        "topic": message.topic.replace(f"{self.topic_prefix}/", ""),
                        "payload": message.payload.decode(),
                    }
                )
            except Exception as e:
                # if the payload can't be decoded don't relay to clients
                logger.debug(
                    f"MQTT payload for {message.topic} wasn't text. Skipping..."
                )
                return

            self.websocket_server.manager.broadcast(ws_message)

        self.mqtt_client.message_callback_add(f"{self.topic_prefix}/#", send)

        self.websocket_thread.start()

    def stop(self):
        self.websocket_server.manager.close_all()
        self.websocket_server.manager.stop()
        self.websocket_server.manager.join()
        self.websocket_server.shutdown()
        self.websocket_thread.join()

frigate/mqtt.py
diff --git a/frigate/mqtt.py b/frigate/mqtt.py
index 4b74d6f62d..a1e564f7dd 100644
--- a/frigate/mqtt.py
+++ b/frigate/mqtt.py
@@ -314,6 +314,11 @@ def on_connect(client, userdata, flags, rc):
             config.cameras[name].motion.contour_area,
             retain=True,
         )
+        client.publish(
+            f"{mqtt_config.topic_prefix}/{name}/motion",
+            "OFF",
+            retain=False,
+        )
 
     return client
 

[Support]: motion sensor stays on indefinitely after restart
### Describe the problem you are having

The following can be reliably reproduced in my setup:

- I enter the room, frigate's motion sensor (`binary_sensor.<camera>_motion`) turns `on` 
- I restart the frigate add-on
- I leave the room while frigate is restarting.

During the restart `binary_sensor.<camera>_motion` becomes `unavailable`, and after the restart turn back to `on` and it remains on indefinitely.

More restarts do not affect the sensor. The only way to turn in back `off` is to enter the room again, cause a new motion detection, and wait for the new detection to finish.

Here's a log of the MQTT messages sent during this procedure:
```
2022-07-15 11:04:28 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/motion: b'ON'
2022-07-15 11:04:39 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/available: b'offline'
2022-07-15 11:04:40 ERROR (MainThread) [custom_components.frigate.api] Error fetching information from http://ccab4aaf-frigate-fa-beta:5000/api/stats: Cannot connect to host ccab4aaf-frigate-fa-beta:5000 ssl:default [Connect call failed ('172.30.33.2', 5000)]
2022-07-15 11:04:40 ERROR (MainThread) [custom_components.frigate] Error fetching frigate data: 
2022-07-15 11:04:45 ERROR (MainThread) [custom_components.frigate.api] Error fetching information from http://ccab4aaf-frigate-fa-beta:5000/api/stats: 502, message='Bad Gateway', url=URL('http://ccab4aaf-frigate-fa-beta:5000/api/stats')
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/recordings/state: b'OFF'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/snapshots/state: b'OFF'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/detect/state: b'OFF'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/motion/state: b'ON'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/available: b'online'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/recordings/state: b'OFF'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/snapshots/state: b'OFF'
2022-07-15 11:04:46 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/detect/state: b'OFF'
2022-07-15 11:04:48 DEBUG (MainThread) [homeassistant.components.mqtt.client] Received message on frigate/frigate_livingroom/all: b'0'
```
If I understand correctly:
- `frigate/<cameta>/motion` sets the state of `binary_sensor.<camera>_motion`
- `frigate/<cameta>/motion/state` sets the state of `switch.<camera>_motion`

It looks like after a restart, frigate sends a message to inform HA about the state of the `switch`, but not about the `binary_sensor`, which stays on until a future detection.





### Version

0.11.0-ef54cd6

### Frigate config file

```yaml
mqtt:
  host: 192.168.2.5
  user: mqtt_user
  password:

detectors:
  cpu1:
    type: cpu
    num_threads: 2

cameras:
  frigate_livingroom:
    ffmpeg:
      inputs:
        - path: "rtsp://<ip>/stream2"
          roles:
          - detect
        - path: "rtsp://<ip>/stream1"
          roles:
          - rtmp
          - record
    motion:
      mask:
        - 235,23,233,0,0,0,0,22
    detect:
      width: 640
      height: 360
      fps: 5
      enabled: False

record:
  enabled: False
  retain:
    days: 1

objects:
  track: []
```


### Relevant log output

```shell
[2022-07-15 10:42:29] frigate.app                    INFO    : Starting Frigate (0.11.0-ef54cd6)
[2022-07-15 10:42:29] frigate.app                    INFO    : Creating directory: /tmp/cache
Starting migrations
[2022-07-15 10:42:29] peewee_migrate                 INFO    : Starting migrations
There is nothing to migrate
[2022-07-15 10:42:29] peewee_migrate                 INFO    : There is nothing to migrate
[2022-07-15 10:42:29] frigate.app                    INFO    : Output process started: 219
[2022-07-15 10:42:29] detector.cpu1                  INFO    : Starting detection process: 218
[2022-07-15 10:42:29] frigate.edgetpu                WARNING : CPU detectors are not recommended and should only be used for testing or for trial purposes.
[2022-07-15 10:42:29] frigate.app                    INFO    : Camera processor started for frigate_livingroom: 222
[2022-07-15 10:42:29] ws4py                          INFO    : Using epoll
[2022-07-15 10:42:29] frigate.app                    INFO    : Capture process started for frigate_livingroom: 225
[2022-07-15 10:42:30] ws4py                          INFO    : Using epoll
```


### FFprobe output from your camera

```shell
ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers
  built with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
  configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  WARNING: library configuration mismatch
  avcodec     configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared --enable-version3 --disable-doc --disable-programs --enable-libaribb24 --enable-liblensfun --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libtesseract --enable-libvo_amrwbenc
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0
  Metadata:
    title           : Session streamed by "TP-LINK RTSP Server"
    comment         : stream2
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: h264 (Main), yuv420p(progressive), 640x360, 15 fps, 20 tbr, 90k tbn, 30 tbc
    Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s
```


### Frigate stats

_No response_

### Operating system

Debian

### Install method

HassOS Addon

### Coral version

CPU (no coral)

### Network connection

Wireless

### Camera make and model

TP-LINK Tapo C210

### Any other information that may be helpful

_No response_
