Repository: blakeblackshear/frigate

Issue:
frigate as binary sensor switches constantly between online and offline 
setting this for each binary_sensor, fixes this for me. Found here https://community.home-assistant.io/t/tasmota-mqtt-switches-repeatedly-marked-as-unavailable/46935


```
    payload_available: ‘online’
    payload_not_available: ‘offline’
```
Does this mean HA and frigate defaults have a mismatch?

```
frigate/detect_objects.py:        client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True)
frigate/detect_objects.py:    client.will_set(MQTT_TOPIC_PREFIX+'/available', payload='offline', qos=1, retain=True)
```



Discussion:
I have seen this happen for other users if they have multiple instances of frigate running at the same time. 
Yes, I also use two frigate instances  both using their own topic_prefix and on the HA side separate availability_topic honoring the topic prefix for each binary sensor.

So it has nothing to do with online/offline vs Online/Offline?


I don't think so. Frigate registers using a single client ID with the mqtt server. You can't run multiple instances without separate client IDs. https://github.com/blakeblackshear/frigate/blob/master/detect_objects.py#L39

Do you have multiple Google Coral devices? 
Yes, got 2 raspberry's with Google Coral in two different locations, processing the local camera streams.
> I don't think so.
ok then I can't explain why the problem went away after setting 

```
    payload_available: ‘online’
    payload_not_available: ‘offline’

```

> Frigate registers using a single client ID with the mqtt server. You can't run multiple instances without separate client IDs.

Ok, so I should hard code different client names for both frigate instances?

That works. Ideally it would be set in the config file with a default. 

Resolving patch:
diff --git a/detect_objects.py b/detect_objects.py
--- a/detect_objects.py
+++ b/detect_objects.py
@@ -17,6 +17,30 @@
 MQTT_TOPIC_PREFIX = CONFIG.get('mqtt', {}).get('topic_prefix', 'frigate')
 MQTT_USER = CONFIG.get('mqtt', {}).get('user')
 MQTT_PASS = CONFIG.get('mqtt', {}).get('password')
+MQTT_CLIENT_ID = CONFIG.get('mqtt', {}).get('client_id', 'frigate')
+
+# Set the default FFmpeg config
+FFMPEG_CONFIG = CONFIG.get('ffmpeg', {})
+FFMPEG_DEFAULT_CONFIG = {
+    'global_args': FFMPEG_CONFIG.get('global_args', 
+        ['-hide_banner','-loglevel','panic']),
+    'hwaccel_args': FFMPEG_CONFIG.get('hwaccel_args', 
+        []),
+    'input_args': FFMPEG_CONFIG.get('input_args',
+        ['-avoid_negative_ts', 'make_zero',
+         '-fflags', 'nobuffer',
+         '-flags', 'low_delay',
+         '-strict', 'experimental',
+         '-fflags', '+genpts+discardcorrupt',
+         '-vsync', 'drop',
+         '-rtsp_transport', 'tcp',
+         '-stimeout', '5000000',
+         '-use_wallclock_as_timestamps', '1']),
+    'output_args': FFMPEG_CONFIG.get('output_args',
+        ['-vf', 'mpdecimate',
+         '-f', 'rawvideo',
+         '-pix_fmt', 'rgb24'])
+}
 
 WEB_PORT = CONFIG.get('web_port', 5000)
 DEBUG = (CONFIG.get('debug', '0') == '1')
@@ -36,7 +60,7 @@ def on_connect(client, userdata, flags, rc):
                 print ("Unable to connect to MQTT: Connection refused. Error code: " + str(rc))
         # publish a message to signal that the service is running
         client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True)
-    client = mqtt.Client(client_id="frigate")
+    client = mqtt.Client(client_id=MQTT_CLIENT_ID)
     client.on_connect = on_connect
     client.will_set(MQTT_TOPIC_PREFIX+'/available', payload='offline', qos=1, retain=True)
     if not MQTT_USER is None:
@@ -50,7 +74,7 @@ def on_connect(client, userdata, flags, rc):
 
     cameras = {}
     for name, config in CONFIG['cameras'].items():
-        cameras[name] = Camera(name, config, prepped_frame_queue, client, MQTT_TOPIC_PREFIX)
+        cameras[name] = Camera(name, FFMPEG_DEFAULT_CONFIG, config, prepped_frame_queue, client, MQTT_TOPIC_PREFIX)
 
     prepped_queue_processor = PreppedQueueProcessor(
         cameras,
@@ -65,21 +89,32 @@ def on_connect(client, userdata, flags, rc):
     # create a flask app that encodes frames a mjpeg on demand
     app = Flask(__name__)
 
+    @app.route('/')
+    def ishealthy():
+        # return a healh
+        return "Frigate is running. Alive and healthy!"
+
     @app.route('/<camera_name>/best_person.jpg')
     def best_person(camera_name):
-        best_person_frame = cameras[camera_name].get_best_person()
-        if best_person_frame is None:
-            best_person_frame = np.zeros((720,1280,3), np.uint8)
-        ret, jpg = cv2.imencode('.jpg', best_person_frame)
-        response = make_response(jpg.tobytes())
-        response.headers['Content-Type'] = 'image/jpg'
-        return response
+        if camera_name in cameras:
+            best_person_frame = cameras[camera_name].get_best_person()
+            if best_person_frame is None:
+                best_person_frame = np.zeros((720,1280,3), np.uint8)
+            ret, jpg = cv2.imencode('.jpg', best_person_frame)
+            response = make_response(jpg.tobytes())
+            response.headers['Content-Type'] = 'image/jpg'
+            return response
+        else:
+            return f'Camera named {camera_name} not found', 404
 
     @app.route('/<camera_name>')
     def mjpeg_feed(camera_name):
-        # return a multipart response
-        return Response(imagestream(camera_name),
-                        mimetype='multipart/x-mixed-replace; boundary=frame')
+        if camera_name in cameras:
+            # return a multipart response
+            return Response(imagestream(camera_name),
+                            mimetype='multipart/x-mixed-replace; boundary=frame')
+        else:
+            return f'Camera named {camera_name} not found', 404
 
     def imagestream(camera_name):
         while True:
diff --git a/frigate/objects.py b/frigate/objects.py
--- a/frigate/objects.py
+++ b/frigate/objects.py
@@ -87,6 +87,6 @@ def run(self):
                 
                 # print a timestamp
                 time_to_show = datetime.datetime.fromtimestamp(self.best_person['frame_time']).strftime("%m/%d/%Y %H:%M:%S")
-                cv2.putText(best_frame, time_to_show, (10, 10), cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=4)
+                cv2.putText(best_frame, time_to_show, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, fontScale=.8, color=(255, 255, 255), thickness=2)
                 
                 self.best_frame = cv2.cvtColor(best_frame, cv2.COLOR_RGB2BGR)
diff --git a/frigate/video.py b/frigate/video.py
--- a/frigate/video.py
+++ b/frigate/video.py
@@ -111,30 +111,20 @@ def run(self):
                 self.camera.frame_ready.notify_all()
 
 class Camera:
-    def __init__(self, name, config, prepped_frame_queue, mqtt_client, mqtt_prefix):
+    def __init__(self, name, ffmpeg_config, config, prepped_frame_queue, mqtt_client, mqtt_prefix):
         self.name = name
         self.config = config
         self.detected_objects = []
         self.recent_frames = {}
-        self.ffmpeg_input = get_ffmpeg_input(self.config['ffmpeg_input'])
+
+        self.ffmpeg = config.get('ffmpeg', {})
+        self.ffmpeg_input = get_ffmpeg_input(self.ffmpeg['input'])
+        self.ffmpeg_global_args = self.ffmpeg.get('global_args', ffmpeg_config['global_args'])
+        self.ffmpeg_hwaccel_args = self.ffmpeg.get('hwaccel_args', ffmpeg_config['hwaccel_args'])
+        self.ffmpeg_input_args = self.ffmpeg.get('input_args', ffmpeg_config['input_args'])
+        self.ffmpeg_output_args = self.ffmpeg.get('output_args', ffmpeg_config['output_args'])
+
         self.take_frame = self.config.get('take_frame', 1)
-        self.ffmpeg_log_level = self.config.get('ffmpeg_log_level', 'panic')
-        self.ffmpeg_hwaccel_args = self.config.get('ffmpeg_hwaccel_args', [])
-        self.ffmpeg_input_args = self.config.get('ffmpeg_input_args', [
-            '-avoid_negative_ts', 'make_zero', 
-            '-fflags', 'nobuffer',
-            '-flags', 'low_delay',
-            '-strict', 'experimental',
-            '-fflags', '+genpts+discardcorrupt',
-            '-vsync', 'drop',
-            '-rtsp_transport', 'tcp', 
-            '-stimeout', '5000000', 
-            '-use_wallclock_as_timestamps', '1'
-        ])
-        self.ffmpeg_output_args = self.config.get('ffmpeg_output_args', [
-            '-f', 'rawvideo',
-            '-pix_fmt', 'rgb24'
-        ])
         self.regions = self.config['regions']
         self.frame_shape = get_frame_shape(self.ffmpeg_input)
         self.frame_size = self.frame_shape[0] * self.frame_shape[1] * self.frame_shape[2]
@@ -232,12 +222,8 @@ def start_or_restart_capture(self):
         self.capture_thread.start()
     
     def start_ffmpeg(self):
-        ffmpeg_global_args = [
-            '-hide_banner', '-loglevel', self.ffmpeg_log_level
-        ]
-
         ffmpeg_cmd = (['ffmpeg'] +
-            ffmpeg_global_args +
+            self.ffmpeg_global_args +
             self.ffmpeg_hwaccel_args +
             self.ffmpeg_input_args +
             ['-i', self.ffmpeg_input] +


Test patch: