├── .gitignore ├── localsdk ├── video │ ├── video.h │ └── video.c ├── audio │ ├── audio.h │ └── audio.c ├── alarm │ ├── alarm.h │ └── alarm.c ├── night │ ├── night.h │ └── night.c ├── init.h ├── osd │ ├── osd.h │ └── osd.c ├── speaker │ ├── speaker.h │ └── speaker.c ├── init.c └── localsdk.h ├── rtsp ├── rtsp.h ├── Makefile ├── libRtspServer.h ├── rtsp.c └── libRtspServer.cpp ├── .gitmodules ├── mqtt ├── homeassistant.h ├── mqtt.h ├── homeassistant.c └── mqtt.c ├── logger ├── logger.h └── logger.c ├── configs ├── configs.h └── configs.c ├── Makefile ├── mjsxj02hl.c ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | /bin 3 | /lib 4 | 5 | # PVS-Studio 6 | strace_out 7 | PVS-Studio.log 8 | PVS-Studio.html 9 | /PVS-Studio.fullhtml 10 | -------------------------------------------------------------------------------- /localsdk/video/video.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_VIDEO_H_ 2 | #define _LOCALSDK_VIDEO_H_ 3 | 4 | #include 5 | 6 | // Init video 7 | bool video_init(); 8 | 9 | // Free video 10 | bool video_free(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /localsdk/audio/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_AUDIO_H_ 2 | #define _LOCALSDK_AUDIO_H_ 3 | 4 | #include 5 | 6 | // Is enabled 7 | bool audio_is_enabled(int channel); 8 | 9 | // Init audio 10 | bool audio_init(); 11 | 12 | // Free audio 13 | bool audio_free(); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /localsdk/alarm/alarm.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_ALARM_H_ 2 | #define _LOCALSDK_ALARM_H_ 3 | 4 | #include 5 | 6 | // Init alarm 7 | bool alarm_init(); 8 | 9 | // Enable or disable alarm 10 | bool alarm_switch(bool state); 11 | 12 | // Free alarm 13 | bool alarm_free(); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /localsdk/night/night.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_NIGHT_H_ 2 | #define _LOCALSDK_NIGHT_H_ 3 | 4 | #include 5 | 6 | #define NIGHT_MODE_STATE_NIGHTTIME 0 7 | #define NIGHT_MODE_STATE_DAYTIME 1 8 | #define NIGHT_MODE_STATE_DISABLE 2 9 | #define NIGHT_MODE_STATE_ENABLE 3 10 | 11 | // Init night mode 12 | bool night_init(); 13 | 14 | // Free night mode 15 | bool night_free(); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /rtsp/rtsp.h: -------------------------------------------------------------------------------- 1 | #ifndef _RTSP_H_ 2 | #define _RTSP_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Init RTSP 8 | bool rtsp_init(); 9 | 10 | // Is enabled 11 | bool rtsp_is_enabled(int channel); 12 | 13 | // Free RTSP 14 | bool rtsp_free(); 15 | 16 | // Send data frame 17 | bool rtsp_media_frame(int channel, signed char *data, size_t size, uint32_t timestamp, uint8_t type); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /localsdk/init.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_INIT_H_ 2 | #define _LOCALSDK_INIT_H_ 3 | 4 | #include 5 | 6 | // Get firmware version 7 | char *firmware_version(); 8 | 9 | // Get device id 10 | char *device_id(); 11 | 12 | // Removes all non-printable characters from string 13 | char *prepare_string(char *string); 14 | 15 | // Init all 16 | bool all_init(); 17 | 18 | // Free all 19 | bool all_free(); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /localsdk/osd/osd.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_OSD_H_ 2 | #define _LOCALSDK_OSD_H_ 3 | 4 | #include 5 | 6 | #include "./../localsdk.h" 7 | 8 | 9 | // Is enabled 10 | bool osd_is_enabled(); 11 | 12 | // Init OSD 13 | bool osd_init(); 14 | 15 | // Init OSD after video init 16 | bool osd_postinit(); 17 | 18 | // Free OSD 19 | bool osd_free(); 20 | 21 | // Rectangles callback 22 | int osd_rectangles_callback(LOCALSDK_ALARM_EVENT_INFO *eventInfo); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "yyjson"] 2 | path = yyjson 3 | url = https://github.com/ibireme/yyjson 4 | [submodule "mqtt/paho.mqtt.c"] 5 | path = mqtt/paho.mqtt.c 6 | url = https://github.com/eclipse/paho.mqtt.c 7 | [submodule "configs/inih"] 8 | path = configs/inih 9 | url = https://github.com/benhoyt/inih 10 | [submodule "rtsp/RtspServer"] 11 | path = rtsp/RtspServer 12 | url = https://github.com/ZoneMinder/RtspServer 13 | [submodule "ipctool"] 14 | path = ipctool 15 | url = https://github.com/OpenIPC/ipctool 16 | -------------------------------------------------------------------------------- /mqtt/homeassistant.h: -------------------------------------------------------------------------------- 1 | #ifndef _MQTT_HOMEASSISTANT_H_ 2 | #define _MQTT_HOMEASSISTANT_H_ 3 | 4 | #include 5 | 6 | #define MQTT_HOMEASSISTANT_MANUFACTURER "Xiaomi" 7 | #define MQTT_HOMEASSISTANT_MODEL "Mi Home Security Camera 1080P (MJSXJ02HL)" 8 | 9 | #define MQTT_HOMEASSISTANT_SENSOR 1 10 | #define MQTT_HOMEASSISTANT_BINARY_SENSOR 2 11 | 12 | // Add device to Home Assistant over discovery protocol 13 | bool mqtt_homeassistant_discovery(int type, char *topic_name, char *json_field, char *device_class, char *unit_of_measurement, int off_delay, bool enabled); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /logger/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOGGER_H_ 2 | #define _LOGGER_H_ 3 | 4 | #define LOGGER_LEVEL_FORCED -1 5 | #define LOGGER_LEVEL_DISABLED 0 6 | #define LOGGER_LEVEL_ERROR 1 7 | #define LOGGER_LEVEL_WARNING 2 8 | #define LOGGER_LEVEL_INFO 3 9 | #define LOGGER_LEVEL_DEBUG 4 10 | 11 | // Add message to log 12 | int logger(const char *file, const int line, const char *function, const int level, char *format, ...); 13 | #define LOGGER(level, format, ...) logger(__FILE__, __LINE__, __FUNCTION__, level, format, ##__VA_ARGS__) 14 | 15 | // Hexdump of memory 16 | int logger_memory(const char *description, const int level, const void *address, const int length, int width); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /localsdk/speaker/speaker.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_SPEAKER_H_ 2 | #define _LOCALSDK_SPEAKER_H_ 3 | 4 | #include 5 | 6 | #define SPEAKER_MEDIA_STOPPED 0 7 | #define SPEAKER_MEDIA_PLAYING 1 8 | #define SPEAKER_MEDIA_STOPPING 2 9 | 10 | // Init speaker 11 | bool speaker_init(); 12 | 13 | // Free speaker 14 | bool speaker_free(); 15 | 16 | // Play media (WAV, 8000 hz, 16-bit, mono) 17 | bool speaker_play_media(char *filename, int type); 18 | 19 | // Get playback status 20 | int speaker_status_media(); 21 | 22 | // Stop playback 23 | bool speaker_stop_media(); 24 | 25 | // Set volume 26 | bool speaker_set_volume(int value); 27 | 28 | // Get volume 29 | int speaker_get_volume(); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /mqtt/mqtt.h: -------------------------------------------------------------------------------- 1 | #ifndef _MQTT_H_ 2 | #define _MQTT_H_ 3 | 4 | #include 5 | 6 | #define MQTT_TIMEOUT 5 7 | 8 | #define MQTT_INFO_TOPIC "info" 9 | #define MQTT_ALARM_TOPIC "alarm" 10 | #define MQTT_NIGHT_TOPIC "night" 11 | #define MQTT_COMMAND_TOPIC "cmd" 12 | #define MQTT_STATE_TOPIC "state" 13 | 14 | #define MQTT_STATE_ONLINE "online" 15 | #define MQTT_STATE_OFFLINE "offline" 16 | 17 | // Init mqtt 18 | bool mqtt_init(); 19 | 20 | // Is enabled 21 | bool mqtt_is_enabled(); 22 | 23 | // Check connection 24 | bool mqtt_is_connected(); 25 | 26 | // Check ready 27 | bool mqtt_is_ready(); 28 | 29 | // Free mqtt 30 | bool mqtt_free(bool force); 31 | 32 | // Get full topic 33 | char *mqtt_fulltopic(const char *topic); 34 | 35 | // Prepare string for use as MQTT paths/names 36 | char *mqtt_prepare_string(const char *string); 37 | 38 | // Get clien id 39 | char *mqtt_client_id(); 40 | 41 | // Send data 42 | bool mqtt_send(const char *topic, char *payload); 43 | 44 | // Send formatted data 45 | bool mqtt_sendf(const char *topic, const char *format, ...); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /rtsp/Makefile: -------------------------------------------------------------------------------- 1 | DEBUG = -D_DEBUG 2 | 3 | OUTPUT = ../bin 4 | LIBDIR = ../lib 5 | 6 | TARGET = $(LIBDIR)/librtspserver.so 7 | 8 | OBJS_PATH = $(OUTPUT)/objects/librtspserver 9 | 10 | CROSS_COMPILE = arm-himix100-linux- 11 | CXX = $(CROSS_COMPILE)g++ 12 | 13 | INC = -I$(shell pwd)/RtspServer/src/ -I$(shell pwd)/RtspServer/src/net -I$(shell pwd)/RtspServer/src/xop -I$(shell pwd)/RtspServer/src/3rdpart 14 | 15 | LD_FLAGS = -lrt -pthread -lpthread -ldl -lm $(DEBUG) 16 | CXX_FLAGS = -std=c++11 -fPIC -march=armv7-a -mfpu=neon-vfpv4 -funsafe-math-optimizations 17 | 18 | SRC1 = $(notdir $(wildcard ./RtspServer/src/net/*.cpp)) 19 | OBJS1 = $(patsubst %.cpp,$(OBJS_PATH)/%.o,$(SRC1)) 20 | 21 | SRC2 = $(notdir $(wildcard ./RtspServer/src/xop/*.cpp)) 22 | OBJS2 = $(patsubst %.cpp,$(OBJS_PATH)/%.o,$(SRC2)) 23 | 24 | SRC3 = $(notdir $(wildcard ./libRtspServer.cpp)) 25 | OBJS3 = $(patsubst %.cpp,$(OBJS_PATH)/%.o,$(SRC3)) 26 | 27 | all: BUILD_DIR $(TARGET) 28 | 29 | BUILD_DIR: 30 | @-mkdir -p $(OBJS_PATH) 31 | 32 | $(TARGET) : $(OBJS1) $(OBJS2) $(OBJS3) 33 | $(CXX) $^ -o $@ -shared $(LD_FLAGS) 34 | 35 | $(OBJS_PATH)/%.o : ./%.cpp 36 | $(CXX) -c $< -o $@ $(CXX_FLAGS) $(INC) 37 | $(OBJS_PATH)/%.o : ./RtspServer/src/net/%.cpp 38 | $(CXX) -c $< -o $@ $(CXX_FLAGS) $(INC) 39 | $(OBJS_PATH)/%.o : ./RtspServer/src/xop/%.cpp 40 | $(CXX) -c $< -o $@ $(CXX_FLAGS) $(INC) 41 | 42 | clean: 43 | -rm -rf $(OBJS_PATH) 44 | -------------------------------------------------------------------------------- /rtsp/libRtspServer.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBRTSPSERVER_H 2 | #define __LIBRTSPSERVER_H 3 | 4 | #define LIBRTSPSERVER_TYPE_NONE 0 5 | #define LIBRTSPSERVER_TYPE_H264 1 6 | #define LIBRTSPSERVER_TYPE_H265 2 7 | #define LIBRTSPSERVER_TYPE_AAC 3 8 | #define LIBRTSPSERVER_TYPE_G711A 4 9 | #define LIBRTSPSERVER_TYPE_VP8 5 10 | 11 | #define LIBRTSPSERVER_UNKNOWN_FRAME 0x00 12 | #define LIBRTSPSERVER_VIDEO_FRAME_I 0x01 13 | #define LIBRTSPSERVER_VIDEO_FRAME_P 0x02 14 | #define LIBRTSPSERVER_VIDEO_FRAME_B 0x03 15 | #define LIBRTSPSERVER_AUDIO_FRAME 0x11 16 | 17 | #ifdef __cplusplus 18 | extern "C"{ 19 | #endif 20 | 21 | // Set log printf function 22 | bool rtspserver_logprintf(int (*function)(const char *, ...)); 23 | 24 | // Set connected callback function 25 | bool rtspserver_connected(void (*function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port)); 26 | 27 | // Set disconnected callback function 28 | bool rtspserver_disconnected(void (*function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port)); 29 | 30 | // Create RTSP server 31 | bool rtspserver_create(uint16_t port, char *username, char *password); 32 | 33 | // Create new session 34 | uint32_t rtspserver_session(char *name, bool multicast, uint8_t video_type, uint32_t framerate, uint8_t audio_type, uint32_t samplerate, uint32_t channels, bool has_adts); 35 | 36 | // Get current timestamp 37 | uint32_t rtspserver_timestamp(uint8_t source, uint32_t samplerate); 38 | 39 | // Send media frame 40 | bool rtspserver_frame(uint32_t session_id, signed char *data, uint8_t type, uint32_t size, uint32_t timestamp, bool split_video); 41 | 42 | // Free RTSP server 43 | bool rtspserver_free(uint32_t count, ...); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /configs/configs.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIGS_H_ 2 | #define _CONFIGS_H_ 3 | 4 | #include 5 | 6 | // All configurations 7 | typedef struct { 8 | 9 | // [general] 10 | struct { 11 | char *name; 12 | bool led; 13 | } general; 14 | 15 | // [logger] 16 | struct { 17 | int level; 18 | char *file; 19 | } logger; 20 | 21 | // [osd] 22 | struct { 23 | bool enable; 24 | bool oemlogo; 25 | int oemlogo_x; 26 | int oemlogo_y; 27 | int oemlogo_size; 28 | bool datetime; 29 | int datetime_x; 30 | int datetime_y; 31 | int datetime_size; 32 | bool motion; 33 | bool humanoid; 34 | } osd; 35 | 36 | // [video] 37 | struct { 38 | int gop; 39 | bool flip; 40 | bool mirror; 41 | int primary_type; 42 | int secondary_type; 43 | int primary_bitrate; 44 | int secondary_bitrate; 45 | int primary_rcmode; 46 | int secondary_rcmode; 47 | } video; 48 | 49 | // [audio] 50 | struct { 51 | int volume; 52 | bool primary_enable; 53 | bool secondary_enable; 54 | } audio; 55 | 56 | // [speaker] 57 | struct { 58 | int volume; 59 | int type; 60 | } speaker; 61 | 62 | // [alarm] 63 | struct { 64 | bool enable; 65 | int motion_sens; 66 | int humanoid_sens; 67 | int motion_timeout; 68 | int humanoid_timeout; 69 | char *motion_detect_exec; 70 | char *humanoid_detect_exec; 71 | char *motion_lost_exec; 72 | char *humanoid_lost_exec; 73 | } alarm; 74 | 75 | // [rtsp] 76 | struct { 77 | bool enable; 78 | int port; 79 | char *username; 80 | char *password; 81 | char *primary_name; 82 | char *secondary_name; 83 | bool primary_multicast; 84 | bool secondary_multicast; 85 | bool primary_split_vframes; 86 | bool secondary_split_vframes; 87 | } rtsp; 88 | 89 | // [mqtt] 90 | struct { 91 | bool enable; 92 | char *server; 93 | int port; 94 | char *username; 95 | char *password; 96 | char *topic; 97 | int qos; 98 | bool retain; 99 | int reconnection_interval; 100 | int periodical_interval; 101 | char *discovery; 102 | } mqtt; 103 | 104 | // [night] 105 | struct { 106 | int mode; 107 | int gray; 108 | } night; 109 | 110 | } APPLICATION_CONFIGURATION; 111 | 112 | // Global configuration variable 113 | extern APPLICATION_CONFIGURATION APP_CFG; 114 | 115 | // Init application configs 116 | bool configs_init(char *filename); 117 | 118 | // Free application configs 119 | bool configs_free(); 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /logger/logger.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "./logger.h" 12 | #include "./../localsdk/init.h" 13 | #include "./../configs/configs.h" 14 | 15 | // Write message to log 16 | static int logger_write(const int level, const char *format, ...) { 17 | int result = 0; 18 | if(level < 0 || level <= APP_CFG.logger.level) { 19 | va_list params; 20 | va_start(params, format); 21 | result = vprintf(format, params); 22 | // Write to file 23 | if(APP_CFG.logger.file && APP_CFG.logger.file[0]) { 24 | FILE *file; 25 | if(file = fopen(APP_CFG.logger.file, "a")) { 26 | vfprintf(file, format, params); 27 | fclose(file); 28 | } 29 | } 30 | va_end(params); 31 | } 32 | return result; 33 | } 34 | 35 | // Add message to log 36 | int logger(const char *file, const int line, const char *function, const int level, char *format, ...) { 37 | int result = 0; 38 | // Trim non-printable symbols 39 | char *frmt_buffer = prepare_string(format); 40 | // Date & Time 41 | time_t rawtime; 42 | char datetime[18]; 43 | memset(datetime, '\0', sizeof(datetime)); 44 | if(time(&rawtime) != -1) { 45 | struct tm *timeinfo; 46 | if(timeinfo = localtime(&rawtime)) { 47 | strftime(datetime, sizeof(datetime), "%x %X", timeinfo); 48 | } 49 | } 50 | // Nanoseconds 51 | struct timespec spec; 52 | clock_gettime(CLOCK_REALTIME, &spec); 53 | // Print message 54 | va_list params; 55 | va_start(params, format); 56 | char *message = ""; 57 | if(vasprintf(&message, frmt_buffer, params) != -1) { 58 | result = logger_write(level, "[%s.%09d] [%s:%d][%s]: %s\n", datetime, spec.tv_nsec, file, line, function, message); 59 | free(frmt_buffer); 60 | free(message); 61 | } 62 | va_end(params); 63 | 64 | return result; 65 | } 66 | 67 | // Hexdump of memory 68 | int logger_memory(const char *description, const int level, const void *address, const int length, int width) { 69 | // https://stackoverflow.com/a/7776146 70 | int result = 0; 71 | // Check length 72 | if(length <= 0) { 73 | result += logger_write(level, "[%s][%s]: incorrect length = %d!\n", "logger", "logger_memory", length); 74 | return result; 75 | } 76 | // Check width 77 | if(width < 4 || width > 64) width = 16; 78 | // Output description if given 79 | if(description != NULL) { 80 | result += logger_write(level, "%s:\n", description); 81 | } 82 | // Process every byte in the data 83 | int i; 84 | unsigned char buffer[width+1]; 85 | const unsigned char *chars = (const unsigned char *) address; 86 | for(i=0;i 0x7e)) { 100 | buffer[i % width] = '.'; 101 | } else { 102 | buffer[i % width] = chars[i]; 103 | } 104 | buffer[(i % width) + 1] = '\0'; 105 | } 106 | // Pad out last line if not exactly perLine characters 107 | while((i % width) != 0) { 108 | result += logger_write(level, " "); 109 | i++; 110 | } 111 | // And print the final ASCII buffer 112 | result += logger_write(level, " %s\n", buffer); 113 | return result; 114 | } 115 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SKIP_SHARED_LIBS = OFF 2 | 3 | CROSS_COMPILE = arm-himix100-linux- 4 | CCFLAGS = -march=armv7-a -mfpu=neon-vfpv4 -funsafe-math-optimizations 5 | LDPATH = /opt/hisi-linux/x86-arm/arm-himix100-linux/target/usr/app/lib 6 | 7 | CC = $(CROSS_COMPILE)gcc 8 | CXX = $(CROSS_COMPILE)g++ 9 | 10 | LDFLAGS = -pthread -llocalsdk -l_hiae -livp -live -lmpi -lmd -l_hiawb -lisp -lsecurec -lsceneauto -lVoiceEngine -lupvqe -l_hidehaze -l_hidrc -l_hildci -ldnvqe -lsns_f22 -lpaho-mqtt3c -lyyjson -lrtspserver -lstdc++ 11 | 12 | OUTPUT = ./bin 13 | LIBDIR = ./lib 14 | 15 | ############### 16 | # APPLICATION # 17 | ############### 18 | 19 | all: mkdirs mjsxj02hl 20 | 21 | mjsxj02hl: ./mjsxj02hl.c external-libs objects 22 | $(CC) $(CCFLAGS) -L$(LDPATH) ./mjsxj02hl.c $(OUTPUT)/objects/*.o $(OUTPUT)/objects/*.a $(LDFLAGS) -o $(OUTPUT)/mjsxj02hl 23 | 24 | ############## 25 | # PVS-STUDIO # 26 | ############## 27 | 28 | PVS_ANALYZER = GA:1,2,3 29 | PVS_TYPE = html 30 | 31 | analyze: 32 | pvs-studio-analyzer trace -- make SKIP_SHARED_LIBS=$(SKIP_SHARED_LIBS) 33 | pvs-studio-analyzer analyze --compiler $(CC) --compiler $(CXX) -e bin -e /opt -e /usr -e configs/inih -e mqtt/paho.mqtt.c -e rtsp/RtspServer -e yyjson -e ipctool 34 | plog-converter -a $(PVS_ANALYZER) -t $(PVS_TYPE) -d V1019 -o PVS-Studio.$(PVS_TYPE) PVS-Studio.log 35 | 36 | ################# 37 | # EXTERNAL LIBS # 38 | ################# 39 | 40 | ifeq ($(SKIP_SHARED_LIBS), OFF) 41 | external-libs: clean-libs mkdir-libs static-libs shared-libs install-libs 42 | else 43 | external-libs: clean-libs mkdir-libs static-libs install-libs 44 | endif 45 | 46 | clean-libs: 47 | -make clean OUTPUT="../$(OUTPUT)" LIBDIR="../$(LIBDIR)" -C ./rtsp 48 | -make clean -C $(OUTPUT)/objects/paho.mqtt.c 49 | -make clean -C $(OUTPUT)/objects/yyjson 50 | -make clean -C $(OUTPUT)/objects/ipctool ; rm -f $(OUTPUT)/ipctool 51 | -rm -rf $(LIBDIR)/* 52 | 53 | mkdir-libs: 54 | -mkdir -p $(LIBDIR) 55 | -mkdir -p $(OUTPUT)/objects/ipctool 56 | -mkdir -p $(OUTPUT)/objects/yyjson 57 | -mkdir -p $(OUTPUT)/objects/paho.mqtt.c 58 | -make BUILD_DIR OUTPUT="../$(OUTPUT)" LIBDIR="../$(LIBDIR)" -C ./rtsp 59 | 60 | update-libs: 61 | git pull --recurse-submodules 62 | git submodule update --remote --recursive 63 | 64 | static-libs: libipchw.a 65 | shared-libs: libyyjson.so libpaho-mqtt3c.so librtspserver.so 66 | 67 | install-libs: 68 | -cp -arf $(LIBDIR)/. $(LDPATH) 69 | 70 | libipchw.a: 71 | cmake -S./ipctool -B$(OUTPUT)/objects/ipctool -DCMAKE_C_COMPILER=$(CC) -DCMAKE_C_FLAGS="$(CCFLAGS)" -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release 72 | make -C $(OUTPUT)/objects/ipctool ipchw ipctool 73 | cp -f $(OUTPUT)/objects/ipctool/libipchw.a $(OUTPUT)/objects/ 74 | cp -f $(OUTPUT)/objects/ipctool/ipctool $(OUTPUT)/ 75 | 76 | libyyjson.so: 77 | cmake -S./yyjson -B$(OUTPUT)/objects/yyjson -DCMAKE_C_COMPILER=$(CC) -DCMAKE_C_FLAGS="$(CCFLAGS)" -DBUILD_SHARED_LIBS=ON 78 | make -C $(OUTPUT)/objects/yyjson 79 | cp -fP $(OUTPUT)/objects/yyjson/libyyjson.so* $(LIBDIR)/ 80 | 81 | libpaho-mqtt3c.so: 82 | cmake -S./mqtt/paho.mqtt.c -B$(OUTPUT)/objects/paho.mqtt.c -DCMAKE_C_COMPILER=$(CC) -DCMAKE_C_FLAGS="$(CCFLAGS)" 83 | make -C $(OUTPUT)/objects/paho.mqtt.c 84 | cp -fP $(OUTPUT)/objects/paho.mqtt.c/src/libpaho-mqtt3c.so* $(LIBDIR)/ 85 | 86 | librtspserver.so: 87 | make -C ./rtsp 88 | 89 | ####################### 90 | # APPLICATION OBJECTS # 91 | ####################### 92 | 93 | objects: logger.o init.o configs.o inih.o osd.o video.o audio.o speaker.o alarm.o night.o mqtt.o homeassistant.o rtsp.o 94 | 95 | logger.o: ./logger/logger.c 96 | $(CC) $(CCFLAGS) -c ./logger/logger.c -o $(OUTPUT)/objects/logger.o 97 | 98 | configs.o: ./configs/configs.c 99 | $(CC) $(CCFLAGS) -c ./configs/configs.c -o $(OUTPUT)/objects/configs.o 100 | 101 | inih.o: ./configs/inih/ini.c 102 | $(CC) $(CCFLAGS) -c ./configs/inih/ini.c -o $(OUTPUT)/objects/inih.o 103 | 104 | init.o: ./localsdk/init.c 105 | $(CC) $(CCFLAGS) -c ./localsdk/init.c -o $(OUTPUT)/objects/init.o 106 | 107 | osd.o: ./localsdk/osd/osd.c 108 | $(CC) $(CCFLAGS) -c ./localsdk/osd/osd.c -o $(OUTPUT)/objects/osd.o 109 | 110 | video.o: ./localsdk/video/video.c 111 | $(CC) $(CCFLAGS) -c ./localsdk/video/video.c -o $(OUTPUT)/objects/video.o 112 | 113 | audio.o: ./localsdk/audio/audio.c 114 | $(CC) $(CCFLAGS) -c ./localsdk/audio/audio.c -o $(OUTPUT)/objects/audio.o 115 | 116 | speaker.o: ./localsdk/speaker/speaker.c 117 | $(CC) $(CCFLAGS) -c ./localsdk/speaker/speaker.c -o $(OUTPUT)/objects/speaker.o 118 | 119 | alarm.o: ./localsdk/alarm/alarm.c 120 | $(CC) $(CCFLAGS) -c ./localsdk/alarm/alarm.c -o $(OUTPUT)/objects/alarm.o 121 | 122 | night.o: ./localsdk/night/night.c 123 | $(CC) $(CCFLAGS) -c ./localsdk/night/night.c -o $(OUTPUT)/objects/night.o 124 | 125 | mqtt.o: ./mqtt/mqtt.c 126 | $(CC) $(CCFLAGS) -c ./mqtt/mqtt.c -o $(OUTPUT)/objects/mqtt.o 127 | 128 | homeassistant.o: ./mqtt/homeassistant.c 129 | $(CC) $(CCFLAGS) -c ./mqtt/homeassistant.c -o $(OUTPUT)/objects/homeassistant.o 130 | 131 | rtsp.o: ./rtsp/rtsp.c 132 | $(CC) $(CCFLAGS) -c ./rtsp/rtsp.c -o $(OUTPUT)/objects/rtsp.o 133 | 134 | clean: 135 | -rm -rf $(OUTPUT)/* 136 | 137 | mkdirs: clean 138 | -mkdir -p $(OUTPUT)/objects 139 | -------------------------------------------------------------------------------- /localsdk/audio/audio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "./audio.h" 6 | #include "./../localsdk.h" 7 | #include "./../../logger/logger.h" 8 | #include "./../../configs/configs.h" 9 | #include "./../../rtsp/rtsp.h" 10 | 11 | // Audio capture callback 12 | static int g711_capture_callback(int chn, LOCALSDK_AUDIO_G711_FRAME_INFO *frameInfo) { 13 | int result = LOCALSDK_OK; 14 | if(frameInfo && frameInfo->size) { 15 | // RTSP 16 | if(rtsp_is_enabled(chn)) { 17 | if(!rtsp_media_frame(chn, frameInfo->data, frameInfo->size, frameInfo->timestamp, LOCALSDK_AUDIO_G711_FRAME)) { 18 | result = LOCALSDK_ERROR; 19 | } 20 | } 21 | } 22 | return result; 23 | } 24 | 25 | static int g711_capture_primary_channel(LOCALSDK_AUDIO_G711_FRAME_INFO *frameInfo) { 26 | if(audio_is_enabled(LOCALSDK_VIDEO_PRIMARY_CHANNEL)) { 27 | return g711_capture_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL, frameInfo); 28 | } else return LOCALSDK_ERROR; 29 | } 30 | 31 | static int g711_capture_secondary_channel(LOCALSDK_AUDIO_G711_FRAME_INFO *frameInfo) { 32 | if(audio_is_enabled(LOCALSDK_VIDEO_SECONDARY_CHANNEL)) { 33 | return g711_capture_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL, frameInfo); 34 | } else return LOCALSDK_ERROR; 35 | } 36 | 37 | // Is enabled 38 | bool audio_is_enabled(int channel) { 39 | switch(channel) { 40 | case LOCALSDK_VIDEO_PRIMARY_CHANNEL: return APP_CFG.audio.primary_enable; 41 | case LOCALSDK_VIDEO_SECONDARY_CHANNEL: return APP_CFG.audio.secondary_enable; 42 | default: return false; 43 | } 44 | } 45 | 46 | // Init audio 47 | bool audio_init() { 48 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 49 | bool result = true; 50 | 51 | if(result &= (local_sdk_audio_init() == LOCALSDK_OK)) { 52 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_init()"); 53 | // Init channel 0 54 | if(result &= (local_sdk_audio_create(LOCALSDK_AUDIO_CHANNEL) == LOCALSDK_OK)) { 55 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_create()"); 56 | LOCALSDK_AUDIO_OPTIONS audio_options = { 57 | .sample_rate = LOCALSDK_AUDIO_SAMPLE_RATE, 58 | .bit_depth = LOCALSDK_AUDIO_BIT_DEPTH, 59 | .unknown_2 = 25, // FIXME: what is it? 60 | .track_type = LOCALSDK_AUDIO_TRACK_TYPE, 61 | .unknown_4 = 0, // FIXME: what is it? 62 | .unknown_5 = 2, // FIXME: what is it? 63 | .unknown_6 = 1, // FIXME: what is it? 64 | .unknown_7 = 1, // FIXME: what is it? 65 | .unknown_8 = 2, // FIXME: what is it? 66 | .unknown_9 = 20, // FIXME: what is it? 67 | .volume = APP_CFG.audio.volume, 68 | .pcm_buffer_size = LOCALSDK_AUDIO_PCM_BUFFER_SIZE, 69 | .g711_buffer_size = LOCALSDK_AUDIO_G711_BUFFER_SIZE, 70 | }; 71 | if(result &= (local_sdk_audio_set_parameters(LOCALSDK_AUDIO_CHANNEL, &audio_options) == LOCALSDK_OK)) { 72 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_set_parameters()"); 73 | if(result &= (local_sdk_audio_set_encode_frame_callback(LOCALSDK_AUDIO_CHANNEL, g711_capture_primary_channel) == LOCALSDK_OK)) { 74 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_set_encode_frame_callback(g711_capture_primary_channel)"); 75 | if(result &= (local_sdk_audio_set_encode_frame_callback(LOCALSDK_AUDIO_CHANNEL, g711_capture_secondary_channel) == LOCALSDK_OK)) { 76 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_set_encode_frame_callback(g711_capture_secondary_channel)"); 77 | if(result &= (local_sdk_audio_start() == LOCALSDK_OK)) { 78 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_start()"); 79 | if(result &= (local_sdk_audio_run() == LOCALSDK_OK)) { 80 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_run()"); 81 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_run()"); 82 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_start()"); 83 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_set_encode_frame_callback(g711_capture_secondary_channel)"); 84 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_set_encode_frame_callback(g711_capture_primary_channel)"); 85 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_set_parameters()"); 86 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_create()"); 87 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_audio_init()"); 88 | 89 | // Free alarm if error occurred 90 | if(!result) { 91 | if(audio_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "audio_free()"); 92 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "audio_free()"); 93 | } 94 | 95 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 96 | return result; 97 | } 98 | 99 | // Free audio 100 | bool audio_free() { 101 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 102 | bool result = true; 103 | 104 | // Stop audio 105 | if(result &= (local_sdk_audio_stop() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_stop()"); 106 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_audio_stop()"); 107 | 108 | // End audio 109 | if(result &= (local_sdk_audio_end() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_end()"); 110 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_audio_end()"); 111 | 112 | // Destory audio 113 | if(result &= (local_sdk_audio_destory() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_audio_destory()"); 114 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_audio_destory()"); 115 | 116 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 117 | return result; 118 | } 119 | -------------------------------------------------------------------------------- /localsdk/speaker/speaker.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "./speaker.h" 11 | #include "./../localsdk.h" 12 | #include "./../../logger/logger.h" 13 | #include "./../../configs/configs.h" 14 | 15 | int playback_status = SPEAKER_MEDIA_STOPPED; 16 | 17 | // Init speaker 18 | bool speaker_init() { 19 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 20 | bool result = true; 21 | 22 | if(result &= (local_sdk_speaker_init() == LOCALSDK_OK)) { 23 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_init()"); 24 | 25 | LOCALSDK_SPEAKER_OPTIONS speaker_options = { 26 | .sample_rate = LOCALSDK_SPEAKER_SAMPLE_RATE, 27 | .bit_depth = LOCALSDK_SPEAKER_BIT_DEPTH, 28 | .unknown_2 = 25, // FIXME: what is it? 29 | .track_type = LOCALSDK_SPEAKER_TRACK_TYPE, 30 | .unknown_4 = 30, // FIXME: what is it? 31 | .volume = APP_CFG.speaker.volume, 32 | .buffer_size = LOCALSDK_AUDIO_PCM_BUFFER_SIZE, 33 | .unknown_7 = 1, // FIXME: what is it? 34 | }; 35 | 36 | if(result &= (local_sdk_speaker_set_parameters(&speaker_options) == LOCALSDK_OK)) { 37 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_set_parameters()"); 38 | if(result &= (local_sdk_speaker_start() == LOCALSDK_OK)) { 39 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_start()"); 40 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_speaker_start()"); 41 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_speaker_set_parameters()"); 42 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_speaker_init()"); 43 | 44 | // Free speaker if error occurred 45 | if(!result) { 46 | if(speaker_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_free()"); 47 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "speaker_free()"); 48 | } 49 | 50 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 51 | return result; 52 | } 53 | 54 | // Free speaker 55 | bool speaker_free() { 56 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 57 | LOGGER(LOGGER_LEVEL_DEBUG, "This function is a stub."); 58 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", "true"); 59 | return true; 60 | } 61 | 62 | // Play media (WAV, 8000 hz, 16-bit, mono) 63 | bool speaker_play_media(char *filename, int type) { 64 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 65 | bool result = true; 66 | 67 | LOGGER(LOGGER_LEVEL_INFO, "Filename: %s", filename); 68 | LOGGER(LOGGER_LEVEL_INFO, "Type: %d", type); 69 | 70 | // Stop current playing 71 | if(speaker_status_media() != SPEAKER_MEDIA_STOPPED) { 72 | if(speaker_stop_media()) { 73 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_stop_media()"); 74 | while(speaker_status_media() != SPEAKER_MEDIA_STOPPED) { usleep(100000); } 75 | } 76 | } 77 | playback_status = SPEAKER_MEDIA_PLAYING; 78 | 79 | // Feed file data 80 | FILE *media = fopen(filename, "rb"); 81 | if(result &= (media != NULL)) { 82 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "fopen()"); 83 | 84 | if(type != LOCALSDK_SPEAKER_G711_TYPE) { // Skip head 85 | fseek(media, 44, SEEK_SET); //-V575 86 | } 87 | 88 | if(result &= (local_sdk_speaker_clean_buf_data() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_clean_buf_data()"); 89 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_speaker_clean_buf_data()"); 90 | 91 | int error_counter = 0; 92 | int buffer_size = LOCALSDK_AUDIO_PCM_BUFFER_SIZE; 93 | if(type == LOCALSDK_SPEAKER_G711_TYPE) { buffer_size = LOCALSDK_AUDIO_G711_BUFFER_SIZE; } 94 | 95 | while(!feof(media)) { 96 | error_counter = 0; 97 | char *buffer = malloc(buffer_size); 98 | if(buffer != NULL) { 99 | size_t length = fread(buffer, 1, buffer_size, media); 100 | while(true) { 101 | if(type == LOCALSDK_SPEAKER_G711_TYPE) { 102 | if(local_sdk_speaker_feed_g711_data(buffer, length) == LOCALSDK_OK || (error_counter >= 300)) break; 103 | } else { 104 | if(local_sdk_speaker_feed_pcm_data(buffer, length) == LOCALSDK_OK || (error_counter >= 300)) break; 105 | } 106 | usleep(100000); 107 | error_counter++; 108 | if(speaker_status_media() == SPEAKER_MEDIA_STOPPED) playback_status = SPEAKER_MEDIA_STOPPING; 109 | if(speaker_status_media() == SPEAKER_MEDIA_STOPPING) break; 110 | } 111 | free(buffer); 112 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "malloc(buffer_size)"); 113 | if(speaker_status_media() == SPEAKER_MEDIA_STOPPED) playback_status = SPEAKER_MEDIA_STOPPING; 114 | if(speaker_status_media() == SPEAKER_MEDIA_STOPPING) break; 115 | } 116 | if(error_counter >= 300) { 117 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "error_counter (>= 300)"); 118 | result &= false; 119 | } 120 | if(result &= (local_sdk_speaker_finish_buf_data() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_finish_buf_data()"); 121 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_speaker_finish_buf_data()"); 122 | fclose(media); 123 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "fopen()"); 124 | 125 | playback_status = SPEAKER_MEDIA_STOPPED; 126 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 127 | return result; 128 | } 129 | 130 | // Get playback status 131 | int speaker_status_media() { 132 | return playback_status; 133 | } 134 | 135 | // Stop playback 136 | bool speaker_stop_media() { 137 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 138 | bool result = true; 139 | 140 | if(result &= (speaker_status_media() != SPEAKER_MEDIA_STOPPED)) { 141 | playback_status = SPEAKER_MEDIA_STOPPING; 142 | } 143 | 144 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 145 | return result; 146 | } 147 | 148 | // Set volume 149 | bool speaker_set_volume(int value) { 150 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 151 | bool result = true; 152 | 153 | LOGGER(LOGGER_LEVEL_INFO, "Volume: %d", value); 154 | if(result &= (local_sdk_speaker_set_volume(value) == LOCALSDK_OK)) { 155 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_speaker_set_volume()"); 156 | APP_CFG.speaker.volume = value; 157 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_speaker_set_volume()"); 158 | 159 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 160 | return result; 161 | } 162 | 163 | // Get volume 164 | int speaker_get_volume() { 165 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 166 | int value = APP_CFG.speaker.volume; 167 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (volume = %d).", value); 168 | return value; 169 | } 170 | -------------------------------------------------------------------------------- /localsdk/osd/osd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "./osd.h" 7 | #include "./../localsdk.h" 8 | #include "./../../logger/logger.h" 9 | #include "./../../configs/configs.h" 10 | 11 | static pthread_t datetime_thread; 12 | 13 | // One-second timer for pthread 14 | static void *osd_datetime_timer(void *args) { 15 | while(true) { 16 | time_t current_time = time(NULL); 17 | struct tm *timestamp = localtime(¤t_time); 18 | if(local_sdk_video_osd_update_timestamp(LOCALSDK_VIDEO_PRIMARY_CHANNEL, true, timestamp) != LOCALSDK_OK) { 19 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_timestamp(true)"); 20 | } 21 | sleep(1); 22 | pthread_testcancel(); 23 | } 24 | 25 | if(local_sdk_video_osd_update_timestamp(LOCALSDK_VIDEO_PRIMARY_CHANNEL, false, NULL) != LOCALSDK_OK) { 26 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_timestamp(false)"); 27 | } 28 | } 29 | 30 | // Is enabled 31 | bool osd_is_enabled() { 32 | return APP_CFG.osd.enable; 33 | } 34 | 35 | // Init OSD 36 | bool osd_init() { 37 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 38 | bool result = true; 39 | 40 | if(osd_is_enabled()) { 41 | // Init primary channel (not work for secondary channel) 42 | LOCALSDK_OSD_OPTIONS osd_primary_options = { 43 | .unknown = 67, // FIXME: what is it? 44 | .datetime_x = APP_CFG.osd.datetime_x, 45 | .datetime_y = APP_CFG.osd.datetime_y, 46 | .datetime_reduce = ((APP_CFG.osd.datetime_size < 0) ? abs(APP_CFG.osd.datetime_size)+1 : 1), 47 | .datetime_increase = ((APP_CFG.osd.datetime_size > 0) ? APP_CFG.osd.datetime_size+1 : 1), 48 | .oemlogo_x = APP_CFG.osd.oemlogo_x, 49 | .oemlogo_y = APP_CFG.osd.oemlogo_y, 50 | .oemlogo_reduce = ((APP_CFG.osd.oemlogo_size < 0) ? abs(APP_CFG.osd.oemlogo_size)+1 : 1), 51 | .oemlogo_increase = ((APP_CFG.osd.oemlogo_size > 0) ? APP_CFG.osd.oemlogo_size+1 : 1), 52 | }; 53 | if(result &= (local_sdk_video_osd_set_parameters(LOCALSDK_VIDEO_PRIMARY_CHANNEL, &osd_primary_options) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_osd_set_parameters()"); 54 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_osd_set_parameters()"); 55 | 56 | // Free OSD if error occurred 57 | if(!result) { 58 | if(osd_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "osd_free()"); 59 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "osd_free()"); 60 | } 61 | } else LOGGER(LOGGER_LEVEL_INFO, "OSD is disabled in the settings."); 62 | 63 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 64 | return result; 65 | } 66 | 67 | // Init OSD after video init 68 | bool osd_postinit() { 69 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 70 | bool result = true; 71 | 72 | if(osd_is_enabled()) { 73 | // Display OEM logo (MI) 74 | if(APP_CFG.osd.oemlogo) { 75 | if(result &= (local_sdk_video_osd_update_logo(LOCALSDK_VIDEO_PRIMARY_CHANNEL, true) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_osd_update_logo(true)"); 76 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_osd_update_logo(true)"); 77 | } 78 | 79 | // Display date and time 80 | if(APP_CFG.osd.datetime) { 81 | if(result &= (pthread_create(&datetime_thread, NULL, osd_datetime_timer, NULL) == 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_create(datetime_thread)"); 82 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "pthread_create(datetime_thread)"); 83 | } 84 | 85 | // For rectangles see osd_rectangles_callback() 86 | } 87 | 88 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 89 | return result; 90 | } 91 | 92 | // Free OSD 93 | bool osd_free() { 94 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 95 | bool result = true; 96 | 97 | if(osd_is_enabled()) { 98 | // OEM logo 99 | if(APP_CFG.osd.oemlogo) { 100 | // Hide 101 | if(result &= (local_sdk_video_osd_update_logo(LOCALSDK_VIDEO_PRIMARY_CHANNEL, false) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_osd_update_logo(false)"); 102 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_logo(false)"); 103 | } 104 | // Date and time 105 | if(APP_CFG.osd.datetime) { 106 | // Stop datetime thread 107 | if(datetime_thread) { 108 | if(result &= (pthread_cancel(datetime_thread) == 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_cancel(datetime_thread)"); 109 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "pthread_cancel(datetime_thread)"); 110 | } 111 | // Hide 112 | if(result &= (local_sdk_video_osd_update_timestamp(LOCALSDK_VIDEO_PRIMARY_CHANNEL, false, NULL) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_osd_update_timestamp(false)"); 113 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_timestamp(false)"); 114 | } 115 | // Rectangle(s) 116 | if(APP_CFG.osd.motion || APP_CFG.osd.humanoid) { 117 | // Hide 118 | if(result &= (local_sdk_video_osd_update_rect_multi(LOCALSDK_VIDEO_PRIMARY_CHANNEL, false, NULL) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_osd_update_rect_multi(false)"); 119 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_rect_multi(false)"); 120 | } 121 | } 122 | 123 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 124 | return result; 125 | } 126 | 127 | // Rectangles callback 128 | int osd_rectangles_callback(LOCALSDK_ALARM_EVENT_INFO *eventInfo) { 129 | if(osd_is_enabled()) { 130 | if(APP_CFG.osd.motion || APP_CFG.osd.humanoid) { 131 | LOCALSDK_OSD_RECTANGLES rectangles; 132 | rectangles.count = 0; 133 | for(int i=0;iobjects[i].type == LOCALSDK_ALARM_TYPE_MOTION) 135 | || 136 | (APP_CFG.osd.humanoid && eventInfo->objects[i].type == LOCALSDK_ALARM_TYPE_HUMANOID) 137 | ) { 138 | if(eventInfo->objects[i].state) { 139 | rectangles.count++; 140 | rectangles.objects[rectangles.count-1].x = eventInfo->objects[i].x; 141 | rectangles.objects[rectangles.count-1].y = eventInfo->objects[i].y; 142 | rectangles.objects[rectangles.count-1].width = eventInfo->objects[i].width; 143 | rectangles.objects[rectangles.count-1].height = eventInfo->objects[i].height; 144 | rectangles.objects[rectangles.count-1].unknown = 1; 145 | if(eventInfo->objects[i].type == LOCALSDK_ALARM_TYPE_HUMANOID) { 146 | rectangles.objects[rectangles.count-1].color = LOCALSDK_OSD_COLOR_ORANGE; 147 | } else { 148 | rectangles.objects[rectangles.count-1].color = LOCALSDK_OSD_COLOR_GREEN; 149 | } 150 | } 151 | } 152 | } 153 | if(local_sdk_video_osd_update_rect_multi(LOCALSDK_VIDEO_PRIMARY_CHANNEL, true, &rectangles) != LOCALSDK_OK) { 154 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_osd_update_rect_multi(true)"); 155 | } 156 | } 157 | } 158 | return LOCALSDK_OK; 159 | } 160 | -------------------------------------------------------------------------------- /localsdk/init.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "./init.h" 13 | #include "./localsdk.h" 14 | #include "./osd/osd.h" 15 | #include "./video/video.h" 16 | #include "./audio/audio.h" 17 | #include "./speaker/speaker.h" 18 | #include "./alarm/alarm.h" 19 | #include "./night/night.h" 20 | #include "./../logger/logger.h" 21 | #include "./../configs/configs.h" 22 | #include "./../ipctool/src/tools.h" 23 | 24 | // Read file into string 25 | static char *get_file_contents(char *filename) { 26 | char *file_contents = ""; 27 | FILE *file_stream; 28 | if(file_stream = fopen(filename, "r")) { 29 | if(fseek(file_stream, 0, SEEK_END) != -1) { 30 | long contents_size = ftell(file_stream) + 1; 31 | if(fseek(file_stream, 0, SEEK_SET) != -1) { 32 | if(file_contents = malloc(contents_size)) { 33 | memset(file_contents, '\0', contents_size); 34 | fread(file_contents, 1, contents_size, file_stream); 35 | if(ferror(file_stream)) LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "fread()"); 36 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "malloc()"); 37 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "fseek(SEEK_SET)"); 38 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "fseek(SEEK_END)"); 39 | fclose(file_stream); 40 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "fopen()"); 41 | return file_contents; 42 | } 43 | 44 | // Removes all non-printable characters from string 45 | char *prepare_string(char *string) { 46 | char *prepared_string = ""; 47 | if(string != NULL) { 48 | size_t length = strlen(string) + 1; 49 | if(prepared_string = malloc(length)) { 50 | memset(prepared_string, '\0', length); 51 | size_t j = 0; 52 | for(size_t i = 0; i= 0x12020400; address -= 4) { 78 | uint32_t value; 79 | if(!mem_reg(address, &value, OP_READ)) break; 80 | size += snprintf(buffer + size, length - size, "%08x", value); 81 | } 82 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "malloc()"); 83 | return buffer; 84 | } 85 | 86 | // Log printf function 87 | static int logprintf(const char *format, ...) { 88 | int result = 0; 89 | va_list params; 90 | va_start(params, format); 91 | char *message = ""; 92 | if(vasprintf(&message, format, params) != -1) { 93 | result = LOGGER(LOGGER_LEVEL_DEBUG, message); 94 | free(message); 95 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "vasprintf(message)"); 96 | va_end(params); 97 | return result; 98 | } 99 | 100 | // Init all 101 | bool all_init() { 102 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 103 | bool result = true; 104 | 105 | if(result &= (localsdk_set_logprintf_func(logprintf) == LOCALSDK_OK)) { 106 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "localsdk_set_logprintf_func()"); 107 | //if(result &= (localsdk_set_shellcall_func(shellcall_func) == LOCALSDK_OK)) { // FIXME 108 | //LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "localsdk_set_shellcall_func()"); 109 | if(result &= (localsdk_init() == LOCALSDK_OK)) { 110 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "localsdk_init()"); 111 | if(result &= (localsdk_get_version() == LOCALSDK_CURRENT_VERSION)) { 112 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "video_init()"); 113 | if(result &= osd_init()) { 114 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "localsdk_get_version()"); 115 | if(result &= video_init()) { 116 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "osd_init()"); 117 | if(result &= audio_init()) { 118 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "audio_init()"); 119 | if(result &= speaker_init()) { 120 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_init()"); 121 | if(result &= alarm_init()) { 122 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_init()"); 123 | if(result &= night_init()) { 124 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "night_init()"); 125 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "night_init()"); 126 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "alarm_init()"); 127 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "speaker_init()"); 128 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "audio_init()"); 129 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "video_init()"); 130 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "osd_init()"); 131 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "localsdk_get_version()"); 132 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "localsdk_init()"); 133 | //} else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "localsdk_set_shellcall_func()"); 134 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "localsdk_set_logprintf_func()"); 135 | 136 | // Free all if error occurred 137 | if(!result) { 138 | if(all_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "all_free()"); 139 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "all_free()"); 140 | } 141 | 142 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 143 | return result; 144 | } 145 | 146 | // Free all 147 | bool all_free() { 148 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 149 | bool result = true; 150 | 151 | // Free night mode 152 | if(result &= night_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "night_free()"); 153 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 154 | 155 | // Free alarm 156 | if(result &= alarm_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_free()"); 157 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 158 | 159 | // Free speaker 160 | if(result &= speaker_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_free()"); 161 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 162 | 163 | // Free audio 164 | if(result &= audio_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "audio_free()"); 165 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 166 | 167 | // Free OSD 168 | if(result &= osd_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "osd_free()"); 169 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 170 | 171 | // Free video 172 | if(result &= video_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "video_free()"); 173 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 174 | 175 | // Free localsdk 176 | if(result &= (localsdk_destory() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "localsdk_destory()"); 177 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "localsdk_get_version()"); 178 | 179 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 180 | return result; 181 | } 182 | -------------------------------------------------------------------------------- /rtsp/rtsp.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "./rtsp.h" 10 | #include "./libRtspServer.h" 11 | #include "./../localsdk/localsdk.h" 12 | #include "./../localsdk/video/video.h" 13 | #include "./../localsdk/audio/audio.h" 14 | #include "./../logger/logger.h" 15 | #include "./../configs/configs.h" 16 | 17 | static uint32_t primary_session = 0; 18 | static uint32_t secondary_session = 0; 19 | 20 | // Logger function for libRtspServer 21 | static int librtspserver_logger(const char *format, ...) { 22 | int result = 0; 23 | va_list params; 24 | va_start(params, format); 25 | char *message = ""; 26 | if(vasprintf(&message, format, params) != -1) { 27 | result = LOGGER(LOGGER_LEVEL_INFO, message); 28 | free(message); 29 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "vasprintf(message)"); 30 | va_end(params); 31 | return result; 32 | } 33 | 34 | // Connected callback function for libRtspServer 35 | static void librtspserver_connected(uint32_t session_id, const char *peer_ip, uint16_t peer_port) { 36 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 37 | 38 | int channel = (session_id == secondary_session ? LOCALSDK_VIDEO_SECONDARY_CHANNEL : LOCALSDK_VIDEO_PRIMARY_CHANNEL); 39 | if(local_sdk_video_force_I_frame(channel) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_force_I_frame()"); 40 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_force_I_frame()"); 41 | 42 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 43 | } 44 | 45 | // LocalSDK video type to libRtspServer type 46 | static uint8_t librtspserver_video_type(uint8_t type) { 47 | switch(type) { 48 | case LOCALSDK_VIDEO_PAYLOAD_H264: return LIBRTSPSERVER_TYPE_H264; 49 | case LOCALSDK_VIDEO_PAYLOAD_H265: return LIBRTSPSERVER_TYPE_H265; 50 | default: return LIBRTSPSERVER_TYPE_NONE; 51 | } 52 | } 53 | 54 | // LocalSDK frame type to libRtspServer type 55 | static uint8_t librtspserver_frame_type(uint8_t type) { 56 | switch(type) { 57 | case LOCALSDK_VIDEO_H26X_FRAME_I: return LIBRTSPSERVER_VIDEO_FRAME_I; 58 | case LOCALSDK_VIDEO_H26X_FRAME_P: return LIBRTSPSERVER_VIDEO_FRAME_P; 59 | case LOCALSDK_AUDIO_G711_FRAME: return LIBRTSPSERVER_AUDIO_FRAME; 60 | default: return LIBRTSPSERVER_UNKNOWN_FRAME; 61 | } 62 | } 63 | 64 | // Is enabled 65 | bool rtsp_is_enabled(int channel) { 66 | switch(channel) { 67 | case LOCALSDK_VIDEO_PRIMARY_CHANNEL: 68 | return (APP_CFG.rtsp.enable && (APP_CFG.rtsp.primary_name && APP_CFG.rtsp.primary_name[0])); 69 | case LOCALSDK_VIDEO_SECONDARY_CHANNEL: 70 | return (APP_CFG.rtsp.enable && (APP_CFG.rtsp.secondary_name && APP_CFG.rtsp.secondary_name[0])); 71 | default: 72 | return (APP_CFG.rtsp.enable && (rtsp_is_enabled(LOCALSDK_VIDEO_PRIMARY_CHANNEL) || rtsp_is_enabled(LOCALSDK_VIDEO_SECONDARY_CHANNEL))); 73 | } 74 | } 75 | 76 | // Init RTSP 77 | bool rtsp_init() { 78 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 79 | bool result = true; 80 | 81 | if(rtsp_is_enabled(-1)) { // If RTSP enabled 82 | if(result &= rtspserver_logprintf(librtspserver_logger)) { 83 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_logprintf()"); 84 | if(result &= rtspserver_create(APP_CFG.rtsp.port, APP_CFG.rtsp.username, APP_CFG.rtsp.password)) { 85 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_create()"); 86 | if(result &= rtspserver_connected(librtspserver_connected)) { 87 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_connected()"); 88 | 89 | // Primary channel 90 | bool primary_result = false; 91 | if(rtsp_is_enabled(LOCALSDK_VIDEO_PRIMARY_CHANNEL)) { 92 | char *primary_name = APP_CFG.rtsp.primary_name; 93 | bool primary_multicast = APP_CFG.rtsp.primary_multicast; 94 | uint8_t primary_video_type = librtspserver_video_type(APP_CFG.video.primary_type); 95 | uint8_t primary_audio_type = (audio_is_enabled(LOCALSDK_VIDEO_PRIMARY_CHANNEL) ? LIBRTSPSERVER_TYPE_G711A : LIBRTSPSERVER_TYPE_NONE); 96 | if((primary_session = rtspserver_session(primary_name, primary_multicast, primary_video_type, LOCALSDK_VIDEO_FRAMERATE, primary_audio_type, 0, 0, false))) { 97 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_session(primary)"); 98 | primary_result = true; 99 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "rtspserver_session(primary)"); 100 | } else LOGGER(LOGGER_LEVEL_INFO, "%s channel is disabled in the settings or its name is not set.", "Primary"); 101 | 102 | // Secondary channel 103 | bool secondary_result = false; 104 | if(rtsp_is_enabled(LOCALSDK_VIDEO_SECONDARY_CHANNEL)) { 105 | char *secondary_name = APP_CFG.rtsp.secondary_name; 106 | bool secondary_multicast = APP_CFG.rtsp.secondary_multicast; 107 | uint8_t secondary_video_type = librtspserver_video_type(APP_CFG.video.secondary_type); 108 | uint8_t secondary_audio_type = (audio_is_enabled(LOCALSDK_VIDEO_SECONDARY_CHANNEL) ? LIBRTSPSERVER_TYPE_G711A : LIBRTSPSERVER_TYPE_NONE); 109 | if((secondary_session = rtspserver_session(secondary_name, secondary_multicast, secondary_video_type, LOCALSDK_VIDEO_FRAMERATE, secondary_audio_type, 0, 0, false))) { 110 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_session(secondary)"); 111 | secondary_result = true; 112 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "rtspserver_session(secondary)"); 113 | } else LOGGER(LOGGER_LEVEL_INFO, "%s channel is disabled in the settings or its name is not set.", "Secondary"); 114 | 115 | // Results 116 | result &= (primary_result || secondary_result); 117 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "rtspserver_connected()"); 118 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "rtspserver_create()"); 119 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "rtspserver_logprintf()"); 120 | } else LOGGER(LOGGER_LEVEL_INFO, "RTSP server is disabled in the settings."); 121 | 122 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 123 | return result; 124 | } 125 | 126 | // Free RTSP 127 | bool rtsp_free() { 128 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 129 | bool result = true; 130 | 131 | if(rtsp_is_enabled(-1)) { // If RTSP enabled 132 | if(result &= rtspserver_free(2, primary_session, secondary_session)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtspserver_free()"); 133 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "rtspserver_free()"); 134 | } 135 | 136 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 137 | return result; 138 | } 139 | 140 | // Send data frame 141 | bool rtsp_media_frame(int channel, signed char *data, size_t size, uint32_t timestamp, uint8_t type) { 142 | bool result = false; 143 | if(rtsp_is_enabled(channel)) { // If RTSP enabled 144 | // Get current timestamp 145 | if(type == LOCALSDK_AUDIO_G711_FRAME) { 146 | timestamp = rtspserver_timestamp(LIBRTSPSERVER_TYPE_G711A, 0); 147 | } else { 148 | if( 149 | ((channel == LOCALSDK_VIDEO_PRIMARY_CHANNEL) && (APP_CFG.video.primary_type == LOCALSDK_VIDEO_PAYLOAD_H264)) 150 | || 151 | ((channel == LOCALSDK_VIDEO_SECONDARY_CHANNEL) && (APP_CFG.video.secondary_type == LOCALSDK_VIDEO_PAYLOAD_H264)) 152 | ) { 153 | timestamp = rtspserver_timestamp(LIBRTSPSERVER_TYPE_H264, 0); 154 | } else if( 155 | ((channel == LOCALSDK_VIDEO_PRIMARY_CHANNEL) && (APP_CFG.video.primary_type == LOCALSDK_VIDEO_PAYLOAD_H265)) 156 | || 157 | ((channel == LOCALSDK_VIDEO_SECONDARY_CHANNEL) && (APP_CFG.video.secondary_type == LOCALSDK_VIDEO_PAYLOAD_H265)) 158 | ) { 159 | timestamp = rtspserver_timestamp(LIBRTSPSERVER_TYPE_H265, 0); 160 | } 161 | } 162 | // Split video frames into separate packets 163 | bool split_video = (channel == LOCALSDK_VIDEO_SECONDARY_CHANNEL ? APP_CFG.rtsp.secondary_split_vframes : APP_CFG.rtsp.primary_split_vframes); 164 | // Send frame 165 | result = rtspserver_frame((channel == LOCALSDK_VIDEO_SECONDARY_CHANNEL ? secondary_session : primary_session), data, librtspserver_frame_type(type), size, timestamp, split_video); 166 | } 167 | return result; 168 | } 169 | 170 | -------------------------------------------------------------------------------- /mjsxj02hl.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "./logger/logger.h" 14 | #include "./localsdk/localsdk.h" 15 | #include "./localsdk/init.h" 16 | #include "./configs/configs.h" 17 | #include "./mqtt/mqtt.h" 18 | #include "./rtsp/rtsp.h" 19 | 20 | // Signal callback 21 | void signal_callback(int signal) { 22 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 23 | 24 | // Enable orange LED 25 | if(APP_CFG.general.led) { 26 | if(local_sdk_indicator_led_option(true, false) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_indicator_led_option(true, false)"); 27 | else { 28 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_indicator_led_option(true, false)"); 29 | signal = EX_SOFTWARE; 30 | } 31 | } 32 | 33 | // MQTT free 34 | if(mqtt_free(true)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_free(true)"); 35 | else { 36 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "mqtt_free(true)"); 37 | signal = EX_SOFTWARE; 38 | } 39 | 40 | // RTSP free 41 | if(rtsp_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtsp_free()"); 42 | else { 43 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "rtsp_free()"); 44 | signal = EX_SOFTWARE; 45 | } 46 | 47 | // All free 48 | if(all_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "all_free()"); 49 | else { 50 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "all_free()"); 51 | signal = EX_SOFTWARE; 52 | } 53 | 54 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (signal = %d).", signal); 55 | exit(signal); 56 | } 57 | 58 | // Factory reset callback 59 | int factory_reset_callback() { 60 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 61 | int result = LOCALSDK_OK; 62 | 63 | if(system("mjsxj02hl --factory-reset") == EX_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(mjsxj02hl --factory-reset)"); 64 | else { 65 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(mjsxj02hl --factory-reset)"); 66 | result = LOCALSDK_ERROR; 67 | } 68 | 69 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result == LOCALSDK_OK ? "LOCALSDK_OK" : "LOCALSDK_ERROR")); 70 | return result; 71 | } 72 | 73 | // Main function 74 | int main(int argc, char **argv) { 75 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 76 | 77 | // Initialize pseudo-random number generator 78 | srand(time(NULL)); 79 | 80 | // Default path of config file 81 | char *config_filename = "/usr/app/share/mjsxj02hl.conf"; 82 | 83 | // Running with arguments 84 | if(argc > 1) { 85 | if(strcmp(argv[1], "--config") == 0) { // Set config path 86 | if(argc == 3) { 87 | config_filename = argv[2]; 88 | } else { 89 | printf("Error: missing filename! Use the --help option for more information.\n"); 90 | return EX_USAGE; 91 | } 92 | } else if(strcmp(argv[1], "--factory-reset") == 0) { // Factory reset 93 | printf("Reset to factory settings...\n"); 94 | system("rm -rf /configs/*"); 95 | system("reboot"); 96 | return EX_OK; 97 | } else if(strcmp(argv[1], "--get-image") == 0) { // Get image 98 | if(argc == 3) { 99 | if(system("pidof -o %PPID mjsxj02hl > /dev/null") == EX_OK) { 100 | if(local_sdk_video_get_jpeg(LOCALSDK_VIDEO_SECONDARY_CHANNEL, argv[2]) == LOCALSDK_OK) { 101 | return EX_OK; 102 | } else { 103 | printf("Error: local_sdk_video_get_jpeg() failed!\n"); 104 | return EX_SOFTWARE; 105 | } 106 | } else { 107 | printf("Error: main thread of mjsxj02hl application is not running!\n"); 108 | return EX_UNAVAILABLE; 109 | } 110 | } else { 111 | printf("Error: missing filename! Use the --help option for more information.\n"); 112 | return EX_USAGE; 113 | } 114 | } else if(strcmp(argv[1], "--help") == 0) { // Help 115 | printf("Usage: mjsxj02hl [ [options...]]\n"); 116 | printf("\n"); 117 | printf("Running without arguments starts the main thread of the application.\n"); 118 | printf("\n"); 119 | printf(" --config Specify the location of the configuration file for the main thread of application.\n"); 120 | printf("\n"); 121 | printf(" --factory-reset Reset device settings to default values. Attention: this action cannot be undone!\n"); 122 | printf("\n"); 123 | printf(" --get-image Output the camera image to a file. Requires a running main thread of mjsxj02hl application.\n"); 124 | printf("\n"); 125 | printf(" --help Display this message.\n"); 126 | printf("\n"); 127 | printf("Report an error or help in the development of the project you can on the page %s\n", "https://github.com/kasitoru/mjsxj02hl_application"); 128 | return EX_OK; 129 | } else { 130 | printf("Error: unknown action! Use the --help option for more information.\n"); 131 | return EX_USAGE; 132 | } 133 | } 134 | 135 | // Firmware version 136 | char *fw_ver = firmware_version(); 137 | LOGGER(LOGGER_LEVEL_FORCED, "Firmware version: %s", fw_ver); 138 | free(fw_ver); 139 | 140 | // Device id 141 | char *dev_id = device_id(); 142 | LOGGER(LOGGER_LEVEL_FORCED, "Device ID: %s", dev_id); 143 | free(dev_id); 144 | 145 | // Main thread 146 | if(configs_init(config_filename)) { // Init configs 147 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "configs_init()"); 148 | if(all_init()) { // Init all systems 149 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "all_init()"); 150 | 151 | // Register signals 152 | if(signal(SIGINT, signal_callback) != SIG_ERR) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "signal(SIGINT)"); // SIGINT 153 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "signal(SIGINT)"); 154 | 155 | if(signal(SIGABRT, signal_callback) != SIG_ERR) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "signal(SIGABRT)"); // SIGABRT 156 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "signal(SIGABRT)"); 157 | 158 | if(signal(SIGTERM, signal_callback) != SIG_ERR) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "signal(SIGTERM)"); // SIGTERM 159 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "signal(SIGTERM)"); 160 | 161 | // Onboard LED indicator 162 | if(APP_CFG.general.led) { // Enable blue LED 163 | if(local_sdk_indicator_led_option(false, true) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_indicator_led_option(false, true)"); 164 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_indicator_led_option()"); 165 | } else { // Disable LEDs 166 | if(local_sdk_indicator_led_option(false, false) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_indicator_led_option(false, false)"); 167 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_indicator_led_option()"); 168 | } 169 | 170 | // Factory reset callback 171 | if(local_sdk_setup_keydown_set_callback(3000, factory_reset_callback) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_setup_keydown_set_callback()"); 172 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_setup_keydown_set_callback()"); 173 | 174 | // RTSP server 175 | if(rtsp_init()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "rtsp_init()"); 176 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "rtsp_init()"); 177 | 178 | // MQTT client 179 | if(mqtt_init()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_init()"); 180 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_init()"); 181 | 182 | // Endless waiting 183 | while(true) { 184 | sleep(1); 185 | } 186 | 187 | } else { 188 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "all_init()"); 189 | return EX_SOFTWARE; 190 | } 191 | } else { 192 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "configs_init()"); 193 | return EX_CONFIG; 194 | } 195 | 196 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (signal = %d).", EX__BASE); 197 | return EX__BASE; 198 | } 199 | -------------------------------------------------------------------------------- /rtsp/libRtspServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "libRtspServer.h" 5 | #include "xop/RtspServer.h" 6 | #include "xop/H264Parser.h" 7 | 8 | static std::shared_ptr event_loop(new xop::EventLoop()); 9 | static std::shared_ptr rtsp_server; 10 | 11 | // Default log printf function 12 | static int logprintf_default(const char *format, ...) { 13 | int result = 0; 14 | va_list params; 15 | va_start(params, format); 16 | char *message; 17 | if(vasprintf(&message, format, params) != -1) { 18 | result = printf("[rtspserver]: %s\n", message); 19 | free(message); 20 | } 21 | va_end(params); 22 | return result; 23 | } 24 | static int (*logprintf_function)(const char *, ...) = logprintf_default; 25 | 26 | // Default connected callback function 27 | static void connected_default(uint32_t session_id, const char *peer_ip, uint16_t peer_port) { } 28 | static void (*connected_function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port) = connected_default; 29 | 30 | // Default disconnected callback function 31 | static void disconnected_default(uint32_t session_id, const char *peer_ip, uint16_t peer_port) { } 32 | static void (*disconnected_function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port) = disconnected_default; 33 | 34 | // Set log printf function 35 | bool rtspserver_logprintf(int (*function)(const char *, ...)) { 36 | logprintf_function = function; 37 | return true; 38 | } 39 | 40 | // Set connected callback function 41 | bool rtspserver_connected(void (*function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port)) { 42 | connected_function = function; 43 | return true; 44 | } 45 | 46 | // Set disconnected callback function 47 | bool rtspserver_disconnected(void (*function)(uint32_t session_id, const char *peer_ip, uint16_t peer_port)) { 48 | disconnected_function = function; 49 | return true; 50 | } 51 | 52 | // Create RTSP server 53 | bool rtspserver_create(uint16_t port, char *username, char *password) { 54 | rtsp_server = xop::RtspServer::Create(event_loop.get()); 55 | if(rtsp_server->Start("0.0.0.0", port)) { 56 | logprintf_function("The RTSP server is running on port %d.", port); 57 | // Digest authentication 58 | if(username && username[0]) { 59 | rtsp_server->SetAuthConfig("RTSP", std::string(username), std::string(password)); 60 | logprintf_function("Digest authentication is enabled."); 61 | } 62 | return true; 63 | } 64 | logprintf_function("RTSP server startup error! Port %d is busy?", port); 65 | return false; 66 | } 67 | 68 | // Create new session 69 | uint32_t rtspserver_session(char *name, bool multicast, uint8_t video_type, uint32_t framerate, uint8_t audio_type, uint32_t samplerate, uint32_t channels, bool has_adts) { 70 | if(!rtsp_server) { return 0; } 71 | logprintf_function("A new multimedia session \"%s\" has been created.", name); 72 | xop::MediaSession *session = xop::MediaSession::CreateNew(std::string(name)); 73 | // Video 74 | switch(video_type) { 75 | case LIBRTSPSERVER_TYPE_H264: 76 | session->AddSource(xop::channel_0, xop::H264Source::CreateNew(framerate)); 77 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "H264", "enabled", name, xop::channel_0); 78 | break; 79 | case LIBRTSPSERVER_TYPE_H265: 80 | session->AddSource(xop::channel_0, xop::H265Source::CreateNew(framerate)); 81 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "H265", "enabled", name, xop::channel_0); 82 | break; 83 | case LIBRTSPSERVER_TYPE_VP8: 84 | session->AddSource(xop::channel_0, xop::VP8Source::CreateNew(framerate)); 85 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "VP8", "enabled", name, xop::channel_0); 86 | break; 87 | case LIBRTSPSERVER_TYPE_NONE: 88 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "Video", "disabled", name, xop::channel_0); 89 | break; 90 | default: 91 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "Video", "unknown", name, xop::channel_0); 92 | } 93 | // Audio 94 | switch(audio_type) { 95 | case LIBRTSPSERVER_TYPE_AAC: 96 | session->AddSource(xop::channel_1, xop::AACSource::CreateNew(samplerate, channels, has_adts)); 97 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "AAC", "enabled", name, xop::channel_1); 98 | break; 99 | case LIBRTSPSERVER_TYPE_G711A: 100 | session->AddSource(xop::channel_1, xop::G711ASource::CreateNew()); 101 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "G711A", "enabled", name, xop::channel_1); 102 | break; 103 | case LIBRTSPSERVER_TYPE_NONE: 104 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "Audio", "disabled", name, xop::channel_1); 105 | break; 106 | default: 107 | logprintf_function("%s source is %s for session \"%s\" (channel = %d).", "Audio", "unknown", name, xop::channel_1); 108 | } 109 | // Multicast 110 | if(multicast) { 111 | session->StartMulticast(); 112 | logprintf_function("Multicast is enabled for session \"%s\".", name); 113 | } 114 | // Callbacks 115 | session->AddNotifyConnectedCallback([] (xop::MediaSessionId session_id, std::string peer_ip, uint16_t peer_port) { 116 | logprintf_function("Client connected to media session #%d (IP = %s, port = %hu).", session_id, peer_ip.c_str(), peer_port); 117 | connected_function(session_id, peer_ip.c_str(), peer_port); 118 | }); 119 | session->AddNotifyDisconnectedCallback([](xop::MediaSessionId session_id, std::string peer_ip, uint16_t peer_port) { 120 | logprintf_function("Client disconnected from media session #%d (IP = %s, port = %hu).", session_id, peer_ip.c_str(), peer_port); 121 | disconnected_function(session_id, peer_ip.c_str(), peer_port); 122 | }); 123 | // Done 124 | xop::MediaSessionId session_id = rtsp_server->AddSession(session); 125 | logprintf_function("Media session \"%s\" was started with ID = %d.", name, session_id); 126 | return session_id; 127 | } 128 | 129 | // Get current timestamp 130 | uint32_t rtspserver_timestamp(uint8_t source, uint32_t samplerate) { 131 | switch(source) { 132 | case LIBRTSPSERVER_TYPE_H264: return xop::H264Source::GetTimestamp(); 133 | case LIBRTSPSERVER_TYPE_H265: return xop::H265Source::GetTimestamp(); 134 | case LIBRTSPSERVER_TYPE_AAC: return xop::AACSource::GetTimestamp(samplerate); 135 | case LIBRTSPSERVER_TYPE_G711A: return xop::G711ASource::GetTimestamp(); 136 | case LIBRTSPSERVER_TYPE_VP8: return xop::VP8Source::GetTimestamp(); 137 | default: return 0; 138 | } 139 | } 140 | 141 | // Send media frame 142 | bool rtspserver_frame(uint32_t session_id, signed char *data, uint8_t type, uint32_t size, uint32_t timestamp, bool split_video) { 143 | if(!rtsp_server) { return false; } 144 | xop::AVFrame frame = {0}; 145 | frame.type = type; 146 | frame.timestamp = timestamp; 147 | // Prepare and send 148 | if(split_video) { 149 | xop::Nal nal; 150 | uint32_t endpoint = ((uint32_t) data) + size; 151 | while(true) { 152 | // Divide the video package into separate frames 153 | if(frame.type != xop::AUDIO_FRAME) { 154 | nal = xop::H264Parser::findNal((const uint8_t*) data, size); 155 | data = (signed char *) nal.first; 156 | size = ((uint32_t) nal.second) - ((uint32_t) nal.first) + 1; 157 | } 158 | // Send current frame 159 | if(data) { 160 | frame.size = size; 161 | frame.buffer.reset(new uint8_t[frame.size], std::default_delete()); 162 | memcpy(frame.buffer.get(), data, frame.size); 163 | rtsp_server->PushFrame(session_id, (frame.type != xop::AUDIO_FRAME ? xop::channel_0 : xop::channel_1), frame); 164 | if(frame.type != xop::AUDIO_FRAME) { 165 | data = (signed char *) nal.second; 166 | size = endpoint - ((uint32_t) nal.second) + 1; 167 | } else break; 168 | } else break; 169 | } 170 | return true; 171 | } else { 172 | uint32_t offset = ((frame.type == xop::AUDIO_FRAME) ? 0 : 4); // Skip 00 00 00 01 (for video frames) 173 | frame.size = size - offset; 174 | frame.buffer.reset(new uint8_t[frame.size], std::default_delete()); 175 | memcpy(frame.buffer.get(), data + offset, frame.size); 176 | return rtsp_server->PushFrame(session_id, (frame.type != xop::AUDIO_FRAME ? xop::channel_0 : xop::channel_1), frame); 177 | } 178 | } 179 | 180 | // Free RTSP server 181 | bool rtspserver_free(uint32_t count, ...) { 182 | bool result = false; 183 | if(event_loop && rtsp_server) { 184 | // Remove sessions 185 | va_list sessions; 186 | va_start(sessions, count); 187 | for(uint32_t i=0;iRemoveSession(session_id); 191 | } 192 | } 193 | va_end(sessions); 194 | // Stop server 195 | rtsp_server->Stop(); 196 | event_loop->Quit(); 197 | logprintf_function("The RTSP server is stopped."); 198 | result = true; 199 | } 200 | return result; 201 | } 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MJSXJ02HL application 2 | 3 | [![Donate](https://img.shields.io/badge/donate-YooMoney-blueviolet.svg)](https://yoomoney.ru/to/4100110221014297) 4 | 5 | Application for Xiaomi Smart Camera Standard Edition (MJSXJ02HL) with RTSP and MQTT support. It is used in a [custom firmware](https://github.com/kasitoru/mjsxj02hl_firmware). 6 | 7 | **Attention! This firmware is no longer supported by the author. We recommend using [OpenIPC](https://github.com/OpenIPC/device-mjsxj02hl).** 8 | 9 | 10 | ## Build 11 | 12 | 1. Install Hi3518Ev300 [toolchain](https://dl.openipc.org/SDK/HiSilicon/Hi3516Ev200_16Ev300_18Ev300/Hi3516EV200R001C01SPC011/arm-himix100-linux.tgz): 13 | 14 | ```bash 15 | tar -zxf arm-himix100-linux.tgz 16 | sudo ./arm-himix100-linux.install 17 | ``` 18 | 19 | 2. Copy the libraries from directory `/usr/app/lib` of the original firmware to directory `/opt/hisi-linux/x86-arm/arm-himix100-linux/target/usr/app/lib`: 20 | 21 | ```bash 22 | sudo mkdir -p /opt/hisi-linux/x86-arm/arm-himix100-linux/target/usr/app/lib 23 | sudo chmod 777 /opt/hisi-linux/x86-arm/arm-himix100-linux/target/usr/app/lib 24 | 25 | git clone https://github.com/kasitoru/mjsxj02hl_firmware 26 | cp -a ./mjsxj02hl_firmware/firmware/app/lib/. /opt/hisi-linux/x86-arm/arm-himix100-linux/target/usr/app/lib/ 27 | rm -Rf mjsxj02hl_firmware 28 | ``` 29 | 30 | 3. Clone the repository: 31 | 32 | ```bash 33 | git clone https://github.com/kasitoru/mjsxj02hl_application 34 | cd mjsxj02hl_application 35 | ``` 36 | 37 | 4. Update submodules (optional): 38 | ```bash 39 | make update-libs 40 | ``` 41 | 42 | 5. Build application: 43 | ```bash 44 | make 45 | ``` 46 | 47 | To save time, you can disable the build of external shared libraries: 48 | 49 | ```bash 50 | make SKIP_SHARED_LIBS=ON 51 | ``` 52 | 53 | ...and skip step №4. 54 | 55 | ## Configuration 56 | 57 | Default config `/usr/app/share/mjsxj02hl.conf`: 58 | 59 | ```ini 60 | [general] 61 | name = My Camera ; Device name 62 | led = true ; Enable onboard LED indicator 63 | 64 | [logger] 65 | level = 2 ; Log level (0 = disable, 1 = error, 2 = warning, 3 = info, 4 = debug) 66 | file = ; Write log to file (empty for disable) 67 | 68 | [osd] 69 | enable = false ; Enable On-Screen Display (OSD) 70 | oemlogo = true ; Display OEM logo (MI) 71 | oemlogo_x = 2 ; X position of the OEM logo 72 | oemlogo_y = 0 ; Y position of the OEM logo 73 | oemlogo_size = 0 ; Size of the OEM logo (can take negative values) 74 | datetime = true ; Display date and time 75 | datetime_x = 48 ; X position of the date and time 76 | datetime_y = 0 ; Y position of the date and time 77 | datetime_size = 0 ; Size of the date and time (can take negative values) 78 | motion = false ; Display detected motions in rectangles 79 | humanoid = false ; Display detected humanoids in rectangles 80 | 81 | [video] 82 | gop = 1 ; Group of pictures (GOP) every N*FPS (20) 83 | flip = false ; Flip image (all channels) 84 | mirror = false ; Mirror image (all channels) 85 | primary_type = 1 ; Video compression standard for primary channel (1 = h264, 2 = h265) 86 | secondary_type = 1 ; Video compression standard for secondary channel (1 = h264, 2 = h265) 87 | primary_bitrate = 1800 ; Bitrate for primary channel 88 | secondary_bitrate = 900 ; Bitrate for secondary channel 89 | primary_rcmode = 2 ; Rate control mode for primary channel (0 = constant bitrate, 1 = constant quality, 2 = variable bitrate) 90 | secondary_rcmode = 2 ; Rate control mode for secondary channel (0 = constant bitrate, 1 = constant quality, 2 = variable bitrate) 91 | 92 | [audio] 93 | volume = 70 ; Audio volume level (0-100) 94 | primary_enable = true ; Enable audio for primary channel 95 | secondary_enable = true ; Enable audio for secondary channel 96 | 97 | [speaker] 98 | volume = 70 ; Speaker volume level (0-100) 99 | type = 1 ; Default file format (1 = PCM, 2 = G711) 100 | 101 | [alarm] 102 | enable = true ; Enable alarms 103 | motion_sens = 150 ; Motion sensitivity (1-255) 104 | humanoid_sens = 150 ; Humanoid sensitivity (1-255) 105 | motion_timeout = 60 ; Motion timeout (in seconds) 106 | humanoid_timeout = 60 ; Humanoid timeout (in seconds) 107 | motion_detect_exec = ; Execute the command when motion is detected (empty for disable) 108 | humanoid_detect_exec = ; Execute the command when humanoid is detected (empty for disable) 109 | motion_lost_exec = ; Execute the command when motion is lost (empty for disable) 110 | humanoid_lost_exec = ; Execute the command when humanoid is lost (empty for disable) 111 | 112 | [rtsp] 113 | enable = true ; Enable RTSP server 114 | port = 554 ; Port number 115 | username = ; Username (empty for disable) 116 | password = ; Password 117 | primary_name = primary ; Name of the primary channel 118 | secondary_name = secondary ; Name of the secondary channel 119 | primary_multicast = false ; Use multicast for primary channel 120 | secondary_multicast = false ; Use multicast for secondary channel 121 | primary_split_vframes = true ; Split video frames into separate packets for primary channel 122 | secondary_split_vframes = true ; Split video frames into separate packets for secondary channel 123 | 124 | [mqtt] 125 | enable = false ; Enable MQTT client 126 | server = ; Server address 127 | port = 1883 ; Port number 128 | username = ; Username (empty for anonimous) 129 | password = ; Password (empty for disable) 130 | topic = mjsxj02hl ; Name of the root topic 131 | qos = 1 ; Quality of Service (0, 1 or 2) 132 | retain = true ; Retained messages 133 | reconnection_interval = 60 ; Reconnection interval (in seconds) 134 | periodical_interval = 60 ; Interval of periodic message (in seconds) 135 | discovery = homeassistant ; Discovery prefix (https://www.home-assistant.io/docs/mqtt/discovery/#discovery-topic) 136 | 137 | [night] 138 | mode = 2 ; Night mode (0 = off, 1 = on, 2 = auto) 139 | gray = 2 ; Grayscale (0 = off, 1 = on, 2 = auto) 140 | ``` 141 | 142 | ## Usage 143 | 144 | ```bash 145 | mjsxj02hl [ [options...]] 146 | ``` 147 | 148 | Running without arguments starts the main thread of the application. 149 | 150 | ***--config \*** Specify the location of the configuration file for the main thread of application. 151 | 152 | ***--factory-reset*** Reset device settings to default values. Attention: this action cannot be undone! 153 | 154 | ***--get-image \*** Output the camera image to a file. Requires a running main thread of mjsxj02hl application. 155 | 156 | ***--help*** Display help message. 157 | 158 | ## RTSP 159 | 160 | Network URL: `rtsp://[:@]:/` 161 | 162 | Example: `rtsp://192.168.1.18:554/primary` or `rtsp://user:password@192.168.1.18:554/secondary` 163 | 164 | ## MQTT 165 | 166 | ******: The value is set in the settings file (section: `mqtt`, name: `topic`). It is recommended to use the same value for all devices. 167 | 168 | ******: Value based on a parameter in the settings file (section: `general`, name: `name`). It is converted to lowercase, all characters except letters and numbers are cut off, spaces are replaced with underscores. 169 | 170 | ### Input topics 171 | 172 | **Topic: //cmd** 173 | 174 | Execute the specified command on the device. 175 | 176 | Command | Parameters | Description | Example payload 177 | ------- | ---------- | ----------- | --------------- 178 | `get_image` | `filename` (string) | Save the image to the specified file (JPEG, 640x360). | { "action": "get_image", "filename": "/mnt/mmc/image.jpg" } 179 | `set_volume` | `value` (integer) | Set volume level for speaker (0-100). | { "action": "set_volume", "value": 100 } 180 | `play_media` | `filename` (string), `type` (string, optional), `volume` (integer, optional) | Play the specified media file. Two types are supported: "pcm" (WAV, 8000 hz, 16-bit, mono) and "g711" (A-Law, 8000 hz, 16-bit, mono). | { "action": "play_media", "filename": "/mnt/mmc/media.wav", "type": "pcm", "volume": 75 } 181 | `stop_media` | | Stop current playback. | { "action": "stop_media" } 182 | `system` | `command` (string) | Execute the system (shell) command. | { "action": "system", "command": "poweroff -f" } 183 | `restart` | | Restart the main thread of mjsxj02hl application | { "action": "restart" } 184 | `reboot` | | Reboot the device. | { "action": "reboot" } 185 | 186 | ### Output topics 187 | 188 | **Topic: //status** 189 | 190 | This is a topic where availability status of the device is published (`online` or `offline`). 191 | 192 | **Topic: //info** 193 | 194 | This is a topic where general device state is published. 195 | 196 | Field | Description 197 | ----- | ----------- 198 | `fw_version` | Version of the firmware. 199 | `ip_address` | IP address of the device. 200 | `hw_temp` | Temperature from chip's internal sensor. 201 | `total_ram` | The total size of RAM. 202 | `free_ram` | The size of the free RAM. 203 | `total_sdmem` | The total size of SD-card. 204 | `free_sdmem` | The size of free space on the SD-card. 205 | `total_configs` | Total size of the configs partition. 206 | `free_configs` | The size of free space on the configs partition. 207 | `volume_level` | Current volume level of the speaker. 208 | `media_status` | Playback status (0 = stopped, 1 = playing, 2 = stopping). 209 | `image_url` | URL address of the JPEG image from the camera. 210 | 211 | **Topic: //alarm** 212 | 213 | This is a topic where motion detection events is published. 214 | 215 | Field | Description 216 | ----- | ----------- 217 | `motion` | Motion detection state. 218 | `humanoid` | Humanoid detection state. 219 | 220 | **Topic: //night** 221 | 222 | This is a topic where the state of night mode is published. 223 | 224 | Field | Description 225 | ----- | ----------- 226 | `state` | Night mode state. 227 | `gray` | Grayscale state. 228 | 229 | ## Third-party libraries 230 | 231 | * yyjson: https://github.com/ibireme/yyjson 232 | * inih: https://github.com/benhoyt/inih 233 | * paho.mqtt.c: https://github.com/eclipse/paho.mqtt.c 234 | * RtspServer: https://github.com/PHZ76/RtspServer 235 | * ipctool: https://github.com/OpenIPC/ipctool 236 | -------------------------------------------------------------------------------- /localsdk/night/night.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "./night.h" 5 | #include "./../localsdk.h" 6 | #include "./../alarm/alarm.h" 7 | #include "./../../logger/logger.h" 8 | #include "./../../configs/configs.h" 9 | #include "./../../yyjson/src/yyjson.h" 10 | #include "./../../mqtt/mqtt.h" 11 | 12 | // MQTT send info 13 | static bool night_state_mqtt(bool night, bool gray) { 14 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 15 | bool result = true; 16 | 17 | // Send night mode info 18 | yyjson_mut_doc *json_doc = yyjson_mut_doc_new(NULL); 19 | yyjson_mut_val *json_root = yyjson_mut_obj(json_doc); 20 | yyjson_mut_doc_set_root(json_doc, json_root); 21 | 22 | // Motion state 23 | yyjson_mut_obj_add_bool(json_doc, json_root, "state", night); 24 | 25 | // Humanoid state 26 | yyjson_mut_obj_add_bool(json_doc, json_root, "gray", gray); 27 | 28 | // Send it 29 | const char *json = yyjson_mut_write(json_doc, 0, NULL); 30 | if(result &= !!json) { 31 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_write()"); 32 | 33 | char *night_topic = mqtt_fulltopic(MQTT_NIGHT_TOPIC); 34 | if(result &= mqtt_send(night_topic, (char *) json)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_NIGHT_TOPIC)"); 35 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_NIGHT_TOPIC)"); 36 | 37 | free(night_topic); 38 | free((void *) json); 39 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_write()"); 40 | 41 | // Free resources 42 | yyjson_mut_doc_free(json_doc); 43 | 44 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 45 | return result; 46 | } 47 | 48 | // Callback for change state of night mode 49 | static int night_mode_change_callback(int state) { 50 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 51 | int result = LOCALSDK_OK; 52 | 53 | LOGGER(LOGGER_LEVEL_INFO, "State: %s", (state ? "true" : "false")); 54 | 55 | switch(state) { 56 | case NIGHT_MODE_STATE_NIGHTTIME: // night 57 | 58 | // Enable grayscale 59 | if(APP_CFG.night.gray == 2) { // auto 60 | if(local_sdk_video_set_night_mode() == LOCALSDK_ERROR) { 61 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_night_mode()"); 62 | result = LOCALSDK_ERROR; 63 | } else LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_night_mode()"); 64 | } 65 | 66 | // Open IR-cut filter 67 | if(local_sdk_open_ircut() == LOCALSDK_ERROR) { 68 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_open_ircut()"); 69 | result = LOCALSDK_ERROR; 70 | } else LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_ircut()"); 71 | 72 | // MQTT 73 | if(mqtt_is_ready()) { 74 | if(night_state_mqtt(true, (APP_CFG.night.gray == 2 ? true : (APP_CFG.night.gray == 1)))) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "night_state_mqtt()"); 75 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "night_state_mqtt()"); 76 | } 77 | 78 | break; 79 | case NIGHT_MODE_STATE_DAYTIME: // day 80 | 81 | // Close IR-cut filter 82 | if(local_sdk_close_ircut() == LOCALSDK_ERROR) { 83 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_close_ircut()"); 84 | result = LOCALSDK_ERROR; 85 | } else LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_ircut()"); 86 | 87 | // Disable grayscale 88 | if(APP_CFG.night.gray == 2) { // auto 89 | if(local_sdk_video_set_daytime_mode() == LOCALSDK_ERROR) { 90 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_daytime_mode()"); 91 | result = LOCALSDK_ERROR; 92 | } else LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_daytime_mode()"); 93 | } 94 | 95 | // MQTT 96 | if(mqtt_is_ready()) { 97 | if(night_state_mqtt(false, (APP_CFG.night.gray == 2 ? false : (APP_CFG.night.gray == 1)))) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "night_state_mqtt()"); 98 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "night_state_mqtt()"); 99 | } 100 | 101 | break; 102 | case NIGHT_MODE_STATE_DISABLE: // Disable alarm system 103 | if(alarm_switch(false)) { 104 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_switch(false)"); 105 | } else { 106 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "alarm_switch(false)"); 107 | result = LOCALSDK_ERROR; 108 | } 109 | break; 110 | case NIGHT_MODE_STATE_ENABLE: // Enable alarm system 111 | if(alarm_switch(true)) { 112 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_switch(true)"); 113 | } else { 114 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "alarm_switch(true)"); 115 | result = LOCALSDK_ERROR; 116 | } 117 | break; 118 | default: // unknown 119 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "Unknown value of night mode. Switching"); 120 | result = LOCALSDK_ERROR; 121 | break; 122 | } 123 | 124 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result == LOCALSDK_OK ? "LOCALSDK_OK" : "LOCALSDK_ERROR")); 125 | return result; 126 | } 127 | 128 | // Init night mode 129 | bool night_init() { 130 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 131 | bool result = true; 132 | 133 | if(result &= (local_sdk_night_state_set_callback(night_mode_change_callback) == LOCALSDK_OK)) { 134 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_night_state_set_callback()"); 135 | // Set night mode 136 | switch(APP_CFG.night.mode) { 137 | case 0: // off 138 | if(result &= (local_sdk_open_ircut() == LOCALSDK_OK)) { 139 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_ircut()"); 140 | usleep(100000); 141 | if(result &= (local_sdk_close_ircut() == LOCALSDK_OK)) { 142 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_ircut()"); 143 | if(result &= (local_sdk_close_night_light() == LOCALSDK_OK)) { 144 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_night_light()"); 145 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_close_night_light()"); 146 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_close_ircut()"); 147 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_open_ircut()"); 148 | break; 149 | case 1: // on 150 | if(result &= (local_sdk_close_ircut() == LOCALSDK_OK)) { 151 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_ircut()"); 152 | usleep(100000); 153 | if(result &= (local_sdk_open_ircut() == LOCALSDK_OK)) { 154 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_ircut()"); 155 | if(result &= (local_sdk_open_night_light() == LOCALSDK_OK)) { 156 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_night_light()"); 157 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_open_night_light()"); 158 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_open_ircut()"); 159 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_close_ircut()"); 160 | break; 161 | case 2: // auto 162 | if(result &= (local_sdk_open_ircut() == LOCALSDK_OK)) { 163 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_open_ircut()"); 164 | usleep(100000); 165 | if(result &= (local_sdk_close_ircut() == LOCALSDK_OK)) { 166 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_ircut()"); 167 | if(result &= (local_sdk_auto_night_light() == LOCALSDK_OK)) { 168 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_auto_night_light()"); 169 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_auto_night_light()"); 170 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_close_ircut()"); 171 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_open_ircut()"); 172 | break; 173 | default: // unknown 174 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "Unknown value of night mode. Switching"); 175 | break; 176 | } 177 | 178 | // Set grayscale 179 | if(result) { 180 | switch(APP_CFG.night.gray) { 181 | case 0: // off 182 | if(result &= (local_sdk_video_set_daytime_mode() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_daytime_mode()"); 183 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_daytime_mode()"); 184 | break; 185 | case 1: // on 186 | if(result &= (local_sdk_video_set_night_mode() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_night_mode()"); 187 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_night_mode()"); 188 | break; 189 | case 2: // auto 190 | break; 191 | default: // unknown 192 | LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "Unknown value of grayscale mode. Switching"); 193 | break; 194 | } 195 | } 196 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_night_state_set_callback()"); 197 | 198 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 199 | return result; 200 | } 201 | 202 | // Free night mode 203 | bool night_free() { 204 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 205 | bool result = true; 206 | 207 | // Disable night mode 208 | if(result &= (local_sdk_close_ircut() == LOCALSDK_OK)) { 209 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_ircut()"); 210 | if(result &= (local_sdk_close_night_light() == LOCALSDK_OK)) { 211 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_close_night_light()"); 212 | if(result &= (local_sdk_video_set_daytime_mode() == LOCALSDK_OK)) { 213 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_daytime_mode()"); 214 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_set_daytime_mode()"); 215 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_close_night_light()"); 216 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_close_ircut()"); 217 | 218 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 219 | return result; 220 | } 221 | -------------------------------------------------------------------------------- /localsdk/localsdk.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCALSDK_H_ 2 | #define _LOCALSDK_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C"{ 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /******************** 13 | GENERAL 14 | ********************/ 15 | 16 | #define LOCALSDK_OK 0 17 | #define LOCALSDK_ERROR 1 18 | 19 | #define LOCALSDK_CURRENT_VERSION 14 20 | 21 | typedef struct { 22 | uint32_t width; 23 | uint32_t height; 24 | } LOCALSDK_PICTURE_SIZE; 25 | 26 | // Set printf function for debug messages 27 | int localsdk_set_logprintf_func(int (*function)(const char *, ...)); 28 | 29 | // Set shell function 30 | int localsdk_set_shellcall_func(int *param_1); // FIXME 31 | 32 | // Initialize SDK 33 | int localsdk_init(); 34 | 35 | // Destory SDK 36 | int localsdk_destory(); 37 | 38 | // Get SDK version 39 | int localsdk_get_version(); 40 | 41 | // Get the changed resolution type 42 | int inner_change_resulu_type(int resolution, int *result); 43 | 44 | // Get picture size 45 | int SAMPLE_COMM_SYS_GetPicSize(int resolution, LOCALSDK_PICTURE_SIZE *size); 46 | 47 | /******************** 48 | VIDEO 49 | ********************/ 50 | 51 | #define LOCALSDK_VIDEO_PAYLOAD_H264 1 52 | #define LOCALSDK_VIDEO_PAYLOAD_H265 2 53 | 54 | #define LOCALSDK_VIDEO_RESOLUTION_640x360 3 55 | #define LOCALSDK_VIDEO_RESOLUTION_1920x1080 6 56 | 57 | #define LOCALSDK_VIDEO_PRIMARY_CHANNEL 0 58 | #define LOCALSDK_VIDEO_SECONDARY_CHANNEL 1 59 | 60 | #define LOCALSDK_VIDEO_PRIMARY_FRAMESIZE 327680 61 | #define LOCALSDK_VIDEO_SECONDARY_FRAMESIZE 81920 62 | 63 | #define LOCALSDK_VIDEO_FRAMERATE 20 64 | 65 | #define LOCALSDK_VIDEO_RCMODE_CONSTANT_BITRATE 0 66 | #define LOCALSDK_VIDEO_RCMODE_CONSTANT_QUALITY 1 67 | #define LOCALSDK_VIDEO_RCMODE_VARIABLE_BITRATE 2 68 | 69 | #define LOCALSDK_VIDEO_H26X_FRAME_I 0 70 | #define LOCALSDK_VIDEO_H26X_FRAME_P 1 71 | 72 | typedef struct { 73 | signed char *data; 74 | uint32_t size; 75 | uint32_t index; 76 | uint32_t timestamp; 77 | uint16_t unknown_4; // FIXME: what is it? 78 | uint16_t unknown_5; // FIXME: what is it? 79 | uint16_t type; 80 | } LOCALSDK_H26X_FRAME_INFO; 81 | 82 | typedef struct { 83 | uint32_t bitrate; 84 | uint32_t fps; 85 | uint32_t resolution; 86 | uint32_t flip; 87 | uint32_t mirror; 88 | uint32_t unknown_5; // FIXME: what is it? 89 | uint32_t video; 90 | uint32_t osd; 91 | uint32_t payload; 92 | uint32_t rcmode; 93 | uint32_t gop; 94 | uint32_t screen_size; 95 | uint32_t frame_size; 96 | uint32_t jpeg; 97 | uint32_t unknown_14; // FIXME: what is it? 98 | } LOCALSDK_VIDEO_OPTIONS; 99 | 100 | // Init video 101 | int local_sdk_video_init(int fps); 102 | 103 | // Create video 104 | int local_sdk_video_create(int chn, LOCALSDK_VIDEO_OPTIONS *options); 105 | 106 | // Set video parameters 107 | int local_sdk_video_set_parameters(int chn, LOCALSDK_VIDEO_OPTIONS *options); 108 | 109 | // Set video frame callback 110 | int local_sdk_video_set_encode_frame_callback(int chn, int (*callback)(LOCALSDK_H26X_FRAME_INFO *frameInfo)); 111 | int local_sdk_video_set_yuv_frame_callback(int chn, int (*callback)(LOCALSDK_H26X_FRAME_INFO *frameInfo)); // FIXME: Need own structure? 112 | 113 | // Set video algo module callback 114 | int local_sdk_video_set_algo_module_register_callback(int (*callback)()); 115 | int local_sdk_video_set_algo_module_unregister_callback(int (*callback)()); 116 | 117 | // Start video 118 | int local_sdk_video_start(int chn); 119 | 120 | // Stop video 121 | int local_sdk_video_stop(int chn, bool state); 122 | 123 | // Run video 124 | int local_sdk_video_run(int chn); 125 | 126 | // Save video image to jpeg file 127 | int local_sdk_video_get_jpeg(int chn, char *file); 128 | 129 | // Request IDR frame 130 | int local_sdk_video_force_I_frame(int chn); 131 | 132 | // TODO: 133 | int local_sdk_video_set_brightness(int param_1, int param_2, int param_3, int param_4); 134 | int local_sdk_video_set_flip(int param_1, int param_2); 135 | int local_sdk_video_set_fps(int param_1, int param_2, int param_3, int param_4); 136 | int local_sdk_video_set_kbps(int param_1, int param_2); 137 | 138 | /******************** 139 | AUDIO 140 | ********************/ 141 | 142 | #define LOCALSDK_AUDIO_CHANNEL 0 143 | #define LOCALSDK_AUDIO_SAMPLE_RATE 8000 144 | #define LOCALSDK_AUDIO_BIT_DEPTH 16 145 | #define LOCALSDK_AUDIO_TRACK_TYPE 1 146 | 147 | #define LOCALSDK_AUDIO_G711_BUFFER_SIZE 320 148 | #define LOCALSDK_AUDIO_PCM_BUFFER_SIZE 640 149 | 150 | #define LOCALSDK_AUDIO_G711_FRAME 2 151 | 152 | typedef struct { 153 | signed char *data; 154 | uint32_t size; 155 | uint32_t index; 156 | uint32_t timestamp; 157 | } LOCALSDK_AUDIO_G711_FRAME_INFO; 158 | 159 | typedef struct { 160 | uint32_t sample_rate; 161 | uint32_t bit_depth; 162 | uint32_t unknown_2; // FIXME: what is it? 163 | uint32_t track_type; 164 | uint32_t unknown_4; // FIXME: what is it? 165 | uint32_t unknown_5; // FIXME: what is it? 166 | uint32_t unknown_6; // FIXME: what is it? 167 | uint32_t unknown_7; // FIXME: what is it? 168 | uint32_t unknown_8; // FIXME: what is it? 169 | uint32_t unknown_9; // FIXME: what is it? 170 | uint32_t volume; 171 | uint32_t pcm_buffer_size; 172 | uint32_t g711_buffer_size; 173 | } LOCALSDK_AUDIO_OPTIONS; 174 | 175 | // Init audio 176 | int local_sdk_audio_init(); 177 | 178 | // Create audio 179 | int local_sdk_audio_create(int chn); 180 | 181 | // Set audio parameters 182 | int local_sdk_audio_set_parameters(int chn, LOCALSDK_AUDIO_OPTIONS *options); 183 | 184 | // Enable AEC 185 | int local_sdk_audio_set_aec_enable(int chn, bool state); 186 | 187 | // Set volume 188 | int local_sdk_audio_set_volume(int chn, int value); 189 | 190 | // Set audio encode callback 191 | int local_sdk_audio_set_encode_frame_callback(int chn, int (*callback)(LOCALSDK_AUDIO_G711_FRAME_INFO *frameInfo)); 192 | 193 | // Start audio 194 | int local_sdk_audio_start(); 195 | 196 | // Stop audio 197 | int local_sdk_audio_stop(); 198 | 199 | // Run audio 200 | int local_sdk_audio_run(); 201 | 202 | // End audio 203 | int local_sdk_audio_end(); 204 | 205 | // Destory audio 206 | int local_sdk_audio_destory(); 207 | 208 | /******************** 209 | SPEAKER 210 | ********************/ 211 | 212 | #define LOCALSDK_SPEAKER_SAMPLE_RATE 8000 213 | #define LOCALSDK_SPEAKER_BIT_DEPTH 16 214 | #define LOCALSDK_SPEAKER_TRACK_TYPE 1 215 | 216 | #define LOCALSDK_SPEAKER_PCM_TYPE 1 217 | #define LOCALSDK_SPEAKER_G711_TYPE 2 218 | 219 | typedef struct { 220 | uint32_t sample_rate; 221 | uint32_t bit_depth; 222 | uint32_t unknown_2; // FIXME: what is it? 223 | uint32_t track_type; 224 | uint32_t unknown_4; // FIXME: what is it? 225 | uint32_t volume; 226 | uint32_t buffer_size; 227 | uint32_t unknown_7; // FIXME: what is it? 228 | } LOCALSDK_SPEAKER_OPTIONS; 229 | 230 | // Init speaker 231 | int local_sdk_speaker_init(); 232 | 233 | // Set speaker parameters 234 | int local_sdk_speaker_set_parameters(LOCALSDK_SPEAKER_OPTIONS *options); 235 | 236 | // Set volume 237 | int local_sdk_speaker_set_volume(int value); 238 | 239 | // Enable mute 240 | int local_sdk_speaker_mute(); 241 | 242 | // Disable mute 243 | int local_sdk_speaker_unmute(); 244 | 245 | // Start speaker 246 | int local_sdk_speaker_start(); 247 | 248 | // Feed PCM data 249 | int local_sdk_speaker_feed_pcm_data(void *data, int size); 250 | 251 | // Feed G711 data 252 | int local_sdk_speaker_feed_g711_data(void *data, int size); 253 | 254 | // Finish buffer 255 | int local_sdk_speaker_finish_buf_data(); 256 | 257 | // Clean buffer 258 | int local_sdk_speaker_clean_buf_data(); 259 | 260 | /******************** 261 | ALARM 262 | ********************/ 263 | 264 | #define LOCALSDK_ALARM_TYPE_MOTION 1 265 | #define LOCALSDK_ALARM_TYPE_HUMANOID 7 266 | 267 | #define LOCALSDK_ALARM_MAXIMUM_OBJECTS 4 268 | 269 | typedef struct { 270 | uint32_t state; 271 | uint32_t type; 272 | struct { 273 | uint32_t type; 274 | uint32_t state; 275 | uint32_t x; 276 | uint32_t width; 277 | uint32_t y; 278 | uint32_t height; 279 | uint32_t unknown[11]; // FIXME: what is it? 280 | } objects[LOCALSDK_ALARM_MAXIMUM_OBJECTS]; 281 | } LOCALSDK_ALARM_EVENT_INFO; 282 | 283 | // Init alarm 284 | int local_sdk_alarm_init(int width, int height); 285 | 286 | // Set alarm sensitivity (1...255) 287 | int local_sdk_set_alarm_sensitivity(int type, int value); 288 | 289 | // Exit alarm 290 | int local_sdk_alarm_exit(); 291 | 292 | // Set motor state 293 | int local_sdk_alarm_set_motor_state(); 294 | 295 | // Set alarm algo module callback 296 | int local_sdk_alarm_algo_module_register_callback(); 297 | 298 | // Unset alarm algo module callback 299 | int local_sdk_alarm_algo_module_unregister_callback(); 300 | 301 | // Set alarm state callback 302 | int local_sdk_alarm_state_set_callback(int (*callback)(LOCALSDK_ALARM_EVENT_INFO *eventInfo)); 303 | 304 | // Clear alarm state callback 305 | int local_sdk_alarm_state_clear_callback(int (*callback)(LOCALSDK_ALARM_EVENT_INFO *eventInfo)); 306 | 307 | // Set alarm network state 308 | int local_sdk_set_alarm_network_state(); 309 | 310 | // Set alarm switch 311 | int local_sdk_set_alarm_switch(int type, bool state); 312 | 313 | /******************** 314 | OSD 315 | ********************/ 316 | 317 | #define LOCALSDK_OSD_COLOR_GREEN 3 318 | #define LOCALSDK_OSD_COLOR_ORANGE 5 319 | 320 | typedef struct { 321 | uint32_t unknown; // FIXME: what is it? 322 | uint32_t datetime_x; 323 | uint32_t datetime_y; 324 | uint32_t datetime_reduce; 325 | uint32_t datetime_increase; 326 | uint32_t oemlogo_x; 327 | uint32_t oemlogo_y; 328 | uint32_t oemlogo_reduce; 329 | uint32_t oemlogo_increase; 330 | } LOCALSDK_OSD_OPTIONS; 331 | 332 | typedef struct { 333 | uint32_t count; 334 | struct { 335 | uint32_t x; 336 | uint32_t width; 337 | uint32_t y; 338 | uint32_t height; 339 | uint32_t unknown; // FIXME: what is it (always = 1)? 340 | uint32_t color; 341 | } objects[LOCALSDK_ALARM_MAXIMUM_OBJECTS]; 342 | } LOCALSDK_OSD_RECTANGLES; 343 | 344 | // Set osd parameters 345 | int local_sdk_video_osd_set_parameters(int chn, LOCALSDK_OSD_OPTIONS *options); 346 | 347 | // Display OEM logo (MI) 348 | int local_sdk_video_osd_update_logo(int chn, bool state); 349 | 350 | // Display date and time 351 | int local_sdk_video_osd_update_timestamp(int chn, bool state, struct tm *timestamp); 352 | 353 | // Display rectangles 354 | int local_sdk_video_osd_update_rect_multi(int chn, bool state, LOCALSDK_OSD_RECTANGLES *rectangles); 355 | 356 | /******************** 357 | LEDS 358 | ********************/ 359 | 360 | int local_sdk_indicator_led_option(bool orange, bool blue); 361 | 362 | /******************** 363 | BUTTON 364 | ********************/ 365 | 366 | int local_sdk_setup_keydown_set_callback(int timeout, int (*callback)()); 367 | 368 | /******************** 369 | NIGHT MODE 370 | ********************/ 371 | 372 | // Color image 373 | int local_sdk_video_set_daytime_mode(); 374 | 375 | // Grayscale image 376 | int local_sdk_video_set_night_mode(); 377 | 378 | // Enable auto night mode 379 | int local_sdk_auto_night_light(); 380 | 381 | // Enable manual night mode 382 | int local_sdk_open_night_light(); 383 | 384 | // Disable manual night mode 385 | int local_sdk_close_night_light(); 386 | 387 | // Set night mode state callback 388 | int local_sdk_night_state_set_callback(int (*callback)(int state)); 389 | 390 | // Opet IR-cut filter 391 | int local_sdk_open_ircut(); 392 | 393 | // Close IR-cut filter 394 | int local_sdk_close_ircut(); 395 | 396 | #ifdef __cplusplus 397 | } 398 | #endif 399 | 400 | #endif 401 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /localsdk/video/video.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "./video.h" 6 | #include "./../osd/osd.h" 7 | #include "./../localsdk.h" 8 | #include "./../../logger/logger.h" 9 | #include "./../../configs/configs.h" 10 | #include "./../../rtsp/rtsp.h" 11 | 12 | // Video capture callback 13 | static int h26x_capture_callback(int chn, LOCALSDK_H26X_FRAME_INFO *frameInfo) { 14 | int result = LOCALSDK_OK; 15 | if(frameInfo && frameInfo->size) { 16 | // RTSP 17 | if(rtsp_is_enabled(chn)) { 18 | if(!rtsp_media_frame(chn, frameInfo->data, frameInfo->size, frameInfo->timestamp, frameInfo->type)) { 19 | //LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "rtsp_media_frame()"); 20 | result = LOCALSDK_ERROR; 21 | } 22 | } 23 | } 24 | return result; 25 | } 26 | 27 | static int h26x_capture_primary_channel(LOCALSDK_H26X_FRAME_INFO *frameInfo) { 28 | int result = LOCALSDK_OK; 29 | if(h26x_capture_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL, frameInfo) != LOCALSDK_OK) { 30 | //LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "h26x_capture_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 31 | result = LOCALSDK_ERROR; 32 | } 33 | return result; 34 | } 35 | 36 | static int h26x_capture_secondary_channel(LOCALSDK_H26X_FRAME_INFO *frameInfo) { 37 | int result = LOCALSDK_OK; 38 | if(h26x_capture_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL, frameInfo) != LOCALSDK_OK) { 39 | //LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "h26x_capture_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 40 | result = LOCALSDK_ERROR; 41 | } 42 | return result; 43 | } 44 | 45 | // Init video 46 | bool video_init() { 47 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 48 | bool result = true; 49 | 50 | if(result &= (local_sdk_video_init(LOCALSDK_VIDEO_FRAMERATE) == LOCALSDK_OK)) { 51 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_init()"); 52 | // Init channel 0 53 | int primary_resolution_type; 54 | LOCALSDK_PICTURE_SIZE primary_picture_size; 55 | if(result &= (inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_1920x1080, &primary_resolution_type) == LOCALSDK_OK)) { 56 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_1920x1080)"); 57 | if(result &= (SAMPLE_COMM_SYS_GetPicSize(primary_resolution_type, &primary_picture_size) == LOCALSDK_OK)) { 58 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_1920x1080)"); 59 | LOCALSDK_VIDEO_OPTIONS primary_options = { 60 | .bitrate = APP_CFG.video.primary_bitrate, 61 | .fps = LOCALSDK_VIDEO_FRAMERATE, 62 | .resolution = LOCALSDK_VIDEO_RESOLUTION_1920x1080, 63 | .flip = APP_CFG.video.flip, 64 | .mirror = APP_CFG.video.mirror, 65 | .unknown_5 = 0, // FIXME: what is it? 66 | .video = true, 67 | .osd = osd_is_enabled(), 68 | .payload = APP_CFG.video.primary_type, 69 | .rcmode = APP_CFG.video.primary_rcmode, 70 | .gop = APP_CFG.video.gop * LOCALSDK_VIDEO_FRAMERATE, 71 | .screen_size = primary_picture_size.width * primary_picture_size.height, 72 | .frame_size = LOCALSDK_VIDEO_PRIMARY_FRAMESIZE, 73 | .jpeg = false, 74 | .unknown_14 = 0, // FIXME: what is it? 75 | }; 76 | if(result &= (local_sdk_video_create(LOCALSDK_VIDEO_PRIMARY_CHANNEL, &primary_options) == LOCALSDK_OK)) { 77 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_create(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 78 | if(result &= (local_sdk_video_set_parameters(LOCALSDK_VIDEO_PRIMARY_CHANNEL, &primary_options) == LOCALSDK_OK)) { 79 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_parameters(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 80 | if(result &= (local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL, h26x_capture_primary_channel) == LOCALSDK_OK)) { 81 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 82 | if(result &= (local_sdk_video_start(LOCALSDK_VIDEO_PRIMARY_CHANNEL) == LOCALSDK_OK)) { 83 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_start(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 84 | if(result &= (local_sdk_video_run(LOCALSDK_VIDEO_PRIMARY_CHANNEL) == LOCALSDK_OK)) { 85 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_run(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 86 | if(result &= osd_postinit()) { 87 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "osd_postinit()"); 88 | // Init channel 1 89 | int secondary_resolution_type; 90 | LOCALSDK_PICTURE_SIZE secondary_picture_size; 91 | if(result &= (inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360, &secondary_resolution_type) == LOCALSDK_OK)) { 92 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 93 | if(result &= (SAMPLE_COMM_SYS_GetPicSize(secondary_resolution_type, &secondary_picture_size) == LOCALSDK_OK)) { 94 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 95 | LOCALSDK_VIDEO_OPTIONS secondary_options = { 96 | .bitrate = APP_CFG.video.secondary_bitrate, 97 | .fps = LOCALSDK_VIDEO_FRAMERATE, 98 | .resolution = LOCALSDK_VIDEO_RESOLUTION_640x360, 99 | .flip = APP_CFG.video.flip, 100 | .mirror = APP_CFG.video.mirror, 101 | .unknown_5 = 1, // FIXME: what is it? 102 | .video = true, 103 | .osd = false, // Not work for secondary channel 104 | .payload = APP_CFG.video.secondary_type, 105 | .rcmode = APP_CFG.video.secondary_rcmode, 106 | .gop = APP_CFG.video.gop * LOCALSDK_VIDEO_FRAMERATE, 107 | .screen_size = secondary_picture_size.width * secondary_picture_size.height, 108 | .frame_size = LOCALSDK_VIDEO_SECONDARY_FRAMESIZE, 109 | .jpeg = true, 110 | .unknown_14 = 1, // FIXME: what is it? 111 | }; 112 | if(result &= (local_sdk_video_create(LOCALSDK_VIDEO_SECONDARY_CHANNEL, &secondary_options) == LOCALSDK_OK)) { 113 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_create(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 114 | if(result &= (local_sdk_video_set_parameters(LOCALSDK_VIDEO_SECONDARY_CHANNEL, &secondary_options) == LOCALSDK_OK)) { 115 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_parameters(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 116 | if(result &= (local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL, h26x_capture_secondary_channel) == LOCALSDK_OK)) { 117 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 118 | if(result &= (local_sdk_video_set_algo_module_register_callback(local_sdk_alarm_algo_module_register_callback) == LOCALSDK_OK)) { 119 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_set_algo_module_register_callback()"); 120 | if(result &= (local_sdk_video_start(LOCALSDK_VIDEO_SECONDARY_CHANNEL) == LOCALSDK_OK)) { 121 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_start(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 122 | if(result &= (local_sdk_video_run(LOCALSDK_VIDEO_SECONDARY_CHANNEL) == LOCALSDK_OK)) { 123 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_run(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 124 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_run(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 125 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_start(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 126 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_algo_module_register_callback()"); 127 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 128 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_parameters(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 129 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_create(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 130 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 131 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 132 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "osd_postinit()"); 133 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_run(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 134 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_start(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 135 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_encode_frame_callback(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 136 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_set_parameters(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 137 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_create(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 138 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_1920x1080)"); 139 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_1920x1080)"); 140 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_init()"); 141 | 142 | // Free video if error occurred 143 | if(!result) { 144 | if(video_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "video_free()"); 145 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "video_free()"); 146 | } 147 | 148 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 149 | return result; 150 | } 151 | 152 | // Free video 153 | bool video_free() { 154 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 155 | bool result = true; 156 | 157 | // Stop secondary video 158 | if(result &= (local_sdk_video_stop(LOCALSDK_VIDEO_SECONDARY_CHANNEL, true) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_stop(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 159 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_stop(LOCALSDK_VIDEO_SECONDARY_CHANNEL)"); 160 | 161 | // Stop primary video 162 | if(result &= (local_sdk_video_stop(LOCALSDK_VIDEO_PRIMARY_CHANNEL, true) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_stop(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 163 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_video_stop(LOCALSDK_VIDEO_PRIMARY_CHANNEL)"); 164 | 165 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 166 | return result; 167 | } 168 | -------------------------------------------------------------------------------- /localsdk/alarm/alarm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "./alarm.h" 6 | #include "./../localsdk.h" 7 | #include "./../osd/osd.h" 8 | #include "./../../logger/logger.h" 9 | #include "./../../configs/configs.h" 10 | #include "./../../yyjson/src/yyjson.h" 11 | #include "./../../mqtt/mqtt.h" 12 | 13 | static pthread_t timeout_thread; 14 | static int alarm_time_motion, alarm_time_humanoid; 15 | 16 | // MQTT send info 17 | static bool alarm_state_mqtt(bool motion, bool humanoid) { 18 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 19 | bool result = true; 20 | 21 | // Send alarm state info 22 | yyjson_mut_doc *json_doc = yyjson_mut_doc_new(NULL); 23 | yyjson_mut_val *json_root = yyjson_mut_obj(json_doc); 24 | yyjson_mut_doc_set_root(json_doc, json_root); 25 | 26 | // Motion state 27 | if(yyjson_mut_obj_add_bool(json_doc, json_root, "motion", motion)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(motion)"); 28 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool()"); 29 | 30 | // Humanoid state 31 | if(yyjson_mut_obj_add_bool(json_doc, json_root, "humanoid", humanoid)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(humanoid)"); 32 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool()"); 33 | 34 | // Send it 35 | const char *json = yyjson_mut_write(json_doc, 0, NULL); 36 | if(result &= !!json) { 37 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_write()"); 38 | 39 | char *alarm_topic = mqtt_fulltopic(MQTT_ALARM_TOPIC); 40 | if(result &= mqtt_send(alarm_topic, (char *) json)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_ALARM_TOPIC)"); 41 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_ALARM_TOPIC)"); 42 | 43 | free(alarm_topic); 44 | free((void *) json); 45 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_write()"); 46 | 47 | // Free resources 48 | yyjson_mut_doc_free(json_doc); 49 | 50 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 51 | return result; 52 | } 53 | 54 | // State timeout (for pthread) 55 | static void* alarm_state_timeout(void *args) { 56 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 57 | 58 | bool alarm_state_motion, alarm_state_humanoid; 59 | bool alarm_change_motion, alarm_change_humanoid; 60 | 61 | alarm_time_motion = 0; 62 | alarm_time_humanoid = 0; 63 | alarm_state_motion = false; 64 | alarm_state_humanoid = false; 65 | 66 | do { 67 | alarm_change_motion = false; 68 | alarm_change_humanoid = false; 69 | 70 | // Motion 71 | if(alarm_time_motion > 0) { 72 | if(alarm_state_motion) { 73 | if((int) time(NULL) - alarm_time_motion > APP_CFG.alarm.motion_timeout) { 74 | alarm_time_motion = 0; 75 | alarm_state_motion = false; 76 | alarm_change_motion = true; 77 | } 78 | } else { 79 | alarm_state_motion = true; 80 | alarm_change_motion = true; 81 | } 82 | } 83 | 84 | // Humanoid 85 | if(alarm_time_humanoid > 0) { 86 | if(alarm_state_humanoid) { 87 | if((int) time(NULL) - alarm_time_humanoid > APP_CFG.alarm.humanoid_timeout) { 88 | alarm_time_humanoid = 0; 89 | alarm_state_humanoid = false; 90 | alarm_change_humanoid = true; 91 | } 92 | } else { 93 | alarm_state_humanoid = true; 94 | alarm_change_humanoid = true; 95 | } 96 | } 97 | 98 | // State changed 99 | if(alarm_change_motion || alarm_change_humanoid) { 100 | 101 | // Motion 102 | if(alarm_change_motion) { 103 | LOGGER(LOGGER_LEVEL_INFO, "Change %s status: %d", "motion", alarm_state_motion); 104 | // Execute the command 105 | if(alarm_state_motion && APP_CFG.alarm.motion_detect_exec && APP_CFG.alarm.motion_detect_exec[0]) { 106 | // Detect 107 | if(system(APP_CFG.alarm.motion_detect_exec) == 0) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(motion_detect_exec)"); 108 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(motion_detect_exec)"); 109 | } else if(!alarm_state_motion && APP_CFG.alarm.motion_lost_exec && APP_CFG.alarm.motion_lost_exec[0]) { 110 | // Lost 111 | if(system(APP_CFG.alarm.motion_lost_exec) == 0) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(motion_lost_exec)"); 112 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(motion_lost_exec)"); 113 | } 114 | } 115 | 116 | // Humanoid 117 | if(alarm_change_humanoid) { 118 | LOGGER(LOGGER_LEVEL_INFO, "Change %s status: %d", "humanoid", alarm_state_humanoid); 119 | // Execute the command 120 | if(alarm_state_humanoid && APP_CFG.alarm.humanoid_detect_exec && APP_CFG.alarm.humanoid_detect_exec[0]) { 121 | // Detect 122 | if(system(APP_CFG.alarm.humanoid_detect_exec) == 0) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(humanoid_detect_exec)"); 123 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(humanoid_detect_exec)"); 124 | } else if(!alarm_state_humanoid && APP_CFG.alarm.humanoid_lost_exec && APP_CFG.alarm.humanoid_lost_exec[0]) { 125 | // Lost 126 | if(system(APP_CFG.alarm.humanoid_lost_exec) == 0) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(humanoid_lost_exec)"); 127 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(humanoid_lost_exec)"); 128 | } 129 | } 130 | // MQTT 131 | if(mqtt_is_ready()) { 132 | if(alarm_state_mqtt(alarm_state_motion, alarm_state_humanoid)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_state_mqtt()"); 133 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "alarm_state_mqtt()"); 134 | } 135 | } 136 | 137 | sleep(1); 138 | pthread_testcancel(); 139 | } while(true); 140 | } 141 | 142 | // Alarm callback 143 | static int alarm_state_callback(LOCALSDK_ALARM_EVENT_INFO *eventInfo) { 144 | int result = LOCALSDK_OK; 145 | 146 | if(eventInfo) { 147 | // OSD rectangles callback 148 | result = osd_rectangles_callback(eventInfo); 149 | 150 | // Remember the timestamps of events 151 | if(eventInfo->state) { 152 | int current_timestamp = (int) time(NULL); 153 | switch(eventInfo->type) { 154 | case LOCALSDK_ALARM_TYPE_MOTION: 155 | alarm_time_motion = current_timestamp; 156 | break; 157 | case LOCALSDK_ALARM_TYPE_HUMANOID: 158 | alarm_time_humanoid = current_timestamp; 159 | break; 160 | default: 161 | LOGGER(LOGGER_LEVEL_INFO, "Change %s status: %d", "unknown", eventInfo->state); 162 | result = LOCALSDK_ERROR; 163 | } 164 | } 165 | } else result = LOCALSDK_ERROR; 166 | 167 | return result; 168 | } 169 | 170 | // Enable or disable alarm 171 | bool alarm_switch(bool state) { 172 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 173 | bool result = true; 174 | 175 | if (APP_CFG.alarm.enable == true) { 176 | LOGGER(LOGGER_LEVEL_INFO, "State: %s", (state ? "true" : "false")); 177 | 178 | // Switch alarm for motion 179 | if(result &= (local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_MOTION, state) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_MOTION)"); 180 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_MOTION)"); 181 | 182 | // Switch alarm for humanoid 183 | if(result &= (local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_HUMANOID, state)) == LOCALSDK_OK) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_HUMANOID)"); 184 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_set_alarm_switch(LOCALSDK_ALARM_TYPE_HUMANOID)"); 185 | } else { 186 | LOGGER(LOGGER_LEVEL_INFO, "Alarm switch ignored, because alarms are disabled"); 187 | } 188 | 189 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 190 | return result; 191 | } 192 | 193 | // Init alarm 194 | bool alarm_init() { 195 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 196 | bool result = true; 197 | 198 | int changed_resolution_type; 199 | LOCALSDK_PICTURE_SIZE picture_size; 200 | if (APP_CFG.alarm.enable == true) { 201 | if(result &= (inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360, &changed_resolution_type) == LOCALSDK_OK)) { 202 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 203 | if(result &= (SAMPLE_COMM_SYS_GetPicSize(changed_resolution_type, &picture_size) == LOCALSDK_OK)) { 204 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 205 | if(result &= (local_sdk_alarm_init(picture_size.width, picture_size.height) == LOCALSDK_OK)) { 206 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_alarm_init()"); 207 | if(result &= (local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_MOTION, APP_CFG.alarm.motion_sens) == LOCALSDK_OK)) { 208 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_MOTION)"); 209 | if(result &= (local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_HUMANOID, APP_CFG.alarm.humanoid_sens) == LOCALSDK_OK)) { 210 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_HUMANOID)"); 211 | if(result &= (local_sdk_alarm_state_set_callback(alarm_state_callback) == LOCALSDK_OK)) { 212 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_alarm_state_set_callback(alarm_state_callback)"); 213 | if(result &= (pthread_create(&timeout_thread, NULL, alarm_state_timeout, NULL) == 0)) { 214 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_create(timeout_thread)"); 215 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "pthread_create(timeout_thread)"); 216 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_alarm_state_set_callback(alarm_state_callback)"); 217 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_HUMANOID)"); 218 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_set_alarm_sensitivity(LOCALSDK_ALARM_TYPE_MOTION)"); 219 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_alarm_init()"); 220 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "SAMPLE_COMM_SYS_GetPicSize(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 221 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "inner_change_resulu_type(LOCALSDK_VIDEO_RESOLUTION_640x360)"); 222 | } else LOGGER(LOGGER_LEVEL_INFO, "Alarm init skipped, because alarms are disabled"); 223 | 224 | // Free alarm if error occurred 225 | if(!result) { 226 | if(alarm_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_free()"); 227 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "alarm_free()"); 228 | } 229 | 230 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 231 | return result; 232 | } 233 | 234 | // Free alarm 235 | bool alarm_free() { 236 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 237 | bool result = true; 238 | 239 | // Disable alarm 240 | if(result &= alarm_switch(false)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "alarm_switch(false)"); 241 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "alarm_switch(false)"); 242 | 243 | // Stop timeout thread 244 | if(timeout_thread) { 245 | if(result &= (pthread_cancel(timeout_thread) == 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_cancel(timeout_thread)"); 246 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "pthread_cancel(timeout_thread)"); 247 | } 248 | 249 | // Clear alarm state callback 250 | if(result &= (local_sdk_alarm_state_clear_callback(alarm_state_callback) == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_alarm_state_clear_callback()"); 251 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_alarm_state_clear_callback()"); 252 | 253 | // Alarm exit 254 | if(result &= (local_sdk_alarm_exit() == LOCALSDK_OK)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_alarm_exit()"); 255 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "local_sdk_alarm_exit()"); 256 | 257 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 258 | return result; 259 | } 260 | -------------------------------------------------------------------------------- /configs/configs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "./configs.h" 5 | #include "./inih/ini.h" 6 | #include "./../localsdk/localsdk.h" 7 | #include "./../logger/logger.h" 8 | 9 | // Default values 10 | APPLICATION_CONFIGURATION APP_CFG = { 11 | // [general] 12 | .general.name = "My Camera", // Device name 13 | .general.led = true, // Enable onboard LED indicator 14 | 15 | // [logger] 16 | .logger.level = LOGGER_LEVEL_WARNING, // Log level 17 | .logger.file = "", // Write log to file 18 | 19 | // [osd] 20 | .osd.enable = false, // Enable On-Screen Display (OSD) 21 | .osd.oemlogo = true, // Display OEM logo (MI) 22 | .osd.oemlogo_x = 2, // X position of the OEM logo 23 | .osd.oemlogo_y = 0, // Y position of the OEM logo 24 | .osd.oemlogo_size = 0, // Size of the OEM logo (can take negative values) 25 | .osd.datetime = true, // Display date and time 26 | .osd.datetime_x = 48, // X position of the date and time 27 | .osd.datetime_y = 0, // Y position of the date and time 28 | .osd.datetime_size = 0, // Size of the date and time (can take negative values) 29 | .osd.motion = false, // Display detected motions in rectangles 30 | .osd.humanoid = false, // Display detected humanoids in rectangles 31 | 32 | // [video] 33 | .video.gop = 1, // Group of pictures (GOP) every N*FPS (20) 34 | .video.flip = false, // Flip image (all channels) 35 | .video.mirror = false, // Mirror image (all channels) 36 | .video.primary_type = LOCALSDK_VIDEO_PAYLOAD_H264, // Video compression standard for primary channel 37 | .video.secondary_type = LOCALSDK_VIDEO_PAYLOAD_H264, // Video compression standard for secondary channel 38 | .video.primary_bitrate = 1800, // Bitrate for primary channel 39 | .video.secondary_bitrate = 900, // Bitrate for secondary channel 40 | .video.primary_rcmode = LOCALSDK_VIDEO_RCMODE_VARIABLE_BITRATE, // Rate control mode for primary channel 41 | .video.secondary_rcmode = LOCALSDK_VIDEO_RCMODE_VARIABLE_BITRATE, // Rate control mode for secondary channel 42 | 43 | // [audio] 44 | .audio.volume = 70, // Volume (0-100) 45 | .audio.primary_enable = true, // Enable audio for primary channel 46 | .audio.secondary_enable = true, // Enable audio for secondary channel 47 | 48 | // [speaker] 49 | .speaker.volume = 70, // Volume (0-100) 50 | .speaker.type = LOCALSDK_SPEAKER_PCM_TYPE, // Default file format 51 | 52 | // [alarm] 53 | .alarm.enable = true, // Alarms on/off 54 | .alarm.motion_sens = 150, // Motion sensitivity (1-255) 55 | .alarm.humanoid_sens = 150, // Humanoid sensitivity (1-255) 56 | .alarm.motion_timeout = 60, // Motion timeout (in seconds) 57 | .alarm.humanoid_timeout = 60, // Humanoid timeout (in seconds) 58 | .alarm.motion_detect_exec = "", // Execute the command when motion is detected 59 | .alarm.humanoid_detect_exec = "", // Execute the command when humanoid is detected 60 | .alarm.motion_lost_exec = "", // Execute the command when motion is lost 61 | .alarm.humanoid_lost_exec = "", // Execute the command when humanoid is lost 62 | 63 | // [rtsp] 64 | .rtsp.enable = true, // Enable RTSP server 65 | .rtsp.port = 554, // Port number 66 | .rtsp.username = "", // Username (empty for disable) 67 | .rtsp.password = "", // Password 68 | .rtsp.primary_name = "primary", // Name of the primary channel 69 | .rtsp.secondary_name = "secondary", // Name of the secondary channel 70 | .rtsp.primary_multicast = false, // Use multicast for primary channel 71 | .rtsp.secondary_multicast = false, // Use multicast for secondary channel 72 | .rtsp.primary_split_vframes = true, // Split video frames into separate packets for primary channel 73 | .rtsp.secondary_split_vframes = true, // Split video frames into separate packets for secondary channel 74 | 75 | // [mqtt] 76 | .mqtt.enable = false, // Enable MQTT client 77 | .mqtt.server = "", // Server address 78 | .mqtt.port = 1883, // Port number 79 | .mqtt.username = "", // Username (empty for anonimous) 80 | .mqtt.password = "", // Password (empty for disable) 81 | .mqtt.topic = "mjsxj02hl", // Name of the root topic 82 | .mqtt.qos = 1, // Quality of Service (0, 1 or 2) 83 | .mqtt.retain = true, // Retained messages 84 | .mqtt.reconnection_interval = 60, // Reconnection interval (in seconds) 85 | .mqtt.periodical_interval = 60, // Interval of periodic message (in seconds) 86 | .mqtt.discovery = "homeassistant", // Discovery prefix (https://www.home-assistant.io/docs/mqtt/discovery/#discovery-topic) 87 | 88 | // [night] 89 | .night.mode = 2, // Night mode (0 = off, 1 = on, 2 = auto) 90 | .night.gray = 2, // Grayscale (0 = off, 1 = on, 2 = auto) 91 | }; 92 | 93 | // Handler for ini parser 94 | static int parser_handler(void* cfg, const char *section, const char *name, const char *value) { 95 | bool result = true; 96 | 97 | APPLICATION_CONFIGURATION* config = (APPLICATION_CONFIGURATION*) cfg; 98 | #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 99 | #define atob(v) strcmp(value, "true") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "on") == 0 || strcmp(value, "1") == 0 100 | 101 | // [general] 102 | if(MATCH("general", "name")) { 103 | config->general.name = strdup(value); 104 | } else if(MATCH("general", "led")) { 105 | config->general.led = atob(value); 106 | 107 | // [logger] 108 | } else if(MATCH("logger", "level")) { 109 | config->logger.level = atoi(value); 110 | } else if(MATCH("logger", "file")) { 111 | config->logger.file = strdup(value); 112 | 113 | // [osd] 114 | } else if(MATCH("osd", "enable")) { 115 | config->osd.enable = atob(value); 116 | } else if(MATCH("osd", "oemlogo")) { 117 | config->osd.oemlogo = atob(value); 118 | } else if(MATCH("osd", "oemlogo_x")) { 119 | config->osd.oemlogo_x = atoi(value); 120 | } else if(MATCH("osd", "oemlogo_y")) { 121 | config->osd.oemlogo_y = atoi(value); 122 | } else if(MATCH("osd", "oemlogo_size")) { 123 | config->osd.oemlogo_size = atoi(value); 124 | } else if(MATCH("osd", "datetime")) { 125 | config->osd.datetime = atob(value); 126 | } else if(MATCH("osd", "datetime_x")) { 127 | config->osd.datetime_x = atoi(value); 128 | } else if(MATCH("osd", "datetime_y")) { 129 | config->osd.datetime_y = atoi(value); 130 | } else if(MATCH("osd", "datetime_size")) { 131 | config->osd.datetime_size = atoi(value); 132 | } else if(MATCH("osd", "motion")) { 133 | config->osd.motion = atob(value); 134 | } else if(MATCH("osd", "humanoid")) { 135 | config->osd.humanoid = atob(value); 136 | 137 | // [video] 138 | } else if(MATCH("video", "gop")) { 139 | config->video.gop = atoi(value); 140 | } else if(MATCH("video", "flip")) { 141 | config->video.flip = atob(value); 142 | } else if(MATCH("video", "mirror")) { 143 | config->video.mirror = atob(value); 144 | } else if(MATCH("video", "primary_type")) { 145 | config->video.primary_type = atoi(value); 146 | } else if(MATCH("video", "secondary_type")) { 147 | config->video.secondary_type = atoi(value); 148 | } else if(MATCH("video", "primary_bitrate")) { 149 | config->video.primary_bitrate = atoi(value); 150 | } else if(MATCH("video", "secondary_bitrate")) { 151 | config->video.secondary_bitrate = atoi(value); 152 | } else if(MATCH("video", "primary_rcmode")) { 153 | config->video.primary_rcmode = atoi(value); 154 | } else if(MATCH("video", "secondary_rcmode")) { 155 | config->video.secondary_rcmode = atoi(value); 156 | 157 | // [audio] 158 | } else if(MATCH("audio", "volume")) { 159 | config->audio.volume = atoi(value); 160 | } else if(MATCH("audio", "primary_enable")) { 161 | config->audio.primary_enable = atob(value); 162 | } else if(MATCH("audio", "secondary_enable")) { 163 | config->audio.secondary_enable = atob(value); 164 | 165 | // [speaker] 166 | } else if(MATCH("speaker", "volume")) { 167 | config->speaker.volume = atoi(value); 168 | } else if(MATCH("speaker", "type")) { 169 | config->speaker.type = atoi(value); 170 | 171 | // [alarm] 172 | } else if(MATCH("alarm", "enable")) { 173 | config->alarm.enable = atob(value); 174 | } else if(MATCH("alarm", "motion_sens")) { 175 | config->alarm.motion_sens = atoi(value); 176 | } else if(MATCH("alarm", "humanoid_sens")) { 177 | config->alarm.humanoid_sens = atoi(value); 178 | } else if(MATCH("alarm", "motion_timeout")) { 179 | config->alarm.motion_timeout = atoi(value); 180 | } else if(MATCH("alarm", "humanoid_timeout")) { 181 | config->alarm.humanoid_timeout = atoi(value); 182 | } else if(MATCH("alarm", "motion_detect_exec")) { 183 | config->alarm.motion_detect_exec = strdup(value); 184 | } else if(MATCH("alarm", "humanoid_detect_exec")) { 185 | config->alarm.humanoid_detect_exec = strdup(value); 186 | } else if(MATCH("alarm", "motion_lost_exec")) { 187 | config->alarm.motion_lost_exec = strdup(value); 188 | } else if(MATCH("alarm", "humanoid_lost_exec")) { 189 | config->alarm.humanoid_lost_exec = strdup(value); 190 | 191 | // [rtsp] 192 | } else if(MATCH("rtsp", "enable")) { 193 | config->rtsp.enable = atob(value); 194 | } else if(MATCH("rtsp", "port")) { 195 | config->rtsp.port = atoi(value); 196 | } else if(MATCH("rtsp", "username")) { 197 | config->rtsp.username = strdup(value); 198 | } else if(MATCH("rtsp", "password")) { 199 | config->rtsp.password = strdup(value); 200 | } else if(MATCH("rtsp", "primary_name")) { 201 | config->rtsp.primary_name = strdup(value); 202 | } else if(MATCH("rtsp", "secondary_name")) { 203 | config->rtsp.secondary_name = strdup(value); 204 | } else if(MATCH("rtsp", "primary_multicast")) { 205 | config->rtsp.primary_multicast = atob(value); 206 | } else if(MATCH("rtsp", "secondary_multicast")) { 207 | config->rtsp.secondary_multicast = atob(value); 208 | } else if(MATCH("rtsp", "primary_split_vframes")) { 209 | config->rtsp.primary_split_vframes = atob(value); 210 | } else if(MATCH("rtsp", "secondary_split_vframes")) { 211 | config->rtsp.secondary_split_vframes = atob(value); 212 | 213 | // [mqtt] 214 | } else if(MATCH("mqtt", "enable")) { 215 | config->mqtt.enable = atob(value); 216 | } else if(MATCH("mqtt", "server")) { 217 | config->mqtt.server = strdup(value); 218 | } else if(MATCH("mqtt", "port")) { 219 | config->mqtt.port = atoi(value); 220 | } else if(MATCH("mqtt", "username")) { 221 | config->mqtt.username = strdup(value); 222 | } else if(MATCH("mqtt", "password")) { 223 | config->mqtt.password = strdup(value); 224 | } else if(MATCH("mqtt", "topic")) { 225 | config->mqtt.topic = strdup(value); 226 | } else if(MATCH("mqtt", "qos")) { 227 | config->mqtt.qos = atoi(value); 228 | } else if(MATCH("mqtt", "retain")) { 229 | config->mqtt.retain = atob(value); 230 | } else if(MATCH("mqtt", "reconnection_interval")) { 231 | config->mqtt.reconnection_interval = atoi(value); 232 | } else if(MATCH("mqtt", "periodical_interval")) { 233 | config->mqtt.periodical_interval = atoi(value); 234 | } else if(MATCH("mqtt", "discovery")) { 235 | config->mqtt.discovery = strdup(value); 236 | 237 | // [night] 238 | } else if(MATCH("night", "mode")) { 239 | config->night.mode = atoi(value); 240 | } else if(MATCH("night", "gray")) { 241 | config->night.gray = atoi(value); 242 | 243 | // unknown 244 | } else result &= false; 245 | 246 | if(result) LOGGER(LOGGER_LEVEL_INFO, "Parse success. Section: %s, name: %s, value: %s", section, name, value); 247 | else LOGGER(LOGGER_LEVEL_WARNING, "Parse error! Section: %s, name: %s, value: %s", section, name, value); 248 | 249 | return result; 250 | } 251 | 252 | // Init application configs 253 | bool configs_init(char *filename) { 254 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 255 | bool result = true; 256 | 257 | // Read values from config file 258 | LOGGER(LOGGER_LEVEL_INFO, "Filename: %s", filename); 259 | if(result &= (ini_parse(filename, parser_handler, &APP_CFG) >= 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "ini_parse()"); 260 | else { 261 | LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "ini_parse()"); 262 | if(configs_free()) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "configs_free()"); 263 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "configs_free()"); 264 | } 265 | 266 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 267 | return result; 268 | } 269 | 270 | // Free application configs 271 | bool configs_free() { 272 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 273 | LOGGER(LOGGER_LEVEL_DEBUG, "This function is a stub."); 274 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", "true"); 275 | return true; 276 | } 277 | -------------------------------------------------------------------------------- /mqtt/homeassistant.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | 7 | #include "./homeassistant.h" 8 | #include "./mqtt.h" 9 | #include "./../logger/logger.h" 10 | #include "./../localsdk/init.h" 11 | #include "./../configs/configs.h" 12 | #include "./../yyjson/src/yyjson.h" 13 | 14 | // Global variables 15 | static char *mqtt_homeassistant_json_client_id = ""; 16 | static char *mqtt_homeassistant_json_device_name = ""; 17 | static char *mqtt_homeassistant_json_fw_version = ""; 18 | static char *mqtt_homeassistant_json_state_topic = ""; 19 | 20 | static char *mqtt_homeassistant_json_object_id = ""; 21 | static char *mqtt_homeassistant_json_default_entity_id = ""; 22 | static char *mqtt_homeassistant_json_unique_id = ""; 23 | static char *mqtt_homeassistant_json_topic_name = ""; 24 | static char *mqtt_homeassistant_json_value_template = ""; 25 | 26 | // Device info 27 | static bool mqtt_homeassistant_json_device(yyjson_mut_doc *json_doc, yyjson_mut_val *json_root) { 28 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 29 | bool result = true; 30 | 31 | // Create device object 32 | yyjson_mut_val *device_object = yyjson_mut_obj(json_doc); 33 | // Device identifiers 34 | yyjson_mut_val *identifiers_array = yyjson_mut_arr(json_doc); 35 | mqtt_homeassistant_json_client_id = mqtt_client_id(); 36 | if(result &= yyjson_mut_arr_add_str(json_doc, identifiers_array, mqtt_homeassistant_json_client_id)) { 37 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_arr_add_str(mqtt_homeassistant_json_client_id)"); 38 | if(result &= yyjson_mut_obj_add_val(json_doc, device_object, "identifiers", identifiers_array)) { 39 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_val(identifiers)"); 40 | // Device manufacturer 41 | if(result &= yyjson_mut_obj_add_str(json_doc, device_object, "manufacturer", MQTT_HOMEASSISTANT_MANUFACTURER)) { 42 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(manufacturer)"); 43 | // Device model 44 | if(result &= yyjson_mut_obj_add_str(json_doc, device_object, "model", MQTT_HOMEASSISTANT_MODEL)) { 45 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(model)"); 46 | // Device name 47 | char *mqtt_topic = mqtt_prepare_string(APP_CFG.mqtt.topic); 48 | char *general_name = mqtt_prepare_string(APP_CFG.general.name); 49 | if(result &= (asprintf(&mqtt_homeassistant_json_device_name, "%s_%s", mqtt_topic, general_name) != -1)) { 50 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_device_name)"); 51 | if(result &= yyjson_mut_obj_add_str(json_doc, device_object, "name", mqtt_homeassistant_json_device_name)) { 52 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(name)"); 53 | // Device sw_version 54 | mqtt_homeassistant_json_fw_version = firmware_version(); 55 | if(result &= yyjson_mut_obj_add_str(json_doc, device_object, "sw_version", mqtt_homeassistant_json_fw_version)) { 56 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(sw_version)"); 57 | // Apply device object 58 | if(result &= yyjson_mut_obj_add_val(json_doc, json_root, "device", device_object)) { 59 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_val(device)"); 60 | // Availability 61 | yyjson_mut_val *availability_array = yyjson_mut_arr(json_doc); 62 | yyjson_mut_val *availability_item = yyjson_mut_arr_add_obj(json_doc, availability_array); 63 | mqtt_homeassistant_json_state_topic = mqtt_fulltopic(MQTT_STATE_TOPIC); 64 | if(result &= yyjson_mut_obj_add_str(json_doc, availability_item, "topic", mqtt_homeassistant_json_state_topic)) { 65 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(topic)"); 66 | if(result &= yyjson_mut_obj_add_val(json_doc, json_root, "availability", availability_array)) { 67 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_val(availability)"); 68 | // Availability_mode 69 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "availability_mode", "all")) { 70 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(availability_mode)"); 71 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(availability_mode)"); 72 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_val(availability)"); 73 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(topic)"); 74 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_val(device)"); 75 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(sw_version)"); 76 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(name)"); 77 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_device_name)"); 78 | free(general_name); 79 | free(mqtt_topic); 80 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(model)"); 81 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(manufacturer)"); 82 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_val(identifiers)"); 83 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_arr_add_str(mqtt_homeassistant_json_client_id)"); 84 | 85 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 86 | return result; 87 | } 88 | 89 | // Sensor (MQTT_HOMEASSISTANT_SENSOR) 90 | static bool mqtt_homeassistant_json_sensor(yyjson_mut_doc *json_doc, yyjson_mut_val *json_root, char *topic_name, char *json_field, char *device_class, char *unit_of_measurement, bool enabled) { 91 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 92 | bool result = true; 93 | 94 | // Sensor name 95 | char *mqtt_topic = mqtt_prepare_string(APP_CFG.mqtt.topic); 96 | char *general_name = mqtt_prepare_string(APP_CFG.general.name); 97 | 98 | // Object id 99 | if(result &= (asprintf(&mqtt_homeassistant_json_object_id, "%s_%s_%s_%s", mqtt_topic, general_name, topic_name, json_field) != -1)) { 100 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_object_id)"); 101 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "object_id", mqtt_homeassistant_json_object_id)) { 102 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(object_id)"); 103 | // Default entity id 104 | if(result &= (asprintf(&mqtt_homeassistant_json_default_entity_id, "sensor.%s_%s_%s_%s", mqtt_topic, general_name, topic_name, json_field) != -1)) { 105 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_default_entity_id)"); 106 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "default_entity_id", mqtt_homeassistant_json_default_entity_id)) { 107 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(default_entity_id)"); 108 | // Unique id 109 | char *client_id = mqtt_client_id(); 110 | if(result &= (asprintf(&mqtt_homeassistant_json_unique_id, "%s_%s_%s", client_id, topic_name, json_field) != -1)) { 111 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_unique_id)"); 112 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "unique_id", mqtt_homeassistant_json_unique_id)) { 113 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(unique_id)"); 114 | // Device class 115 | if(device_class != NULL) { 116 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "device_class", device_class)) { 117 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(device_class)"); 118 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(device_class)"); 119 | } 120 | // Units of measurement 121 | if(unit_of_measurement != NULL) { 122 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "unit_of_measurement", unit_of_measurement)) { 123 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(unit_of_measurement)"); 124 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(unit_of_measurement)"); 125 | } 126 | // Enable by default 127 | if(result &= yyjson_mut_obj_add_bool(json_doc, json_root, "enabled_by_default", enabled)) { 128 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(enabled_by_default)"); 129 | // State topic 130 | mqtt_homeassistant_json_topic_name = mqtt_fulltopic(topic_name); 131 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "state_topic", mqtt_homeassistant_json_topic_name)) { 132 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(state_topic)"); 133 | // Value template 134 | if(result &= (asprintf(&mqtt_homeassistant_json_value_template, "{{ value_json.%s }}", json_field) != -1)) { 135 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_value_template)"); 136 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "value_template", mqtt_homeassistant_json_value_template)) { 137 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(value_template)"); 138 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(value_template)"); 139 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_value_template)"); 140 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(state_topic)"); 141 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool(enabled_by_default)"); 142 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(unique_id)"); 143 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_unique_id)"); 144 | free(client_id); 145 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(default_entity_id)"); 146 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_default_entity_id)"); 147 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(object_id)"); 148 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_object_id)"); 149 | 150 | free(general_name); 151 | free(mqtt_topic); 152 | 153 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 154 | return result; 155 | } 156 | 157 | // Binary sensor (MQTT_HOMEASSISTANT_BINARY_SENSOR) 158 | static bool mqtt_homeassistant_json_binary_sensor(yyjson_mut_doc *json_doc, yyjson_mut_val *json_root, char *topic_name, char *json_field, char *device_class, int off_delay, bool enabled) { 159 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 160 | bool result = true; 161 | 162 | // Sensor name 163 | char *mqtt_topic = mqtt_prepare_string(APP_CFG.mqtt.topic); 164 | char *general_name = mqtt_prepare_string(APP_CFG.general.name); 165 | 166 | // Object id 167 | if(result &= (asprintf(&mqtt_homeassistant_json_object_id, "%s_%s_%s_%s", mqtt_topic, general_name, topic_name, json_field) != -1)) { 168 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_object_id)"); 169 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "object_id", mqtt_homeassistant_json_object_id)) { 170 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(object_id)"); 171 | // Default entity id 172 | if(result &= (asprintf(&mqtt_homeassistant_json_default_entity_id, "binary_sensor.%s_%s_%s_%s", mqtt_topic, general_name, topic_name, json_field) != -1)) { 173 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_default_entity_id)"); 174 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "default_entity_id", mqtt_homeassistant_json_default_entity_id)) { 175 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(default_entity_id)"); 176 | // Unique id 177 | char *client_id = mqtt_client_id(); 178 | if(result &= (asprintf(&mqtt_homeassistant_json_unique_id, "%s_%s_%s", client_id, topic_name, json_field) != -1)) { 179 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_unique_id)"); 180 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "unique_id", mqtt_homeassistant_json_unique_id)) { 181 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(unique_id)"); 182 | // Device class 183 | if(device_class != NULL) { 184 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "device_class", device_class)) { 185 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(device_class)"); 186 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(device_class)"); 187 | } 188 | // Off delay 189 | if(off_delay > 0) { 190 | if(result &= yyjson_mut_obj_add_int(json_doc, json_root, "off_delay", off_delay)) { 191 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_int(off_delay)"); 192 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_int(off_delay)"); 193 | } 194 | // Enable by default 195 | if(result &= yyjson_mut_obj_add_bool(json_doc, json_root, "enabled_by_default", enabled)) { 196 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(enabled_by_default)"); 197 | // State topic 198 | mqtt_homeassistant_json_topic_name = mqtt_fulltopic(topic_name); 199 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "state_topic", mqtt_homeassistant_json_topic_name)) { 200 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(state_topic)"); 201 | // Value template 202 | if(result &= (asprintf(&mqtt_homeassistant_json_value_template, "{{ value_json.%s }}", json_field) != -1)) { 203 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(mqtt_homeassistant_json_value_template)"); 204 | if(result &= yyjson_mut_obj_add_str(json_doc, json_root, "value_template", mqtt_homeassistant_json_value_template)) { 205 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(value_template)"); 206 | // Payload values 207 | if(result &= yyjson_mut_obj_add_bool(json_doc, json_root, "payload_on", true)) { 208 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(payload_on)"); 209 | if(result &= yyjson_mut_obj_add_bool(json_doc, json_root, "payload_off", false)) { 210 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(payload_off)"); 211 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool(payload_off)"); 212 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool(payload_on)"); 213 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(value_template)"); 214 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_value_template)"); 215 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(state_topic)"); 216 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool(enabled_by_default)"); 217 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(unique_id)"); 218 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_unique_id)"); 219 | free(client_id); 220 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(default_entity_id)"); 221 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_default_entity_id)"); 222 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(object_id)"); 223 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(mqtt_homeassistant_json_object_id)"); 224 | 225 | free(general_name); 226 | free(mqtt_topic); 227 | 228 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 229 | return result; 230 | } 231 | 232 | // Add device to Home Assistant over discovery protocol 233 | bool mqtt_homeassistant_discovery(int type, char *topic_name, char *json_field, char *device_class, char *unit_of_measurement, int off_delay, bool enabled) { 234 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 235 | bool result = true; 236 | 237 | if(APP_CFG.mqtt.discovery && APP_CFG.mqtt.discovery[0]) { 238 | 239 | // JSON Data 240 | yyjson_mut_doc *json_doc = yyjson_mut_doc_new(NULL); 241 | yyjson_mut_val *json_root = yyjson_mut_obj(json_doc); 242 | yyjson_mut_doc_set_root(json_doc, json_root); 243 | 244 | // Device info 245 | if(result &= mqtt_homeassistant_json_device(json_doc, json_root)) { 246 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_json_device()"); 247 | 248 | // Select sensor by type 249 | char *sensor_type = ""; 250 | switch(type) { 251 | case MQTT_HOMEASSISTANT_SENSOR: 252 | sensor_type = "sensor"; 253 | if(result &= mqtt_homeassistant_json_sensor(json_doc, json_root, topic_name, json_field, device_class, unit_of_measurement, enabled)) { 254 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_json_sensor()"); 255 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_json_sensor()"); 256 | break; 257 | case MQTT_HOMEASSISTANT_BINARY_SENSOR: 258 | sensor_type = "binary_sensor"; 259 | if(result &= mqtt_homeassistant_json_binary_sensor(json_doc, json_root, topic_name, json_field, device_class, off_delay, enabled)) { 260 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_json_binary_sensor()"); 261 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_json_binary_sensor()"); 262 | break; 263 | default: result &= false; 264 | } 265 | 266 | // Payload 267 | if(result) { 268 | const char *json = yyjson_mut_write(json_doc, 0, NULL); 269 | if(result &= !!json) { 270 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_write()"); 271 | char *object_id = ""; 272 | if(result &= (asprintf(&object_id, "%s_%s", topic_name, json_field) != -1)) { 273 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(object_id)"); 274 | char *discovery_topic = ""; 275 | char *dev_id = device_id(); 276 | if(result &= (asprintf(&discovery_topic, "%s/%s/%s/%s/config", APP_CFG.mqtt.discovery, sensor_type, dev_id, object_id) != -1)) { 277 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(discovery_topic)"); 278 | 279 | // Send 280 | if(result &= mqtt_send(discovery_topic, (char *) json)) { 281 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_HOMEASSISTANT_DISCOVERY)"); 282 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_HOMEASSISTANT_DISCOVERY)"); 283 | 284 | free(discovery_topic); 285 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(discovery_topic)"); 286 | free(dev_id); 287 | free(object_id); 288 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(object_id)"); 289 | free((void *)json); 290 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_write()"); 291 | 292 | // Free sensor resources 293 | free(mqtt_homeassistant_json_value_template); 294 | free(mqtt_homeassistant_json_topic_name); 295 | free(mqtt_homeassistant_json_unique_id); 296 | free(mqtt_homeassistant_json_default_entity_id); 297 | free(mqtt_homeassistant_json_object_id); 298 | } 299 | 300 | // Free device resources 301 | free(mqtt_homeassistant_json_state_topic); 302 | free(mqtt_homeassistant_json_fw_version); 303 | free(mqtt_homeassistant_json_device_name); 304 | free(mqtt_homeassistant_json_client_id); 305 | 306 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_json_device()"); 307 | 308 | // Free resources 309 | yyjson_mut_doc_free(json_doc); 310 | 311 | } else LOGGER(LOGGER_LEVEL_INFO, "Home Assistant Discovery is disabled in the settings or discovery prefix not set."); 312 | 313 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 314 | return result; 315 | } 316 | -------------------------------------------------------------------------------- /mqtt/mqtt.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "./mqtt.h" 16 | #include "./homeassistant.h" 17 | #include "./paho.mqtt.c/src/MQTTClient.h" 18 | #include "./../logger/logger.h" 19 | #include "./../localsdk/init.h" 20 | #include "./../localsdk/localsdk.h" 21 | #include "./../localsdk/speaker/speaker.h" 22 | #include "./../configs/configs.h" 23 | #include "./../yyjson/src/yyjson.h" 24 | #include "./../ipctool/include/ipchw.h" 25 | 26 | static MQTTClient MQTTclient; 27 | static pthread_t periodical_thread; 28 | static pthread_t reconnection_thread; 29 | static pthread_t playmedia_thread; 30 | 31 | // Prepare string for use as MQTT paths/names 32 | char *mqtt_prepare_string(const char *string) { 33 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 34 | 35 | // Device name 36 | size_t j = 0; 37 | size_t length = strlen(string) + 1; 38 | char *device_name = malloc(length); 39 | if(device_name != NULL) { 40 | memset(device_name, '\0', length); 41 | for(size_t i = 0; string[i] != '\0'; i++) { 42 | char chr = tolower(string[i]); 43 | if(isspace(chr) || (chr == '/')) { 44 | device_name[i-j] = '_'; 45 | } else if(isalnum(chr)) { 46 | device_name[i-j] = chr; 47 | } else j++; 48 | } 49 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "malloc(length)"); 50 | 51 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 52 | return device_name; 53 | } 54 | 55 | // Get clien id 56 | char *mqtt_client_id() { 57 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 58 | char *client_id = ""; 59 | 60 | char *mqtt_topic = mqtt_prepare_string(APP_CFG.mqtt.topic); 61 | if(mqtt_topic != NULL) { 62 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_prepare_string(APP_CFG.mqtt.topic)"); 63 | char *general_name = mqtt_prepare_string(APP_CFG.general.name); 64 | if(general_name != NULL) { 65 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_prepare_string(APP_CFG.general.name)"); 66 | char *dev_id = device_id(); 67 | if(dev_id != NULL) { 68 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "device_id()"); 69 | char *prep_id = mqtt_prepare_string(dev_id); 70 | if(prep_id != NULL) { 71 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_prepare_string(dev_id)"); 72 | if(asprintf(&client_id, "%s_%s_%s", mqtt_topic, general_name, prep_id) != -1) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(client_id)"); 73 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(client_id)"); 74 | free(prep_id); 75 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "mqtt_prepare_string(dev_id)"); 76 | free(dev_id); 77 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "device_id()"); 78 | free(general_name); 79 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "mqtt_prepare_string(APP_CFG.general.name)"); 80 | free(mqtt_topic); 81 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "mqtt_prepare_string(APP_CFG.mqtt.topic)"); 82 | 83 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 84 | return client_id; 85 | } 86 | 87 | // Get full topic 88 | char *mqtt_fulltopic(const char *topic) { 89 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 90 | char *payload = ""; 91 | 92 | char *general_name = mqtt_prepare_string(APP_CFG.general.name); 93 | if(general_name != NULL) { 94 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_prepare_string(APP_CFG.general.name)"); 95 | if(asprintf(&payload, "%s/%s/%s", APP_CFG.mqtt.topic, general_name, topic) != -1) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(payload)"); 96 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(payload)"); 97 | free(general_name); 98 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "mqtt_prepare_string(APP_CFG.general.name)"); 99 | 100 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 101 | return payload; 102 | } 103 | 104 | // Send data 105 | bool mqtt_send(const char *topic, char *payload) { 106 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 107 | bool result = true; 108 | 109 | if(mqtt_is_ready()) { 110 | LOGGER(LOGGER_LEVEL_INFO, "Topic: %s", topic); 111 | LOGGER(LOGGER_LEVEL_INFO, "Payload: %s", payload); 112 | MQTTClient_message message = MQTTClient_message_initializer; 113 | message.payload = payload; 114 | message.payloadlen = (int) strlen(payload); 115 | message.qos = APP_CFG.mqtt.qos; 116 | message.retained = APP_CFG.mqtt.retain; 117 | MQTTClient_deliveryToken token; 118 | if(result &= (MQTTClient_publishMessage(MQTTclient, topic, &message, &token) == MQTTCLIENT_SUCCESS)) { 119 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_publishMessage()"); 120 | LOGGER(LOGGER_LEVEL_INFO, "Token: %d", token); 121 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "MQTTClient_publishMessage()"); 122 | } 123 | 124 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 125 | return result; 126 | } 127 | 128 | // Send formatted data 129 | bool mqtt_sendf(const char *topic, const char *format, ...) { 130 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 131 | bool result = true; 132 | 133 | va_list params; 134 | va_start(params, format); 135 | char *payload = ""; 136 | if(result &= (vasprintf(&payload, format, params) != -1)) { 137 | if(result &= mqtt_send(topic, payload)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send()"); 138 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send()"); 139 | free(payload); 140 | } 141 | va_end(params); 142 | 143 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 144 | return result; 145 | } 146 | 147 | // Periodic data sending 148 | static void *mqtt_periodical(void *arg) { 149 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 150 | 151 | bool endless_cycle = (bool) arg; 152 | bool first = endless_cycle; 153 | do { //-V1044 154 | // First iteration 155 | if(first) { 156 | first = false; 157 | 158 | // Send online state 159 | char *state_topic = mqtt_fulltopic(MQTT_STATE_TOPIC); 160 | if(mqtt_send(state_topic, MQTT_STATE_ONLINE)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_STATE_TOPIC)"); 161 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_STATE_TOPIC)"); 162 | free(state_topic); 163 | } 164 | 165 | // Home Assistant Discovery 166 | 167 | // info/fw_version 168 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "fw_version", NULL, NULL, 0, false)) { 169 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, fw_version)"); 170 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, fw_version)"); 171 | // info/ip_address 172 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "ip_address", NULL, NULL, 0, false)) { 173 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, ip_address)"); 174 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, ip_address)"); 175 | // info/hw_temp 176 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "hw_temp", "temperature", "°C", 0, false)) { 177 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, hw_temp)"); 178 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, hw_temp)"); 179 | // info/total_ram 180 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "total_ram", NULL, "byte(s)", 0, false)) { 181 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_ram)"); 182 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_ram)"); 183 | // info/free_ram 184 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "free_ram", NULL, "byte(s)", 0, false)) { 185 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_ram)"); 186 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_ram)"); 187 | // info/total_sdmem 188 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "total_sdmem", NULL, "byte(s)", 0, false)) { 189 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_sdmem)"); 190 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_sdmem)"); 191 | // info/free_sdmem 192 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "free_sdmem", NULL, "byte(s)", 0, false)) { 193 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_sdmem)"); 194 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_sdmem)"); 195 | // info/total_configs 196 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "total_configs", NULL, "byte(s)", 0, false)) { 197 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_configs)"); 198 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, total_configs)"); 199 | // info/free_configs 200 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "free_configs", NULL, "byte(s)", 0, false)) { 201 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_configs)"); 202 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, free_configs)"); 203 | // info/volume_level 204 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "volume_level", NULL, "%", 0, false)) { 205 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, volume_level)"); 206 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, volume_level)"); 207 | // info/media_status 208 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "media_status", NULL, NULL, 0, false)) { 209 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, media_status)"); 210 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, media_status)"); 211 | // info/image_url 212 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, "fw_version", NULL, NULL, 0, false)) { 213 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, fw_version)"); 214 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_INFO_TOPIC, fw_version)"); 215 | 216 | if(APP_CFG.alarm.enable == true) { 217 | // alarm/motion 218 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_BINARY_SENSOR, MQTT_ALARM_TOPIC, "motion", "motion", NULL, APP_CFG.alarm.motion_timeout, true)) { 219 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_ALARM_TOPIC, motion)"); 220 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_ALARM_TOPIC, motion)"); 221 | // alarm/humanoid 222 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_BINARY_SENSOR, MQTT_ALARM_TOPIC, "humanoid", "motion", NULL, APP_CFG.alarm.humanoid_timeout, true)) { 223 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_ALARM_TOPIC, humanoid)"); 224 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_ALARM_TOPIC, humanoid)"); 225 | } 226 | 227 | // night/state 228 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_BINARY_SENSOR, MQTT_NIGHT_TOPIC, "state", NULL, NULL, 0, true)) { 229 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_NIGHT_TOPIC, state)"); 230 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_NIGHT_TOPIC, state)"); 231 | // night/gray 232 | if(mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_BINARY_SENSOR, MQTT_NIGHT_TOPIC, "gray", NULL, NULL, 0, true)) { 233 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_NIGHT_TOPIC, gray)"); 234 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_homeassistant_discovery(MQTT_HOMEASSISTANT_SENSOR, MQTT_NIGHT_TOPIC, gray)"); 235 | 236 | // Send system info 237 | yyjson_mut_doc *json_doc = yyjson_mut_doc_new(NULL); 238 | yyjson_mut_val *json_root = yyjson_mut_obj(json_doc); 239 | yyjson_mut_doc_set_root(json_doc, json_root); 240 | // FW version 241 | char *fw_version = firmware_version(); 242 | if(yyjson_mut_obj_add_str(json_doc, json_root, "fw_version", fw_version)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(fw_version)"); 243 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(fw_version)"); 244 | // IP address 245 | char *ip_address = ""; 246 | struct ifaddrs *if_list, *if_item; 247 | if(getifaddrs(&if_list) == 0) { 248 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "getifaddrs()"); 249 | for(if_item = if_list; if_item != NULL; if_item = if_item->ifa_next) { 250 | if(if_item->ifa_addr == NULL) continue; 251 | if(strcmp(if_item->ifa_name, "wlan0") == 0) { 252 | struct sockaddr_in * ip_item; 253 | ip_item = (struct sockaddr_in *) if_item->ifa_addr; 254 | if(ip_item->sin_family == AF_INET) { 255 | ip_address = inet_ntoa(ip_item->sin_addr); 256 | } 257 | } 258 | } 259 | freeifaddrs(if_list); 260 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "getifaddrs()"); 261 | if(yyjson_mut_obj_add_str(json_doc, json_root, "ip_address", ip_address)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(ip_address)"); 262 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(ip_address)"); 263 | // Temperature 264 | float hw_temp = gethwtemp(); 265 | if(yyjson_mut_obj_add_real(json_doc, json_root, "hw_temp", hw_temp)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_real(hw_temp)"); 266 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_real(hw_temp)"); 267 | // RAM 268 | unsigned long total_ram = 0; 269 | unsigned long free_ram = 0; 270 | struct sysinfo ram_info; 271 | if(sysinfo(&ram_info) == 0) { 272 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "sysinfo()"); 273 | total_ram = (unsigned long) ram_info.totalram; 274 | free_ram = (unsigned long) ram_info.freeram; 275 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "sysinfo()"); 276 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "total_ram", total_ram)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(total_ram)"); 277 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(total_ram)"); 278 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "free_ram", free_ram)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(free_ram)"); 279 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(free_ram)"); 280 | // SD-Card memory 281 | unsigned long total_sdmem = 0; 282 | unsigned long free_sdmem = 0; 283 | struct statvfs stat_mmc; 284 | if(statvfs("/mnt/mmc", &stat_mmc) == 0) { 285 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "statvfs(\"/mnt/mmc\")"); 286 | total_sdmem = (unsigned long) stat_mmc.f_frsize * stat_mmc.f_blocks; 287 | free_sdmem = (unsigned long) stat_mmc.f_bsize * stat_mmc.f_bfree; 288 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "statvfs(\"/mnt/mmc\")"); 289 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "total_sdmem", total_sdmem)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(total_sdmem)"); 290 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(total_sdmem)"); 291 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "free_sdmem", free_sdmem)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(free_sdmem)"); 292 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(free_sdmem)"); 293 | // Configs memory 294 | unsigned long total_configs = 0; 295 | unsigned long free_configs = 0; 296 | struct statvfs stat_configs; 297 | if(statvfs("/configs", &stat_configs) == 0) { 298 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "statvfs(\"/configs\")"); 299 | total_configs = (unsigned long) stat_configs.f_frsize * stat_configs.f_blocks; 300 | free_configs = (unsigned long) stat_configs.f_bsize * stat_configs.f_bfree; 301 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "statvfs(\"/configs\")"); 302 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "total_configs", total_configs)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(total_configs)"); 303 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(total_configs)"); 304 | if(yyjson_mut_obj_add_uint(json_doc, json_root, "free_configs", free_configs)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_uint(free_configs)"); 305 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_uint(free_configs)"); 306 | // Volume level 307 | int volume_level = speaker_get_volume(); 308 | if(yyjson_mut_obj_add_int(json_doc, json_root, "volume_level", volume_level)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_int(volume_level)"); 309 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_int(volume_level)"); 310 | // Media status 311 | bool media_status = speaker_status_media(); 312 | if(yyjson_mut_obj_add_bool(json_doc, json_root, "media_status", media_status)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_bool(media_status)"); 313 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_bool(media_status)"); 314 | // Image URL 315 | char *image_url = ""; 316 | if(asprintf(&image_url, "http://%s/cgi/get_image.cgi", ip_address) != -1) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(image_url)"); 317 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "asprintf(image_url)"); 318 | if(yyjson_mut_obj_add_str(json_doc, json_root, "image_url", image_url)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_obj_add_str(image_url)"); 319 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "yyjson_mut_obj_add_str(image_url)"); 320 | // Send it 321 | const char *json = yyjson_mut_write(json_doc, 0, NULL); 322 | if(json) { 323 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_mut_write()"); 324 | char *info_topic = mqtt_fulltopic(MQTT_INFO_TOPIC); 325 | if(mqtt_send(info_topic, (char *) json)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_INFO_TOPIC)"); 326 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_INFO_TOPIC)"); 327 | free(info_topic); 328 | free((void *)json); 329 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_mut_write()"); 330 | // Free resources 331 | yyjson_mut_doc_free(json_doc); 332 | free(image_url); 333 | free(fw_version); 334 | // Sleep 335 | if(endless_cycle) { 336 | LOGGER(LOGGER_LEVEL_INFO, "Wait %d seconds until the next sending...", APP_CFG.mqtt.periodical_interval); 337 | sleep(APP_CFG.mqtt.periodical_interval); 338 | pthread_testcancel(); 339 | } 340 | } while(endless_cycle); 341 | 342 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 343 | return 0; 344 | } 345 | 346 | // Play media (for pthread) 347 | static struct playmedia_args { 348 | char *filename; 349 | int type; 350 | } playmedia_args; 351 | 352 | static void* mqtt_playmedia(void *args) { 353 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 354 | 355 | struct playmedia_args *arguments = (struct playmedia_args *) args; 356 | if(speaker_play_media(arguments->filename, arguments->type)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_play_media()"); 357 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "speaker_play_media()"); 358 | 359 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 360 | return 0; 361 | } 362 | 363 | // Message received 364 | static int mqtt_message_callback(void *context, char *topicName, int topicLen, MQTTClient_message *message) { 365 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 366 | bool result = true; 367 | 368 | LOGGER(LOGGER_LEVEL_INFO, "Topic: %s", topicName); 369 | LOGGER(LOGGER_LEVEL_INFO, "Payload: %.*s", message->payloadlen, (char *) message->payload); 370 | 371 | // Parse JSON 372 | yyjson_doc *json_doc = yyjson_read((char *) message->payload, message->payloadlen, 0); 373 | yyjson_val *json_root = yyjson_doc_get_root(json_doc); 374 | yyjson_val *json_action = yyjson_obj_get(json_root, "action"); 375 | if(result &= !!json_action) { 376 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_obj_get(action)"); 377 | if(result &= yyjson_is_str(json_action)) { 378 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_str(json_action)"); 379 | // Get image 380 | if(strcmp(yyjson_get_str(json_action), "get_image") == 0) { 381 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(get_image)"); 382 | yyjson_val *json_filename = yyjson_obj_get(json_root, "filename"); 383 | if(result &= yyjson_is_str(json_filename)) { 384 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_str(json_filename)"); 385 | if(result &= (local_sdk_video_get_jpeg(LOCALSDK_VIDEO_SECONDARY_CHANNEL, (char *) yyjson_get_str(json_filename)) == LOCALSDK_OK)) { 386 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "local_sdk_video_get_jpeg()"); 387 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "local_sdk_video_get_jpeg()"); 388 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_is_str(json_filename)"); 389 | // Set volume 390 | } else if(strcmp(yyjson_get_str(json_action), "set_volume") == 0) { 391 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(set_volume)"); 392 | yyjson_val *json_value = yyjson_obj_get(json_root, "value"); 393 | if(result &= yyjson_is_int(json_value)) { 394 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_int(json_value)"); 395 | if(result &= speaker_set_volume(yyjson_get_int(json_value))) { 396 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_set_volume()"); 397 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "speaker_set_volume()"); 398 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_is_int(json_value)"); 399 | // Play media 400 | } else if(strcmp(yyjson_get_str(json_action), "play_media") == 0) { 401 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(play_media)"); 402 | yyjson_val *json_filename = yyjson_obj_get(json_root, "filename"); 403 | if(result &= yyjson_is_str(json_filename)) { 404 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_str(json_filename)"); 405 | playmedia_args.filename = (char *) yyjson_get_str(json_filename); 406 | // Set volume level 407 | yyjson_val *json_volume = yyjson_obj_get(json_root, "volume"); 408 | if(result &= yyjson_is_int(json_volume)) { 409 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_int(json_volume)"); 410 | if(result &= speaker_set_volume(yyjson_get_int(json_volume))) { 411 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_set_volume()"); 412 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "speaker_set_volume()"); 413 | } 414 | // Get media type 415 | playmedia_args.type = APP_CFG.speaker.type; 416 | yyjson_val *json_type = yyjson_obj_get(json_root, "type"); 417 | if(result &= yyjson_is_str(json_type)) { 418 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_int(json_type)"); 419 | if(result &= (strcmp(yyjson_get_str(json_type), "g711") == 0)) { 420 | playmedia_args.type = LOCALSDK_SPEAKER_G711_TYPE; 421 | } else { 422 | playmedia_args.type = LOCALSDK_SPEAKER_PCM_TYPE; 423 | } 424 | } 425 | // Play 426 | if(result &= (pthread_create(&playmedia_thread, NULL, mqtt_playmedia, (void *) &playmedia_args) == 0)) { 427 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_create(playmedia_thread)"); 428 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "pthread_create(playmedia_thread)"); 429 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_is_str(json_filename)"); 430 | // Stop playback 431 | } else if(strcmp(yyjson_get_str(json_action), "stop_media") == 0) { 432 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(stop_media)"); 433 | if(result &= speaker_stop_media()) { 434 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "speaker_stop_media()"); 435 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "speaker_stop_media()"); 436 | // System 437 | } else if(strcmp(yyjson_get_str(json_action), "system") == 0) { 438 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(system)"); 439 | yyjson_val *json_command = yyjson_obj_get(json_root, "command"); 440 | if(result &= yyjson_is_str(json_command)) { 441 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "yyjson_is_str(json_command)"); 442 | if(result &= (system((char *) yyjson_get_str(json_command)) == 0)) { 443 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(json_command)"); 444 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(json_command)"); 445 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_is_str(json_command)"); 446 | // Restart 447 | } else if(strcmp(yyjson_get_str(json_action), "restart") == 0) { 448 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(restart)"); 449 | if(result &= (system("restart.sh &") == 0)) { 450 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(restart.sh)"); 451 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(restart.sh)"); 452 | // Reboot 453 | } else if(strcmp(yyjson_get_str(json_action), "reboot") == 0) { 454 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "strcmp(reboot)"); 455 | if(result &= (system("reboot &") == 0)) { 456 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "system(reboot)"); 457 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "system(reboot)"); 458 | // Unknown action 459 | } else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "Unknown action"); 460 | mqtt_periodical((void *) false); // Send new data 461 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_is_str(json_action)"); 462 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "yyjson_obj_get(action)"); 463 | 464 | yyjson_doc_free(json_doc); 465 | MQTTClient_freeMessage(&message); 466 | MQTTClient_free(topicName); 467 | 468 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 469 | return result; 470 | } 471 | 472 | // Delivery confirmed 473 | static void mqtt_delivery_callback(void *context, MQTTClient_deliveryToken dt) { 474 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 475 | LOGGER(LOGGER_LEVEL_INFO, "Message with token value %d delivery confirmed.", dt); 476 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 477 | } 478 | 479 | // Lost connection 480 | static bool mqtt_initialization(bool first_init); 481 | static void mqtt_disconnect_callback(void *context, char *cause) { 482 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 483 | 484 | do { 485 | LOGGER(LOGGER_LEVEL_INFO, "The connection to the MQTT server was lost! Wait %d seconds...", APP_CFG.mqtt.reconnection_interval); 486 | mqtt_free(false); 487 | sleep(APP_CFG.mqtt.reconnection_interval); 488 | pthread_testcancel(); 489 | } while(mqtt_initialization(false) == false); 490 | 491 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 492 | } 493 | 494 | // Reconnect (for pthread) 495 | static void* mqtt_reconnection(void *args) { 496 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 497 | mqtt_disconnect_callback(NULL, NULL); 498 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed."); 499 | return 0; 500 | } 501 | 502 | // Is enabled 503 | bool mqtt_is_enabled() { 504 | return (APP_CFG.mqtt.enable && APP_CFG.mqtt.server && APP_CFG.mqtt.server[0]); 505 | } 506 | 507 | // Init mqtt (local) 508 | static bool mqtt_initialization(bool first_init) { 509 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 510 | bool result = true; 511 | 512 | if(mqtt_is_enabled()) { // If MQTT enabled 513 | // Get server address 514 | char *server_address = ""; 515 | if(result &= (asprintf(&server_address, "tcp://%s:%d", APP_CFG.mqtt.server, APP_CFG.mqtt.port) != -1)) { 516 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "asprintf(server_address)"); 517 | // Create MQTT client 518 | char *client_id = mqtt_client_id(); 519 | if(result &= (MQTTClient_create(&MQTTclient, server_address, client_id, MQTTCLIENT_PERSISTENCE_NONE, NULL) == MQTTCLIENT_SUCCESS)) { 520 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_create()"); 521 | // Set callbacks 522 | if(result &= (MQTTClient_setCallbacks(MQTTclient, NULL, mqtt_disconnect_callback, mqtt_message_callback, mqtt_delivery_callback) == MQTTCLIENT_SUCCESS)) { 523 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_setCallbacks()"); 524 | // Connection options 525 | MQTTClient_connectOptions connect_options = MQTTClient_connectOptions_initializer; 526 | connect_options.connectTimeout = MQTT_TIMEOUT; 527 | // Get authorization data 528 | if(APP_CFG.mqtt.username && APP_CFG.mqtt.username[0]) { 529 | connect_options.username = APP_CFG.mqtt.username; 530 | LOGGER(LOGGER_LEVEL_INFO, "Username: %s", connect_options.username); 531 | if(APP_CFG.mqtt.password && APP_CFG.mqtt.password[0]) { 532 | connect_options.password = APP_CFG.mqtt.password; 533 | LOGGER(LOGGER_LEVEL_INFO, "Password: %s", ""); 534 | } else LOGGER(LOGGER_LEVEL_INFO, "%s success.", "Password: %s", " (shared connection)"); 535 | } else LOGGER(LOGGER_LEVEL_INFO, "%s success.", "Username: %s", " (anonymous connection)"); 536 | // LWT 537 | MQTTClient_willOptions lwt_options = MQTTClient_willOptions_initializer; 538 | char *state_topic = mqtt_fulltopic(MQTT_STATE_TOPIC); 539 | lwt_options.topicName = state_topic; 540 | lwt_options.message = MQTT_STATE_OFFLINE; 541 | lwt_options.retained = APP_CFG.mqtt.retain; 542 | lwt_options.qos = APP_CFG.mqtt.qos; 543 | connect_options.will = &lwt_options; 544 | // Connection to server 545 | if(result &= (MQTTClient_connect(MQTTclient, &connect_options) == MQTTCLIENT_SUCCESS)) { 546 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_connect()"); 547 | // Subscribe to topic 548 | char *command_topic = mqtt_fulltopic(MQTT_COMMAND_TOPIC); 549 | if(result &= (MQTTClient_subscribe(MQTTclient, command_topic, true) == MQTTCLIENT_SUCCESS)) { 550 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_subscribe(MQTT_COMMAND_TOPIC)"); 551 | // Periodic data sending 552 | if(result &= (pthread_create(&periodical_thread, NULL, mqtt_periodical, (void *) true) == 0)) { //-V566 553 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_create(periodical_thread)"); 554 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "pthread_create(periodical_thread)"); 555 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "MQTTClient_subscribe(MQTT_COMMAND_TOPIC)"); 556 | free(command_topic); 557 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "MQTTClient_connect()"); 558 | free(state_topic); 559 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "MQTTClient_setCallbacks()"); 560 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "MQTTClient_create()"); 561 | free(client_id); 562 | free(server_address); 563 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "asprintf(server_address)"); 564 | 565 | // Reconnect (only for first init) 566 | if((result == false) && (first_init == true)) { 567 | if(pthread_create(&reconnection_thread, NULL, mqtt_reconnection, NULL) == 0) { 568 | LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_create(reconnection_thread)"); 569 | } else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "pthread_create(reconnection_thread)"); 570 | } 571 | } else LOGGER(LOGGER_LEVEL_INFO, "MQTT is disabled in the settings or server address not set."); 572 | 573 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 574 | return result; 575 | } 576 | 577 | // Init mqtt (global) 578 | bool mqtt_init() { 579 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 580 | bool result = true; 581 | 582 | result &= mqtt_initialization(true); 583 | 584 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 585 | return result; 586 | } 587 | 588 | // Check connection 589 | bool mqtt_is_connected() { 590 | return !!MQTTClient_isConnected(MQTTclient); 591 | } 592 | 593 | // Check ready 594 | bool mqtt_is_ready() { 595 | return (mqtt_is_enabled() && mqtt_is_connected()); 596 | } 597 | 598 | // Free mqtt 599 | bool mqtt_free(bool force) { 600 | LOGGER(LOGGER_LEVEL_DEBUG, "Function is called..."); 601 | bool result = true; 602 | 603 | if(mqtt_is_enabled()) { // If MQTT enabled 604 | 605 | // Stop reconnection 606 | if(force) { 607 | if(reconnection_thread) { 608 | if(result &= (pthread_cancel(reconnection_thread) == 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_cancel(reconnection_thread)"); 609 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "pthread_cancel(reconnection_thread)"); 610 | } 611 | } 612 | 613 | // Stop periodic data sending 614 | if(periodical_thread) { 615 | if(result &= (pthread_cancel(periodical_thread) == 0)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "pthread_cancel(periodical_thread)"); 616 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "pthread_cancel(periodical_thread)"); 617 | } 618 | 619 | if(mqtt_is_connected()) { 620 | // Unsubscribe 621 | if(result &= (MQTTClient_unsubscribe(MQTTclient, mqtt_fulltopic(MQTT_COMMAND_TOPIC)) == MQTTCLIENT_SUCCESS)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_unsubscribe(MQTT_COMMAND_TOPIC)"); 622 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "MQTTClient_unsubscribe(MQTT_COMMAND_TOPIC)"); 623 | 624 | // Send offline state 625 | if(force) { 626 | char *state_topic = mqtt_fulltopic(MQTT_STATE_TOPIC); 627 | if(result &= mqtt_send(state_topic, MQTT_STATE_OFFLINE)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "mqtt_send(MQTT_STATE_TOPIC)"); 628 | else LOGGER(LOGGER_LEVEL_ERROR, "%s error!", "mqtt_send(MQTT_STATE_TOPIC)"); 629 | free(state_topic); 630 | } 631 | 632 | // Disconnect 633 | if(result &= (MQTTClient_disconnect(MQTTclient, MQTT_TIMEOUT * 1000) == MQTTCLIENT_SUCCESS)) LOGGER(LOGGER_LEVEL_DEBUG, "%s success.", "MQTTClient_disconnect()"); 634 | else LOGGER(LOGGER_LEVEL_WARNING, "%s error!", "MQTTClient_disconnect()"); 635 | } 636 | // Destroy 637 | MQTTClient_destroy(&MQTTclient); 638 | } 639 | 640 | LOGGER(LOGGER_LEVEL_DEBUG, "Function completed (result = %s).", (result ? "true" : "false")); 641 | return result; 642 | } 643 | --------------------------------------------------------------------------------