2020-11-04 04:26:39 +01:00
|
|
|
import logging
|
2020-11-04 13:28:07 +01:00
|
|
|
import threading
|
2020-11-01 15:06:15 +01:00
|
|
|
|
2020-11-04 13:31:25 +01:00
|
|
|
import paho.mqtt.client as mqtt
|
|
|
|
|
2020-12-23 15:54:08 +01:00
|
|
|
from frigate.config import FrigateConfig
|
2020-11-03 15:15:58 +01:00
|
|
|
|
2020-11-04 04:26:39 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
|
2021-01-16 03:52:59 +01:00
|
|
|
def create_mqtt_client(config: FrigateConfig, camera_metrics):
|
2020-12-23 15:54:08 +01:00
|
|
|
mqtt_config = config.mqtt
|
|
|
|
|
|
|
|
def on_clips_command(client, userdata, message):
|
|
|
|
payload = message.payload.decode()
|
|
|
|
logger.debug(f"on_clips_toggle: {message.topic} {payload}")
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
camera_name = message.topic.split("/")[-3]
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2020-12-24 05:16:36 +01:00
|
|
|
clips_settings = config.cameras[camera_name].clips
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
if payload == "ON":
|
2020-12-24 05:16:36 +01:00
|
|
|
if not clips_settings.enabled:
|
|
|
|
logger.info(f"Turning on clips for {camera_name} via mqtt")
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
clips_settings.enabled = True
|
2021-02-17 14:23:32 +01:00
|
|
|
elif payload == "OFF":
|
2020-12-24 05:16:36 +01:00
|
|
|
if clips_settings.enabled:
|
|
|
|
logger.info(f"Turning off clips for {camera_name} via mqtt")
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
clips_settings.enabled = False
|
2020-12-24 05:16:36 +01:00
|
|
|
else:
|
|
|
|
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
|
|
|
|
2021-01-19 14:41:17 +01:00
|
|
|
state_topic = f"{message.topic[:-4]}/state"
|
|
|
|
client.publish(state_topic, payload, retain=True)
|
2020-12-24 05:16:36 +01:00
|
|
|
|
|
|
|
def on_snapshots_command(client, userdata, message):
|
|
|
|
payload = message.payload.decode()
|
|
|
|
logger.debug(f"on_snapshots_toggle: {message.topic} {payload}")
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
camera_name = message.topic.split("/")[-3]
|
2020-12-24 05:16:36 +01:00
|
|
|
|
|
|
|
snapshots_settings = config.cameras[camera_name].snapshots
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
if payload == "ON":
|
2020-12-24 05:16:36 +01:00
|
|
|
if not snapshots_settings.enabled:
|
|
|
|
logger.info(f"Turning on snapshots for {camera_name} via mqtt")
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
snapshots_settings.enabled = True
|
2021-02-17 14:23:32 +01:00
|
|
|
elif payload == "OFF":
|
2020-12-24 05:16:36 +01:00
|
|
|
if snapshots_settings.enabled:
|
|
|
|
logger.info(f"Turning off snapshots for {camera_name} via mqtt")
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
snapshots_settings.enabled = False
|
2020-12-23 15:54:08 +01:00
|
|
|
else:
|
|
|
|
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
|
|
|
|
2021-01-19 14:41:17 +01:00
|
|
|
state_topic = f"{message.topic[:-4]}/state"
|
|
|
|
client.publish(state_topic, payload, retain=True)
|
2021-02-17 14:23:32 +01:00
|
|
|
|
2021-01-16 03:52:59 +01:00
|
|
|
def on_detect_command(client, userdata, message):
|
|
|
|
payload = message.payload.decode()
|
|
|
|
logger.debug(f"on_detect_toggle: {message.topic} {payload}")
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
camera_name = message.topic.split("/")[-3]
|
2021-01-16 03:52:59 +01:00
|
|
|
|
2021-01-16 04:53:40 +01:00
|
|
|
detect_settings = config.cameras[camera_name].detect
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
if payload == "ON":
|
2021-01-16 03:52:59 +01:00
|
|
|
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
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
detect_settings.enabled = True
|
2021-02-17 14:23:32 +01:00
|
|
|
elif payload == "OFF":
|
2021-01-16 03:52:59 +01:00
|
|
|
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
|
Use dataclasses for config handling
Use config data classes to eliminate some of the boilerplate associated
with setting up the configuration. In particular, using dataclasses
removes a lot of the boilerplate around assigning properties to the
object and allows these to be easily immutable by freezing them. In the
case of simple, non-nested dataclasses, this also provides more
convenient `asdict` helpers.
To set this up, where previously the objects would be parsed from the
config via the `__init__` method, create a `build` classmethod that does
this and calls the dataclass initializer.
Some of the objects are mutated at runtime, in particular some of the
zones are mutated to set the color (this might be able to be refactored
out) and some of the camera functionality can be enabled/disabled. Some
of the configs with `enabled` properties don't seem to have mqtt hooks
to be able to toggle this, in particular, the clips, snapshots, and
detect can be toggled but rtmp and record configs do not, but all of
these configs are still not frozen in case there is some other
functionality I am missing.
There are a couple other minor fixes here, one that was introduced
by me recently where `max_seconds` was not defined, the other to
properly `get()` the message payload when handling publishing mqtt
messages sent via websocket.
2021-05-23 00:28:15 +02:00
|
|
|
detect_settings.enabled = False
|
2021-01-16 03:52:59 +01:00
|
|
|
else:
|
|
|
|
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
|
|
|
|
2021-01-19 14:41:17 +01:00
|
|
|
state_topic = f"{message.topic[:-4]}/state"
|
|
|
|
client.publish(state_topic, payload, retain=True)
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2020-11-01 15:06:15 +01:00
|
|
|
def on_connect(client, userdata, flags, rc):
|
2020-11-04 13:28:07 +01:00
|
|
|
threading.current_thread().name = "mqtt"
|
2020-11-01 15:06:15 +01:00
|
|
|
if rc != 0:
|
|
|
|
if rc == 3:
|
2020-11-04 04:26:39 +01:00
|
|
|
logger.error("MQTT Server unavailable")
|
2020-11-01 15:06:15 +01:00
|
|
|
elif rc == 4:
|
2020-11-04 04:26:39 +01:00
|
|
|
logger.error("MQTT Bad username or password")
|
2020-11-01 15:06:15 +01:00
|
|
|
elif rc == 5:
|
2020-11-04 04:26:39 +01:00
|
|
|
logger.error("MQTT Not authorized")
|
2020-11-01 15:06:15 +01:00
|
|
|
else:
|
2021-02-17 14:23:32 +01:00
|
|
|
logger.error(
|
|
|
|
"Unable to connect to MQTT: Connection refused. Error code: "
|
|
|
|
+ str(rc)
|
|
|
|
)
|
|
|
|
|
2020-11-04 13:28:07 +01:00
|
|
|
logger.info("MQTT connected")
|
2021-02-19 13:44:22 +01:00
|
|
|
client.subscribe(f"{mqtt_config.topic_prefix}/#")
|
2021-02-17 14:23:32 +01:00
|
|
|
client.publish(mqtt_config.topic_prefix + "/available", "online", retain=True)
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
client = mqtt.Client(client_id=mqtt_config.client_id)
|
2020-11-01 15:06:15 +01:00
|
|
|
client.on_connect = on_connect
|
2021-02-17 14:23:32 +01:00
|
|
|
client.will_set(
|
|
|
|
mqtt_config.topic_prefix + "/available", payload="offline", qos=1, retain=True
|
|
|
|
)
|
|
|
|
|
2020-12-23 15:54:08 +01:00
|
|
|
# register callbacks
|
|
|
|
for name in config.cameras.keys():
|
2021-02-17 14:23:32 +01:00
|
|
|
client.message_callback_add(
|
|
|
|
f"{mqtt_config.topic_prefix}/{name}/clips/set", on_clips_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
|
|
|
|
)
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2021-06-06 00:52:50 +02:00
|
|
|
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)
|
2020-12-23 15:54:08 +01:00
|
|
|
if not mqtt_config.user is None:
|
|
|
|
client.username_pw_set(mqtt_config.user, password=mqtt_config.password)
|
2020-12-05 16:47:43 +01:00
|
|
|
try:
|
2020-12-23 15:54:08 +01:00
|
|
|
client.connect(mqtt_config.host, mqtt_config.port, 60)
|
2020-12-05 16:47:43 +01:00
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Unable to connect to MQTT server: {e}")
|
|
|
|
raise
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2020-11-01 15:06:15 +01:00
|
|
|
client.loop_start()
|
2021-01-16 17:22:24 +01:00
|
|
|
|
|
|
|
for name in config.cameras.keys():
|
2021-02-17 14:23:32 +01:00
|
|
|
client.publish(
|
|
|
|
f"{mqtt_config.topic_prefix}/{name}/clips/state",
|
|
|
|
"ON" if config.cameras[name].clips.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,
|
|
|
|
)
|
2020-12-23 15:54:08 +01:00
|
|
|
|
2020-11-04 13:31:25 +01:00
|
|
|
return client
|