├── autogen.sh ├── Makefile.am ├── .gitignore ├── configure.ac ├── README.md ├── LICENCE ├── ini.h ├── ini.c └── mqttdisplay.c /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | aclocal --install -I m4 && 4 | autoreconf --force --install && 5 | ./configure "$@" 6 | 7 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = mqttdisplay 2 | mqttdisplay_SOURCES = mqttdisplay.c ini.c 3 | mqttdisplay_CFLAGS = -Wall 4 | mqttdisplay_LDFLAGS = 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | autom4te.cache 2 | aclocal.m4 3 | compile 4 | configure 5 | depcomp 6 | install-sh 7 | missing 8 | config.log 9 | config.status 10 | Makefile 11 | Makefile.in 12 | config.h 13 | config.h.in 14 | mqttdisplay 15 | mqttdisplay-mqttdisplay.o 16 | stamp-h1 17 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([mqttdisplay],[0.0.1],[michael@no-surprises.co.uk],[mqttdisplay],[https://github.com/mgdm/MQTTDisplay]) 2 | AM_INIT_AUTOMAKE([1.9 foreign]) 3 | AC_CONFIG_HEADERS([config.h]) 4 | 5 | AC_PROG_CC 6 | AC_PROG_INSTALL 7 | AC_FUNC_STRERROR_R 8 | 9 | AC_DEFUN([REQUIRE_LIB], [ { 10 | AC_ARG_WITH([$1], AC_HELP_STRING([--with-$1=],[Location where $4 is installed]),[],[with_$1=default]) 11 | AS_IF( [test "x$with_$1" != xdefault], 12 | [ 13 | LDFLAGS="$LDFLAGS -L${with_$1}/lib" 14 | CFLAGS="$CFLAGS -I${with_$1}/include" 15 | ]) 16 | 17 | AC_CHECK_LIB($2,$3,[], 18 | [ 19 | AC_MSG_ERROR([$4 was not found, try specifying with --with-$1]) 20 | ]) 21 | 22 | } ] ) 23 | 24 | 25 | REQUIRE_LIB(libmosquitto,mosquitto,mosquitto_lib_init,[Mosquitto MQTT library]) 26 | REQUIRE_LIB(libsureelec,sureelec,libsureelec_create,[libsureelec display library]) 27 | 28 | AC_CONFIG_FILES([Makefile]) 29 | 30 | AC_OUTPUT 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTTDisplay 2 | 3 | This displays MQTT messages on a Sure Electronics LCD display. It requires my 4 | [libsureelec](https://github.com/mgdm/libsureelec) library to speak to the 5 | hardware, and [libmosquitto](http://mosquitto.org) to handle the MQTT 6 | connection. 7 | 8 | ## Usage 9 | 10 | `mqttdisplay -b BROKER -p PORT -d DISPLAY` 11 | 12 | BROKER is the hostname of the MQTT broker. Defaults to localhost. PORT is the 13 | MQTT port, and defaults to 1883. DISPLAY is the display device, and defaults to 14 | `/dev/tty.SLAB_USBtoUART`, which is correct for the CP2102 driver on Mac OS X. 15 | 16 | You can store the options in a config file, which is currently hard-coded to be 17 | `~/.mqttdisplay`. For example: 18 | 19 | ```ini 20 | [mqttdisplay] 21 | broker=mqtt.example.org 22 | port=1883 23 | topic=test/example 24 | ``` 25 | 26 | ## Credits 27 | This code uses the INIH library for parsing the config file. 28 | https://code.google.com/p/inih/ 29 | 30 | I also stole the trick to avoid the warning about daemon() being deprecated 31 | from mDNSResponder: 32 | 33 | http://www.opensource.apple.com/source/mDNSResponder/mDNSResponder-333.10/mDNSPosix/PosixDaemon.c 34 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Michael Maclean 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /ini.h: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 4 | home page for more info: 5 | 6 | http://code.google.com/p/inih/ 7 | 8 | */ 9 | 10 | #ifndef __INI_H__ 11 | #define __INI_H__ 12 | 13 | /* Make this header file easier to include in C++ code */ 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | #include 19 | 20 | /* Parse given INI-style file. May have [section]s, name=value pairs 21 | (whitespace stripped), and comments starting with ';' (semicolon). Section 22 | is "" if name=value pair parsed before any section heading. name:value 23 | pairs are also supported as a concession to Python's ConfigParser. 24 | 25 | For each name=value pair parsed, call handler function with given user 26 | pointer as well as section, name, and value (data only valid for duration 27 | of handler call). Handler should return nonzero on success, zero on error. 28 | 29 | Returns 0 on success, line number of first error on parse error (doesn't 30 | stop on first error), -1 on file open error, or -2 on memory allocation 31 | error (only when INI_USE_STACK is zero). 32 | */ 33 | int ini_parse(const char* filename, 34 | int (*handler)(void* user, const char* section, 35 | const char* name, const char* value), 36 | void* user); 37 | 38 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 39 | close the file when it's finished -- the caller must do that. */ 40 | int ini_parse_file(FILE* file, 41 | int (*handler)(void* user, const char* section, 42 | const char* name, const char* value), 43 | void* user); 44 | 45 | /* Nonzero to allow multi-line value parsing, in the style of Python's 46 | ConfigParser. If allowed, ini_parse() will call the handler with the same 47 | name for each subsequent line parsed. */ 48 | #ifndef INI_ALLOW_MULTILINE 49 | #define INI_ALLOW_MULTILINE 1 50 | #endif 51 | 52 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 53 | the file. See http://code.google.com/p/inih/issues/detail?id=21 */ 54 | #ifndef INI_ALLOW_BOM 55 | #define INI_ALLOW_BOM 1 56 | #endif 57 | 58 | /* Nonzero to use stack, zero to use heap (malloc/free). */ 59 | #ifndef INI_USE_STACK 60 | #define INI_USE_STACK 1 61 | #endif 62 | 63 | /* Maximum line length for any line in INI file. */ 64 | #ifndef INI_MAX_LINE 65 | #define INI_MAX_LINE 200 66 | #endif 67 | 68 | #ifdef __cplusplus 69 | } 70 | #endif 71 | 72 | #endif /* __INI_H__ */ 73 | -------------------------------------------------------------------------------- /ini.c: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 4 | home page for more info: 5 | 6 | http://code.google.com/p/inih/ 7 | 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "ini.h" 15 | 16 | #if !INI_USE_STACK 17 | #include 18 | #endif 19 | 20 | #define MAX_SECTION 50 21 | #define MAX_NAME 50 22 | 23 | /* Strip whitespace chars off end of given string, in place. Return s. */ 24 | static char* rstrip(char* s) 25 | { 26 | char* p = s + strlen(s); 27 | while (p > s && isspace((unsigned char)(*--p))) 28 | *p = '\0'; 29 | return s; 30 | } 31 | 32 | /* Return pointer to first non-whitespace char in given string. */ 33 | static char* lskip(const char* s) 34 | { 35 | while (*s && isspace((unsigned char)(*s))) 36 | s++; 37 | return (char*)s; 38 | } 39 | 40 | /* Return pointer to first char c or ';' comment in given string, or pointer to 41 | null at end of string if neither found. ';' must be prefixed by a whitespace 42 | character to register as a comment. */ 43 | static char* find_char_or_comment(const char* s, char c) 44 | { 45 | int was_whitespace = 0; 46 | while (*s && *s != c && !(was_whitespace && *s == ';')) { 47 | was_whitespace = isspace((unsigned char)(*s)); 48 | s++; 49 | } 50 | return (char*)s; 51 | } 52 | 53 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ 54 | static char* strncpy0(char* dest, const char* src, size_t size) 55 | { 56 | strncpy(dest, src, size); 57 | dest[size - 1] = '\0'; 58 | return dest; 59 | } 60 | 61 | /* See documentation in header file. */ 62 | int ini_parse_file(FILE* file, 63 | int (*handler)(void*, const char*, const char*, 64 | const char*), 65 | void* user) 66 | { 67 | /* Uses a fair bit of stack (use heap instead if you need to) */ 68 | #if INI_USE_STACK 69 | char line[INI_MAX_LINE]; 70 | #else 71 | char* line; 72 | #endif 73 | char section[MAX_SECTION] = ""; 74 | char prev_name[MAX_NAME] = ""; 75 | 76 | char* start; 77 | char* end; 78 | char* name; 79 | char* value; 80 | int lineno = 0; 81 | int error = 0; 82 | 83 | #if !INI_USE_STACK 84 | line = (char*)malloc(INI_MAX_LINE); 85 | if (!line) { 86 | return -2; 87 | } 88 | #endif 89 | 90 | /* Scan through file line by line */ 91 | while (fgets(line, INI_MAX_LINE, file) != NULL) { 92 | lineno++; 93 | 94 | start = line; 95 | #if INI_ALLOW_BOM 96 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 97 | (unsigned char)start[1] == 0xBB && 98 | (unsigned char)start[2] == 0xBF) { 99 | start += 3; 100 | } 101 | #endif 102 | start = lskip(rstrip(start)); 103 | 104 | if (*start == ';' || *start == '#') { 105 | /* Per Python ConfigParser, allow '#' comments at start of line */ 106 | } 107 | #if INI_ALLOW_MULTILINE 108 | else if (*prev_name && *start && start > line) { 109 | /* Non-black line with leading whitespace, treat as continuation 110 | of previous name's value (as per Python ConfigParser). */ 111 | if (!handler(user, section, prev_name, start) && !error) 112 | error = lineno; 113 | } 114 | #endif 115 | else if (*start == '[') { 116 | /* A "[section]" line */ 117 | end = find_char_or_comment(start + 1, ']'); 118 | if (*end == ']') { 119 | *end = '\0'; 120 | strncpy0(section, start + 1, sizeof(section)); 121 | *prev_name = '\0'; 122 | } 123 | else if (!error) { 124 | /* No ']' found on section line */ 125 | error = lineno; 126 | } 127 | } 128 | else if (*start && *start != ';') { 129 | /* Not a comment, must be a name[=:]value pair */ 130 | end = find_char_or_comment(start, '='); 131 | if (*end != '=') { 132 | end = find_char_or_comment(start, ':'); 133 | } 134 | if (*end == '=' || *end == ':') { 135 | *end = '\0'; 136 | name = rstrip(start); 137 | value = lskip(end + 1); 138 | end = find_char_or_comment(value, '\0'); 139 | if (*end == ';') 140 | *end = '\0'; 141 | rstrip(value); 142 | 143 | /* Valid name[=:]value pair found, call handler */ 144 | strncpy0(prev_name, name, sizeof(prev_name)); 145 | if (!handler(user, section, name, value) && !error) 146 | error = lineno; 147 | } 148 | else if (!error) { 149 | /* No '=' or ':' found on name[=:]value line */ 150 | error = lineno; 151 | } 152 | } 153 | } 154 | 155 | #if !INI_USE_STACK 156 | free(line); 157 | #endif 158 | 159 | return error; 160 | } 161 | 162 | /* See documentation in header file. */ 163 | int ini_parse(const char* filename, 164 | int (*handler)(void*, const char*, const char*, const char*), 165 | void* user) 166 | { 167 | FILE* file; 168 | int error; 169 | 170 | file = fopen(filename, "r"); 171 | if (!file) 172 | return -1; 173 | error = ini_parse_file(file, handler, user); 174 | fclose(file); 175 | return error; 176 | } 177 | -------------------------------------------------------------------------------- /mqttdisplay.c: -------------------------------------------------------------------------------- 1 | #if __APPLE__ 2 | // In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” 3 | // error, which prevents compilation because we build with "-Werror". 4 | // Since this is supposed to be portable cross-platform code, we don't care that daemon is 5 | // deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. 6 | #define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #if __APPLE__ 21 | #undef daemon 22 | extern int daemon(int, int); 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | #include "ini.h" 29 | #include "config.h" 30 | 31 | #ifdef __GLIBC__ 32 | #define POSSIBLY_UNUSED __attribute__((unused)) 33 | #else 34 | #define POSSIBLY_UNUSED 35 | #endif 36 | 37 | #define INI_NAME ".mqttdisplay" 38 | 39 | int STOPPING = 0; 40 | int VERBOSE = 0; 41 | int BRIGHTNESS = 128; 42 | 43 | struct md_config { 44 | char *topic; 45 | char *display; 46 | char *broker; 47 | long broker_port; 48 | int foreground; 49 | }; 50 | 51 | static struct md_config DEFAULTS = { 52 | "mqttdisplay/#", 53 | "/dev/tty.SLAB_USBtoUART", 54 | "localhost", 55 | 1883, 56 | 0 57 | }; 58 | 59 | static struct option OPTIONS[] = { 60 | { "host", required_argument, 0, 'h' }, 61 | { "display", required_argument, 0, 'd' }, 62 | { "port", required_argument, 0, 'p' }, 63 | { "topic", required_argument, 0, 't' }, 64 | { "verbose", no_argument, 0, 'v' }, 65 | { "foreground", no_argument, 0, 'f' }, 66 | { 0, 0, 0, 0 } 67 | }; 68 | 69 | static void debug_print(const char *format, ...) { 70 | if (VERBOSE) { 71 | va_list ap; 72 | va_start(ap, format); /* measure the required size (the number of elements of format) */ 73 | 74 | fprintf(stderr, "mqttdisplay: "); 75 | vfprintf(stderr, format, ap); 76 | fprintf(stderr, "\n"); 77 | 78 | va_end(ap); 79 | } 80 | } 81 | 82 | static char *strerror_wrapper(int err) { 83 | char *buf = calloc(256, sizeof(char)); 84 | POSSIBLY_UNUSED char *bbuf = buf; 85 | #ifdef STRERROR_R_CHAR_P 86 | bbuf = 87 | #endif 88 | strerror_r(err, buf, 256); 89 | return bbuf; 90 | } 91 | 92 | 93 | void handle_errno(int retval, int err) { 94 | const char *message = NULL; 95 | 96 | switch (retval) { 97 | case MOSQ_ERR_SUCCESS: 98 | return; 99 | 100 | case MOSQ_ERR_ERRNO: 101 | message = strerror_wrapper(err); 102 | break; 103 | 104 | default: 105 | message = mosquitto_strerror(err); 106 | break; 107 | } 108 | 109 | if (message) { 110 | fprintf(stderr, "%s\n", message); 111 | exit(EXIT_FAILURE); 112 | } 113 | } 114 | 115 | static char *wrap_text(const char *buffer, size_t width, size_t min_lines) { 116 | size_t count, buflen; 117 | int lines = 0; 118 | const char *ptr, *endptr; 119 | char result[32 * sizeof(char) * width]; 120 | char *result_ptr = result, *chopped_result = NULL; 121 | 122 | count = 0; 123 | buflen = strlen(buffer); 124 | memset(result, ' ', 32 * sizeof(char) * width); 125 | 126 | do { 127 | ptr = buffer + count; 128 | 129 | /* don't set endptr beyond the end of the buffer */ 130 | if (ptr - buffer + width <= buflen) 131 | endptr = ptr + width; 132 | else 133 | endptr = buffer + buflen; 134 | 135 | /* back up EOL to a null terminator or space */ 136 | while (*(endptr) && !isspace(*(endptr)) ) 137 | endptr--; 138 | 139 | if (isspace(*(endptr))) { 140 | endptr++; 141 | } 142 | 143 | strncpy(result_ptr, ptr, (endptr - ptr)); 144 | count += endptr - ptr; 145 | lines++; 146 | 147 | result_ptr = result_ptr + width; 148 | } while (*endptr); 149 | 150 | if (lines < min_lines) { 151 | lines = min_lines; 152 | } 153 | 154 | chopped_result = calloc(lines * width, sizeof(char)); 155 | memcpy(chopped_result, result, lines * width); 156 | return chopped_result; 157 | } 158 | 159 | static void message_callback(struct mosquitto *client, void *obj, const struct mosquitto_message *message) { 160 | char *wrapped_text; 161 | 162 | libsureelec_ctx *display = (libsureelec_ctx *) obj; 163 | debug_print("Got message: %s", message->payload); 164 | 165 | if (message->payloadlen > 0) { 166 | wrapped_text = wrap_text(message->payload, display->device_info.width, display->device_info.height); 167 | 168 | memcpy(display->framebuffer, wrapped_text, display->device_info.width * display->device_info.height); 169 | BRIGHTNESS = 254; 170 | alarm(10); 171 | libsureelec_refresh(display); 172 | } else { 173 | BRIGHTNESS = 0; 174 | libsureelec_clear_display(display); 175 | } 176 | } 177 | 178 | static void interrupt_handler(int signal) { 179 | switch (signal) { 180 | case SIGINT: 181 | debug_print("Caught signal - stopping\n"); 182 | STOPPING = 1; 183 | break; 184 | case SIGALRM: 185 | debug_print("Caught alarm signal - dimming display\n"); 186 | 187 | if (BRIGHTNESS > 0) { 188 | BRIGHTNESS = 128; 189 | } 190 | 191 | break; 192 | case SIGUSR1: 193 | debug_print("Caught user signal - turning display off\n"); 194 | BRIGHTNESS = 0; 195 | break; 196 | default: 197 | debug_print("Caught unknown signal\n"); 198 | break; 199 | } 200 | } 201 | 202 | static struct mosquitto *init_mosquitto(const char *broker, long port, const char *topic, libsureelec_ctx *display) { 203 | struct mosquitto *client = NULL; 204 | int retval = 0; 205 | 206 | mosquitto_lib_init(); 207 | client = mosquitto_new("mqttdisplay", 1, display); 208 | 209 | retval = mosquitto_connect(client, broker, port, 10); 210 | handle_errno(retval, errno); 211 | 212 | mosquitto_message_callback_set(client, message_callback); 213 | 214 | mosquitto_subscribe(client, NULL, topic, 0); 215 | handle_errno(retval, errno); 216 | 217 | return client; 218 | } 219 | 220 | static libsureelec_ctx *init_display(const char *device, int verbose) { 221 | libsureelec_ctx *display = libsureelec_create(device, verbose); 222 | 223 | if (!display) { 224 | fprintf(stderr, "Failed to initialize display"); 225 | exit(EXIT_FAILURE); 226 | } 227 | 228 | libsureelec_clear_display(display); 229 | libsureelec_set_contrast(display, 1); 230 | libsureelec_set_brightness(display, 128); 231 | 232 | return display; 233 | } 234 | 235 | static int ini_handler(void *userdata, const char *section, const char *name, const char *value) { 236 | struct md_config *config = (struct md_config *) userdata; 237 | 238 | #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 239 | if (MATCH("mqttdisplay", "broker")) { 240 | config->broker = strdup(value); 241 | } else if (MATCH("mqttdisplay", "port")) { 242 | config->broker_port = strtol(value, NULL, 10); 243 | } else if (MATCH("mqttdisplay", "display")) { 244 | config->display = strdup(value); 245 | } else if (MATCH("mqttdisplay", "topic")) { 246 | config->topic = strdup(value); 247 | } else if (MATCH("mqttdisplay", "verbose")) { 248 | VERBOSE = strtol(value, NULL, 10) ? 1 : 0; 249 | } else if (MATCH("mqttdisplay", "foreground")) { 250 | config->foreground = strtol(value, NULL, 10) ? 1 : 0; 251 | } 252 | 253 | return 1; 254 | } 255 | 256 | static int read_config(const char *path, struct md_config *config) { 257 | const char *message; 258 | 259 | debug_print("Loading INI file %s\n", path); 260 | if (access(path, R_OK) < 0) { 261 | message = strerror_wrapper(errno); 262 | debug_print("Could not access INI file %s: %s\n", path, message); 263 | return -1; 264 | } 265 | 266 | if (ini_parse(path, ini_handler, config) < 0) { 267 | debug_print("Could not parse INI file %s\n", path); 268 | return 0; 269 | } 270 | 271 | return 1; 272 | } 273 | 274 | static int read_arguments(int argc, char **argv, struct md_config *config) { 275 | int opt = 0, index = 0; 276 | 277 | while (opt != -1) { 278 | opt = getopt_long(argc, argv, "h:d:p:t:vf", OPTIONS, &index); 279 | 280 | switch (opt) { 281 | case 'f': 282 | config->foreground = 1; 283 | break; 284 | 285 | case 'h': 286 | config->broker = strdup(optarg); 287 | break; 288 | 289 | case 'd': 290 | config->display = strdup(optarg); 291 | break; 292 | 293 | case 't': 294 | config->topic = strdup(optarg); 295 | break; 296 | 297 | case 'p': 298 | config->broker_port = strtol(optarg, NULL, 10); 299 | break; 300 | 301 | case 'v': 302 | VERBOSE = 1; 303 | break; 304 | } 305 | } 306 | 307 | return 1; 308 | } 309 | 310 | static char *get_home_directory(void) { 311 | const char *dir; 312 | struct passwd *pwd; 313 | 314 | dir = getenv("HOME"); 315 | if (!dir) { 316 | pwd = getpwuid(getuid()); 317 | 318 | if (pwd) { 319 | dir = pwd->pw_dir; 320 | } 321 | } 322 | 323 | return strdup(dir); 324 | } 325 | 326 | int main(int argc, char **argv) { 327 | struct md_config config = DEFAULTS; 328 | struct mosquitto *client = NULL; 329 | libsureelec_ctx *display = NULL; 330 | char ini_path[256]; 331 | char *home_dir = NULL; 332 | int i, brightness; 333 | 334 | /* We read the config before we read the CLI arguments, but do a quick 335 | * check to see if '-v' is an argument to enable verbose mode ahead of 336 | * time */ 337 | 338 | for (i = 0; i < argc; i++) { 339 | if (strcmp("-v", argv[i]) == 0) { 340 | VERBOSE = 1; 341 | } 342 | } 343 | 344 | home_dir = get_home_directory(); 345 | snprintf(ini_path, sizeof(ini_path), "%s/%s", home_dir, INI_NAME); 346 | 347 | read_config(ini_path, &config); 348 | read_arguments(argc, argv, &config); 349 | 350 | if (signal(SIGINT, interrupt_handler) == SIG_ERR) { 351 | fprintf(stderr, "Failed to install signal handler\n"); 352 | exit(EXIT_FAILURE); 353 | } 354 | 355 | if (signal(SIGALRM, interrupt_handler) == SIG_ERR) { 356 | fprintf(stderr, "Failed to install signal handler\n"); 357 | exit(EXIT_FAILURE); 358 | } 359 | 360 | if (signal(SIGUSR1, interrupt_handler) == SIG_ERR) { 361 | fprintf(stderr, "Failed to install signal handler\n"); 362 | exit(EXIT_FAILURE); 363 | } 364 | 365 | debug_print("Starting with broker %s:%ld, topic \"%s\", and display %s", 366 | config.broker, config.broker_port, config.topic, config.display); 367 | 368 | if (config.foreground == 0) { 369 | if (daemon(0, 0) == -1) { 370 | fprintf(stderr, "Failed to daemonize\n"); 371 | exit(EXIT_FAILURE); 372 | } 373 | } 374 | 375 | display = init_display(config.display, VERBOSE); 376 | 377 | client = init_mosquitto(config.broker, config.broker_port, config.topic, (void *) display); 378 | 379 | brightness = BRIGHTNESS; 380 | while (!STOPPING) { 381 | if (brightness != BRIGHTNESS) { 382 | brightness = BRIGHTNESS; 383 | libsureelec_set_brightness(display, BRIGHTNESS); 384 | } 385 | 386 | mosquitto_loop(client, 100, 1); 387 | } 388 | 389 | return 0; 390 | } 391 | 392 | --------------------------------------------------------------------------------