#include #include #include #include #include #include #include #include #include #include "led.h" #include "log.h" #define Z() if (data->rate > 0) { \ usleep(1000000 / data->rate); \ } struct Data { char *file_name; FILE *pfile; double rate; }; /* global data accessed by all threads */ static struct Data *data; pthread_t thread; /* mutex to lock the thread */ static pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; /** * @brief helper function to set the led rate * * @param value set the contents of the led file * @param d data */ static inline void led_set(char value, struct Data *d) { d->pfile = fopen(d->file_name, "w"); if (d->pfile == NULL) { log_warn("failed to open %s fopen: %s", d->file_name, strerror(errno)); return; } if (fputc(value, d->pfile) == EOF) { log_warn("failed to write to %s fopen: %s", d->file_name, strerror(errno)); /* According to the man page, after erroring doing anything including * fclose may result in undefined behavior. */ return; } fclose(d->pfile); } /** * @brief main thread loop * * @param args args */ static void *run(void *args) { int err; struct Data *d = (struct Data *)args; for (;;) { /* turn off the led before doing anything else */ led_set('0', d); /* wait for the condition to change */ while(d->rate == 0) { if ((err = pthread_cond_wait(&cv, &mp)) != 0) { log_fatal("pthread_cond_wait: %s", strerror(err)); pthread_exit(0); } } /* exit if we're told to */ if (d->rate <= -1) { log_debug("received rate of -1, exiting"); pthread_exit(0); } Z(); led_set('1', d); Z(); } pthread_exit(0); } /** * @brief run before exiting the program * this is for cleaning up all the messes we've made * * @param signal signal */ static void pre_exit(int signal) { led_set_rate(-1); pthread_cond_destroy(&cv); pthread_mutex_destroy(&mp); if (data->pfile) { data->pfile = fopen(data->file_name, "w"); } fputc('0', data->pfile); /* make sure to cleanup the allocated data */ fclose(data->pfile); free(data->file_name); free(data); exit(0); } /** * @brief create the led thread * * @param file file to send blinking signals to * @return 0 on success */ int led_create_thread(char *file) { int err; struct sigaction act; static bool running; /* make sure the led thread cannot be created twice */ if (running) { log_warn("led thread has already been created"); return 1; } /* zero the array, and setup sig handler and exit handler */ memset(&act, 0, sizeof(act)); act.sa_handler = &pre_exit; if (sigaction(SIGINT, &act, NULL) < 0) { log_fatal("failed to create sigaction: %s, memory is gonna leak", strerror(errno)); } /* setup data */ data = calloc(1, sizeof(struct Data)); data->file_name = malloc(sizeof(char) * (strlen(file) + 1)); if (data->file_name == NULL) { log_fatal("failed to create led thread: malloc of led file name failed: %s", strerror(errno)); exit(1); } strcpy(data->file_name, file); data->rate = 0; /* attempt to create the thread */ if ((err = pthread_create(&thread, NULL, run, data)) != 0) { log_fatal("failed to create led thread: pthread_create: %s", strerror(err)); return -1; } /* detach the thread to make cleanup nice and easy */ if ((err = pthread_detach(thread)) != 0) { log_fatal("failed to create led thread: pthread_detach: %s", strerror(err)); return -1; } log_info("started LED thread"); running = true; return 0; } /** * @brief set the led rate * * @param r number of blinks per second */ void led_set_rate(double r) { int err; static bool locked = false; /* there's no reason to set the rate if it's already the same */ if (r == data->rate) { return; } /* lock the thread, update, and unlock it */ if (!locked) { if ((err = pthread_mutex_lock(&mp)) != 0) { log_fatal("failed to lock the mutex: %s", strerror(errno)); } } /* set the rate */ data->rate = r; log_debug("set rate to: %f", r); pthread_cond_signal(&cv); if (!locked) { if ((err = pthread_mutex_unlock(&mp)) != 0) { log_fatal("failed to lock the mutex: %s", strerror(errno)); } } /* make sure we don't lock the thread in an infinite loop */ locked = (r != 0); }