11# Copyright (C) 2025 Intel Corporation
22# SPDX-License-Identifier: Apache-2.0
33
4- import base64
54import json
65import logging
76import threading
87import time
9- from datetime import datetime
108from typing import Any
119
12- import cv2
1310import numpy as np
1411from model_api .models .result import Result
1512
16- from app .schemas .sink import MqttSinkConfig , OutputFormat
13+ from app .schemas .sink import MqttSinkConfig
1714from app .services .dispatchers .base import BaseDispatcher
1815
1916try :
2825CONNECT_TIMEOUT = 10
2926
3027
31- def _encode_image_to_base64 (image : np .ndarray , fmt : str = ".jpg" ) -> str :
32- success , img_buf = cv2 .imencode (fmt , image )
33- if success :
34- return base64 .b64encode (img_buf .tobytes ()).decode ("utf-8" )
35- raise ValueError (f"Failed to encode image in format { fmt } " )
36-
37-
38- def _create_mqtt_payload (data_type : str , ** kwargs ) -> dict [str , Any ]:
39- return {"timestamp" : datetime .now ().isoformat (), "type" : data_type , ** kwargs }
40-
41-
4228class MqttDispatcher (BaseDispatcher ):
4329 def __init__ (
4430 self ,
@@ -65,8 +51,7 @@ def __init__(
6551 self .broker_host = output_config .broker_host
6652 self .broker_port = output_config .broker_port
6753 self .topic = output_config .topic
68- self .username = output_config .username
69- self .password = output_config .password
54+ self .username , self .password = output_config .get_credentials ()
7055
7156 self ._connected = False
7257 self ._connection_lock = threading .Lock ()
@@ -82,7 +67,7 @@ def _create_default_client(self) -> "mqtt.Client":
8267 client = mqtt .Client (client_id = client_id )
8368 client .on_connect = self ._on_connect
8469 client .on_disconnect = self ._on_disconnect
85- if self .username and self .password :
70+ if self .username is not None and self .password is not None :
8671 client .username_pw_set (self .username , self .password )
8772 return client
8873
@@ -119,54 +104,26 @@ def _on_disconnect(self, _client: "mqtt.Client", _userdata: Any, rc: int):
119104 def is_connected (self ) -> bool :
120105 return self ._connected
121106
122- def _publish_message (self , topic : str , payload : dict [str , Any ]) -> bool :
107+ def __publish_message (self , topic : str , payload : dict [str , Any ]) -> None :
123108 if not self ._connected :
124109 logger .warning ("Client not connected. Reconnecting..." )
125110 try :
126111 self ._connect ()
127- except Exception :
112+ except ConnectionError :
128113 logger .exception ("Reconnect failed" )
129- return False
130114
131115 try :
132116 result = self .client .publish (topic , json .dumps (payload ))
133- if result .rc == mqtt .MQTT_ERR_SUCCESS :
134- if self ._track_messages :
135- self ._published_messages .append ({"topic" : topic , "payload" : payload , "timestamp" : datetime .now ()})
136- return True
117+ if result .rc == mqtt .MQTT_ERR_SUCCESS and self ._track_messages :
118+ self ._published_messages .append ({"topic" : topic , "payload" : payload })
137119 logger .error (f"Publish failed: { mqtt .error_string (result .rc )} " )
138- except Exception :
139- logger .exception ("Publish exception" )
140- return False
141-
142- def _dispatch_image (self , image : np .ndarray , data_type : str ):
143- try :
144- image_b64 = _encode_image_to_base64 (image )
145- payload = _create_mqtt_payload (
146- data_type = data_type ,
147- image = image_b64 ,
148- format = "jpeg" ,
149- )
150- self ._publish_message (self .topic , payload )
151- except Exception :
152- logger .exception ("Failed to dispatch %s" , data_type )
153-
154- def _dispatch_predictions (self , predictions : Result ):
155- try :
156- payload = _create_mqtt_payload (data_type = OutputFormat .PREDICTIONS .value , predictions = str (predictions ))
157- self ._publish_message (self .topic , payload )
158- except Exception :
159- logger .exception ("Failed to dispatch predictions" )
120+ except ValueError :
121+ logger .exception ("Invalid payload for MQTT publish" )
160122
161123 def _dispatch (self , original_image : np .ndarray , image_with_visualization : np .ndarray , predictions : Result ) -> None :
162- if OutputFormat .IMAGE_ORIGINAL in self .output_formats :
163- self ._dispatch_image (original_image , OutputFormat .IMAGE_ORIGINAL )
164-
165- if OutputFormat .IMAGE_WITH_PREDICTIONS in self .output_formats :
166- self ._dispatch_image (image_with_visualization , OutputFormat .IMAGE_WITH_PREDICTIONS )
124+ payload = self ._create_payload (original_image , image_with_visualization , predictions )
167125
168- if OutputFormat .PREDICTIONS in self .output_formats :
169- self ._dispatch_predictions (predictions )
126+ self .__publish_message (self .topic , payload )
170127
171128 def get_published_messages (self ) -> list :
172129 return self ._published_messages .copy ()
@@ -175,11 +132,11 @@ def clear_published_messages(self) -> None:
175132 self ._published_messages .clear ()
176133
177134 def close (self ) -> None :
178- try :
179- self . client . loop_stop ()
180- self . client . disconnect ( )
181- except Exception :
182- logger . exception ( "Error closing dispatcher" )
183- finally :
184- self ._connected = False
185- self ._connection_event .clear ()
135+ err = self . client . loop_stop ()
136+ if err != mqtt . MQTT_ERR_SUCCESS :
137+ logger . warning ( f"Error stopping MQTT loop: { mqtt . error_string ( err ) } " )
138+ err = self . client . disconnect ()
139+ if err != mqtt . MQTT_ERR_SUCCESS :
140+ logger . warning ( f"Error disconnecting MQTT client: { mqtt . error_string ( err ) } " )
141+ self ._connected = False
142+ self ._connection_event .clear ()
0 commit comments