initial commit
This commit is contained in:
214
include/led.c
Normal file
214
include/led.c
Normal file
@@ -0,0 +1,214 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
Reference in New Issue
Block a user