1313
1414#include "core/mqtt_client.h"
1515#include "core/logger.h"
16+ #include "core/path_utils.h"
1617#include "core/version.h"
1718#include "database/db_streams.h"
1819#include "video/go2rtc/go2rtc_snapshot.h"
1920
21+ #define MAX_TOPIC_LENGTH 512
22+
2023// MQTT client state
2124static struct mosquitto * mosq = NULL ;
2225static const config_t * mqtt_config = NULL ;
@@ -129,7 +132,7 @@ int mqtt_init(const config_t *config) {
129132
130133 // Set up Last Will and Testament for HA availability tracking
131134 if (config -> mqtt_ha_discovery ) {
132- char lwt_topic [512 ];
135+ char lwt_topic [MAX_TOPIC_LENGTH ];
133136 snprintf (lwt_topic , sizeof (lwt_topic ), "%s/availability" , config -> mqtt_topic_prefix );
134137 rc = mosquitto_will_set (mosq , lwt_topic , (int )strlen ("offline" ), "offline" ,
135138 config -> mqtt_qos , true);
@@ -217,15 +220,15 @@ static void on_connect(struct mosquitto *m, void *userdata, int rc) {
217220
218221 // Publish availability "online" for HA discovery
219222 if (mqtt_config && mqtt_config -> mqtt_ha_discovery ) {
220- char avail_topic [512 ];
223+ char avail_topic [MAX_TOPIC_LENGTH ];
221224 snprintf (avail_topic , sizeof (avail_topic ), "%s/availability" ,
222225 mqtt_config -> mqtt_topic_prefix );
223226 mqtt_publish_raw (avail_topic , "online" , true);
224227 log_info ("MQTT: Published availability 'online' to %s" , avail_topic );
225228
226229 // Subscribe to HA birth topic so we can re-publish discovery
227230 // when Home Assistant restarts
228- char status_topic [512 ];
231+ char status_topic [MAX_TOPIC_LENGTH ];
229232 snprintf (status_topic , sizeof (status_topic ), "%s/status" ,
230233 mqtt_config -> mqtt_ha_discovery_prefix );
231234 int sub_rc = mosquitto_subscribe (m , NULL , status_topic , 0 );
@@ -290,7 +293,7 @@ static void on_message(struct mosquitto *m, void *userdata, const struct mosquit
290293
291294 // Check if this is the HA birth message (status topic → "online")
292295 if (mqtt_config && mqtt_config -> mqtt_ha_discovery ) {
293- char status_topic [512 ];
296+ char status_topic [MAX_TOPIC_LENGTH ];
294297 snprintf (status_topic , sizeof (status_topic ), "%s/status" ,
295298 mqtt_config -> mqtt_ha_discovery_prefix );
296299
@@ -348,7 +351,7 @@ int mqtt_publish_detection(const char *stream_name, const detection_result_t *re
348351 }
349352
350353 // Build topic: {prefix}/detections/{stream_name}
351- char topic [512 ];
354+ char topic [MAX_TOPIC_LENGTH ];
352355 snprintf (topic , sizeof (topic ), "%s/detections/%s" ,
353356 mqtt_config -> mqtt_topic_prefix , stream_name );
354357
@@ -470,25 +473,6 @@ int mqtt_publish_binary(const char *topic, const void *data, size_t len, bool re
470473 return 0 ;
471474}
472475
473- /**
474- * Sanitize a stream name for use as a Home Assistant unique_id / object_id.
475- * Replaces non-alphanumeric characters with underscores and lowercases.
476- */
477- static void sanitize_stream_name (const char * input , char * output , size_t output_size ) {
478- size_t i = 0 ;
479- for (; i < output_size - 1 && input [i ] != '\0' ; i ++ ) {
480- char c = input [i ];
481- if ((c >= 'a' && c <= 'z' ) || (c >= '0' && c <= '9' )) {
482- output [i ] = c ;
483- } else if (c >= 'A' && c <= 'Z' ) {
484- output [i ] = (char )(c + ('a' - 'A' ));
485- } else {
486- output [i ] = '_' ;
487- }
488- }
489- output [i ] = '\0' ;
490- }
491-
492476/**
493477 * Build the common HA device JSON block for lightNVR.
494478 * Caller must free the returned cJSON object.
@@ -555,12 +539,12 @@ int mqtt_publish_ha_discovery(void) {
555539 continue ;
556540 }
557541
558- char safe_name [256 ];
542+ char safe_name [MAX_STREAM_NAME ];
559543 sanitize_stream_name (streams [i ].name , safe_name , sizeof (safe_name ));
560544
561545 // --- 1. Camera entity (snapshot image via MQTT) ---
562546 {
563- char topic [512 ];
547+ char topic [MAX_TOPIC_LENGTH ];
564548 snprintf (topic , sizeof (topic ), "%s/camera/lightnvr/%s/config" , prefix , safe_name );
565549
566550 cJSON * payload = cJSON_CreateObject ();
@@ -570,11 +554,9 @@ int mqtt_publish_ha_discovery(void) {
570554 snprintf (unique_id , sizeof (unique_id ), "lightnvr_%s_camera" , safe_name );
571555 cJSON_AddStringToObject (payload , "unique_id" , unique_id );
572556
573- char name [256 ];
574- snprintf (name , sizeof (name ), "%s" , streams [i ].name );
575- cJSON_AddStringToObject (payload , "name" , name );
557+ cJSON_AddStringToObject (payload , "name" , streams [i ].name );
576558
577- char image_topic [512 ];
559+ char image_topic [MAX_TOPIC_LENGTH ];
578560 snprintf (image_topic , sizeof (image_topic ), "%s/cameras/%s/snapshot" ,
579561 topic_prefix , safe_name );
580562 cJSON_AddStringToObject (payload , "topic" , image_topic );
@@ -584,7 +566,7 @@ int mqtt_publish_ha_discovery(void) {
584566
585567 // Availability
586568 cJSON * avail = cJSON_CreateObject ();
587- char avail_topic [512 ];
569+ char avail_topic [MAX_TOPIC_LENGTH ];
588570 snprintf (avail_topic , sizeof (avail_topic ), "%s/availability" , topic_prefix );
589571 cJSON_AddStringToObject (avail , "topic" , avail_topic );
590572 cJSON_AddStringToObject (avail , "payload_available" , "online" );
@@ -616,7 +598,7 @@ int mqtt_publish_ha_discovery(void) {
616598
617599 // --- 2. Binary sensor for motion detection ---
618600 {
619- char topic [512 ];
601+ char topic [MAX_TOPIC_LENGTH ];
620602 snprintf (topic , sizeof (topic ), "%s/binary_sensor/lightnvr/%s_motion/config" ,
621603 prefix , safe_name );
622604
@@ -631,7 +613,7 @@ int mqtt_publish_ha_discovery(void) {
631613 snprintf (name , sizeof (name ), "%s Motion" , streams [i ].name );
632614 cJSON_AddStringToObject (payload , "name" , name );
633615
634- char state_topic [512 ];
616+ char state_topic [MAX_TOPIC_LENGTH ];
635617 snprintf (state_topic , sizeof (state_topic ), "%s/cameras/%s/motion" ,
636618 topic_prefix , safe_name );
637619 cJSON_AddStringToObject (payload , "state_topic" , state_topic );
@@ -641,7 +623,7 @@ int mqtt_publish_ha_discovery(void) {
641623
642624 // Availability
643625 cJSON * avail = cJSON_CreateObject ();
644- char avail_topic [512 ];
626+ char avail_topic [MAX_TOPIC_LENGTH ];
645627 snprintf (avail_topic , sizeof (avail_topic ), "%s/availability" , topic_prefix );
646628 cJSON_AddStringToObject (avail , "topic" , avail_topic );
647629 cJSON_AddStringToObject (avail , "payload_available" , "online" );
@@ -673,7 +655,7 @@ int mqtt_publish_ha_discovery(void) {
673655
674656 // --- 3. Sensor for detection count (generic) ---
675657 {
676- char topic [512 ];
658+ char topic [MAX_TOPIC_LENGTH ];
677659 snprintf (topic , sizeof (topic ), "%s/sensor/lightnvr/%s_detection_count/config" ,
678660 prefix , safe_name );
679661
@@ -688,15 +670,15 @@ int mqtt_publish_ha_discovery(void) {
688670 snprintf (name , sizeof (name ), "%s Detections" , streams [i ].name );
689671 cJSON_AddStringToObject (payload , "name" , name );
690672
691- char state_topic [512 ];
673+ char state_topic [MAX_TOPIC_LENGTH ];
692674 snprintf (state_topic , sizeof (state_topic ), "%s/cameras/%s/detection_count" ,
693675 topic_prefix , safe_name );
694676 cJSON_AddStringToObject (payload , "state_topic" , state_topic );
695677 cJSON_AddStringToObject (payload , "icon" , "mdi:motion-sensor" );
696678
697679 // Availability
698680 cJSON * avail = cJSON_CreateObject ();
699- char avail_topic [512 ];
681+ char avail_topic [MAX_TOPIC_LENGTH ];
700682 snprintf (avail_topic , sizeof (avail_topic ), "%s/availability" , topic_prefix );
701683 cJSON_AddStringToObject (avail , "topic" , avail_topic );
702684 cJSON_AddStringToObject (avail , "payload_available" , "online" );
@@ -728,7 +710,7 @@ int mqtt_publish_ha_discovery(void) {
728710
729711 // --- Publish initial states so entities don't show "Unknown" ---
730712 {
731- char topic [512 ];
713+ char topic [MAX_TOPIC_LENGTH ];
732714
733715 // Motion: initially OFF
734716 snprintf (topic , sizeof (topic ), "%s/cameras/%s/motion" ,
@@ -832,7 +814,7 @@ void mqtt_set_motion_state(const char *stream_name, const detection_result_t *re
832814
833815 // Publish motion ON
834816 if (should_publish_on ) {
835- char topic [512 ];
817+ char topic [MAX_TOPIC_LENGTH ];
836818 snprintf (topic , sizeof (topic ), "%s/cameras/%s/motion" ,
837819 mqtt_config -> mqtt_topic_prefix , safe_name );
838820 mqtt_publish_raw (topic , "ON" , false);
@@ -841,7 +823,7 @@ void mqtt_set_motion_state(const char *stream_name, const detection_result_t *re
841823
842824 // Publish detection count
843825 {
844- char topic [512 ];
826+ char topic [MAX_TOPIC_LENGTH ];
845827 snprintf (topic , sizeof (topic ), "%s/cameras/%s/detection_count" ,
846828 mqtt_config -> mqtt_topic_prefix , safe_name );
847829 char count_str [16 ];
@@ -851,7 +833,7 @@ void mqtt_set_motion_state(const char *stream_name, const detection_result_t *re
851833
852834 // Publish per-object-class counts
853835 for (int i = 0 ; i < num_labels ; i ++ ) {
854- char topic [512 ];
836+ char topic [MAX_TOPIC_LENGTH ];
855837 snprintf (topic , sizeof (topic ), "%s/cameras/%s/%s" ,
856838 mqtt_config -> mqtt_topic_prefix , safe_name , labels_copy [i ]);
857839 char count_str [16 ];
@@ -889,7 +871,7 @@ static void *ha_snapshot_thread_func(void *arg) {
889871 if (go2rtc_get_snapshot (streams [i ].name , & jpeg_data , & jpeg_size )) {
890872 char safe_name [256 ];
891873 sanitize_stream_name (streams [i ].name , safe_name , sizeof (safe_name ));
892- char topic [512 ];
874+ char topic [MAX_TOPIC_LENGTH ];
893875 snprintf (topic , sizeof (topic ), "%s/cameras/%s/snapshot" ,
894876 mqtt_config -> mqtt_topic_prefix , safe_name );
895877 mqtt_publish_binary (topic , jpeg_data , jpeg_size , false);
@@ -944,7 +926,7 @@ static void *ha_motion_thread_func(void *arg) {
944926 pthread_mutex_unlock (& motion_mutex );
945927
946928 // Publish motion OFF
947- char topic [512 ];
929+ char topic [MAX_TOPIC_LENGTH ];
948930 snprintf (topic , sizeof (topic ), "%s/cameras/%s/motion" ,
949931 mqtt_config -> mqtt_topic_prefix , safe_name );
950932 mqtt_publish_raw (topic , "OFF" , false);
0 commit comments