From: Sam Moore Date: Wed, 9 Oct 2013 09:34:39 +0000 (+0800) Subject: MAJOR refactoring of Sensors code X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=289794ba2dcbe6234e25e5d00531b26baee342b7;p=matches%2FMCTX3420.git MAJOR refactoring of Sensors code Will probably get in trouble for this... Abstracted out the threaded sensor control logic from the actual initialisation/reading/cleanup of sensors. Adding a new sensor to the program still isn't just a one line process, but it's a lot more general than before. Added test sensors for the strain gauges, total CPU usage (because that seemed interesting) Went a bit insane with power and added a "Piped" sensor type which lets people run external programs for the sensors :S (As long as it prints doubles to stdout and is unbuffered it will work). --- diff --git a/server/Makefile b/server/Makefile index f3992c8..34d604e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,13 +2,16 @@ CXX = gcc FLAGS = -std=c99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib LIB = -lfcgi -lssl -lcrypto -lpthread -lm -lopencv_highgui -lopencv_core -lopencv_ml -lopencv_imgproc -lldap -lcrypt -OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o image.o bbb_pin.o pin_test.o login.o +OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o image.o bbb_pin.o pin_test.o login.o sensors/sensors.a RM = rm -f BIN = server #BIN2 = stream -all : $(BIN) $(BIN2) +all : + $(MAKE) -C sensors + $(MAKE) $(BIN) + $(BIN) : $(OBJ) @@ -19,12 +22,13 @@ $(BIN) : $(OBJ) $(CXX) $(FLAGS) -c $< - clean : + make -C sensors clean $(RM) $(BIN) $(BIN2) $(RM) *.o clean_full: #cleans up all backup files + make -C sensors clean_full $(RM) $(BIN) $(BIN2) $(OBJ) $(LINKOBJ) $(RM) *.*~ $(RM) *~ diff --git a/server/fastcgi.c b/server/fastcgi.c index 3218a58..4b69f89 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -52,11 +52,11 @@ static void IdentifyHandler(FCGIContext *context, char *params) { if (ident_sensors) { FCGI_JSONKey("sensors"); FCGI_JSONValue("{\n\t\t"); - for (i = 0; i < NUMSENSORS; i++) { + for (i = 0; i < g_num_sensors; i++) { if (i > 0) { FCGI_JSONValue(",\n\t\t"); } - FCGI_JSONValue("\"%d\" : \"%s\"", i, g_sensor_names[i]); + FCGI_JSONValue("\"%d\" : \"%s\"", i, Sensor_GetName(i)); } FCGI_JSONValue("\n\t}"); } diff --git a/server/main.c b/server/main.c index ecdf068..92f76b3 100644 --- a/server/main.c +++ b/server/main.c @@ -16,6 +16,7 @@ #include // for system logging #include // for signal handling + // --- Variable definitions --- // Options g_options; // options passed to program through command line arguments @@ -96,30 +97,15 @@ void ParseArguments(int argc, char ** argv) } -/** - * Handle a signal - * @param signal - The signal number - */ -//TODO: Something that gets massively annoying with threads is that you can't predict which one gets the signal -// There are ways to deal with this, but I can't remember them -// Probably sufficient to just call Thread_QuitProgram here -void SignalHandler(int signal) -{ - // At the moment just always exit. - // Call `exit` so that Cleanup will be called to... clean up. - Log(LOGWARN, "Got signal %d (%s). Exiting.", signal, strsignal(signal)); - - //exit(signal); -} - /** * Cleanup before the program exits */ void Cleanup() { Log(LOGDEBUG, "Begin cleanup."); + Sensor_Cleanup(); + //Actuator_Cleanup(); Log(LOGDEBUG, "Finish cleanup."); - } /** @@ -135,24 +121,13 @@ int main(int argc, char ** argv) openlog("mctxserv", LOG_PID | LOG_PERROR, LOG_USER); Log(LOGINFO, "Server started"); - ParseArguments(argc, argv); - - //Open the system log + ParseArguments(argc, argv); // Setup the g_options structure from program arguments - // signal handler - //TODO: Make this work - /* - int signals[] = {SIGINT, SIGSEGV, SIGTERM}; - for (int i = 0; i < sizeof(signals)/sizeof(int); ++i) - { - signal(signals[i], SignalHandler); - } - */ Sensor_Init(); Actuator_Init(); Pin_Init(); - //Sensor_StartAll("test"); - //Actuator_StartAll("test"); + + // Try and start things const char *ret; if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL) Fatal("Control_SetMode failed with '%s'", ret); diff --git a/server/parameters b/server/parameters index 6ac1bc1..2fa6890 100644 --- a/server/parameters +++ b/server/parameters @@ -25,8 +25,8 @@ pin_test="0" auth_uri="/etc/shadow" # Set to the dn of the LDAP server -#ldap_base_dn="ou=People,dc=daedalus" # Testing -ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA +ldap_base_dn="ou=People,dc=daedalus" # Testing +#ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA ## OPTIONS TO BE PASSED TO SERVER; DO NOT EDIT diff --git a/server/run.sh b/server/run.sh index 0e52fd9..1563d2e 100755 --- a/server/run.sh +++ b/server/run.sh @@ -83,6 +83,9 @@ echo "Parameters are: $parameters" # Run the program with parameters # TODO: Can tell spawn-fcgi to run the program as an unprivelaged user? # But first will have to work out how to set PWM/GPIO as unprivelaged user +# NOTE: Having the program automatically restart itself after a *FATAL ERROR* doesn't seem like such a good idea now +# (Some things that call Fatal happen under circumstances where they will just keep calling Fatal every time the program starts) +# Change the number of fails to 1 for now. We can potentially use different error codes for different types of errors, but that seems overkill. fails=0 while [ $fails -lt 1 ]; do spawn-fcgi -p9005 -n -- ./server $parameters diff --git a/server/sensor.c b/server/sensor.c index cfab514..8406870 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -11,67 +11,86 @@ #include /** Array of sensors, initialised by Sensor_Init **/ -static Sensor g_sensors[NUMSENSORS]; //global to this file - -/** Array of sensor threshold structures defining the safety values of each sensor**/ -const SensorThreshold thresholds[NUMSENSORS]= { - //Max Safety, Min safety, Max warning, Min warning - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {1, 1, 1, 1} -}; - -/** Human readable names for the sensors **/ -const char * g_sensor_names[NUMSENSORS] = { - "strain0", - "strain1", - "strain2", - "strain3", - "pressure0", - "pressure1", - "pressure_feedback", - "microphone", - "enclosure" -}; +static Sensor g_sensors[SENSORS_MAX]; +/** The number of sensors **/ +int g_num_sensors = 0; + + + +/** + * Add and initialise a Sensor + * @param name - Human readable name of the sensor + * @param read - Function to call whenever the sensor should be read + * @param init - Function to call to initialise the sensor (may be NULL) + * @param max_error - Maximum error threshold; program will exit if this is exceeded for the sensor reading + * @param min_error - Minimum error threshold; program will exit if the sensor reading falls below this value + * @param max_warn - Maximum warning threshold; program will log warnings if the value exceeds this threshold + * @param min_warn - Minimum warning threshold; program will log warnings if the value falls below this threshold + * @returns Number of the sensor added + */ +int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn cleanup, double max_error, double min_error, double max_warn, double min_warn) +{ + if (++g_num_sensors > SENSORS_MAX) + { + Fatal("Too many sensors; Increase SENSORS_MAX from %d in sensor.h and recompile", SENSORS_MAX); + // We could design the program to use realloc(3) + // But since someone who adds a new sensor has to recompile the program anyway... + } + Sensor * s = &(g_sensors[g_num_sensors-1]); + + s->id = g_num_sensors-1; + s->user_id = user_id; + Data_Init(&(s->data_file)); + s->name = name; + s->read = read; // Set read function + s->init = init; // Set init function + if (init != NULL) + init(name, user_id); // Call it + + // Set warning/error thresholds + s->thresholds.max_error = max_error; + s->thresholds.min_error = min_error; + s->thresholds.max_warn = max_warn; + s->thresholds.min_warn = min_warn; + + return g_num_sensors; +} /** - * One off initialisation of *all* sensors + * Initialise all sensors used by the program + * TODO: Edit this to add any extra sensors you need + * TODO: Edit the includes as well */ +#include "sensors/resource.h" +#include "sensors/strain.h" +#include "sensors/piped.h" void Sensor_Init() { - for (int i = 0; i < NUMSENSORS; ++i) + Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, 1e50,-1e50,1e50,-1e50); + Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, 1e50,-1e50,1e50,-1e50); + //Sensor_Add("../testing/count.py", 0, Piped_Read, Piped_Init, Piped_Cleanup, 1e50,-1e50,1e50,-1e50); + //Sensor_Add("strain0", STRAIN0, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("strain1", STRAIN1, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("strain2", STRAIN2, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("strain3", STRAIN3, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("pressure0", PRESSURE0, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("pressure1", PRESSURE1, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("pressure_feedback", PRESSURE_FEEDBACK, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("enclosure", ENCLOSURE, Enclosure_Read, Enclosure_Init, 1,1,1,1); + //Sensor_Add("dilatometer", DILATOMETER, Dilatometer_Read, Dilatometer_Init, -1,-1,-1,-1); +} + +/** + * Cleanup all sensors + */ +void Sensor_Cleanup() +{ + for (int i = 0; i < g_num_sensors; ++i) { - g_sensors[i].id = i; - Data_Init(&(g_sensors[i].data_file)); + Sensor * s = g_sensors+i; + if (s->cleanup != NULL) + s->cleanup(s->user_id); } - - - - // Get the required ADCs - ADC_Export(ADC0); // Strain gauges x 4 - ADC_Export(ADC1); // Pressure sensor 1 - ADC_Export(ADC2); // Pressure sensor 2 - // ADC3 still unused (!?) - ADC_Export(ADC4); // Pressure regulator feedback(?) signal - ADC_Export(ADC5); // Microphone - - // Get GPIO pins //TODO: Confirm pins used with Electronics Team - GPIO_Export(GPIO0_30); // Mux A (strain 1) - GPIO_Set(GPIO0_30, false); - GPIO_Export(GPIO1_28); // Mux B (strain 2) - GPIO_Set(GPIO1_28, false); - GPIO_Export(GPIO0_31); // Mux C (strain 3) - GPIO_Set(GPIO0_31, false); - GPIO_Export(GPIO1_16); // Mux D (strain 4) - GPIO_Set(GPIO1_16, false); - - GPIO_Export(GPIO0_31); // Enclosure switch } /** @@ -93,7 +112,7 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) char filename[BUFSIZ]; const char *experiment_name = (const char*) arg; - if (snprintf(filename, BUFSIZ, "%s_s%d", experiment_name, s->id) >= BUFSIZ) + if (snprintf(filename, BUFSIZ, "%s_%d", experiment_name, s->id) >= BUFSIZ) { Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); } @@ -133,8 +152,6 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) } Data_Close(&(s->data_file)); // Close DataFile - s->newest_data.time_stamp = 0; - s->newest_data.value = 0; Log(LOGDEBUG, "Stopped sensor %d", s->id); break; default: @@ -150,7 +167,7 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) */ void Sensor_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMSENSORS; i++) + for (int i = 0; i < g_num_sensors; i++) Sensor_SetMode(&g_sensors[i], mode, arg); } @@ -160,142 +177,21 @@ void Sensor_SetModeAll(ControlModes mode, void * arg) * @param sensor_id - The ID of the sensor * @param value - The value from the sensor to test */ -void Sensor_CheckData(SensorId id, double value) +void Sensor_CheckData(Sensor * s, double value) { - if( value > thresholds[id].max_error || value < thresholds[id].min_error) + if( value > s->thresholds.max_error || value < s->thresholds.min_error) { - Log(LOGERR, "Sensor %s at %f is above or below its safety value of %f or %f\n", g_sensor_names[id],value, thresholds[id].max_error, thresholds[id].min_error); + Log(LOGERR, "Sensor %s at %f is above or below its safety value of %f or %f\n",s->name,value, s->thresholds.max_error, s->thresholds.min_error); //new function that stops actuators? //Control_SetMode(CONTROL_EMERGENCY, NULL) } - else if( value > thresholds[id].max_warn || value < thresholds[id].min_warn) + else if( value > s->thresholds.max_warn || value < s->thresholds.min_warn) { - Log(LOGWARN, "Sensor %s at %f is above or below its warning value of %f or %f\n", g_sensor_names[id],value,thresholds[id].max_warn, thresholds[id].min_warn); + Log(LOGWARN, "Sensor %s at %f is above or below its warning value of %f or %f\n", s->name,value,s->thresholds.max_warn, s->thresholds.min_warn); } } -/** - * Read a DataPoint from a Sensor; block until value is read - * @param id - The ID of the sensor - * @param d - DataPoint to set - * @returns True if the DataPoint was different from the most recently recorded. - */ -bool Sensor_Read(Sensor * s, DataPoint * d) -{ - - - - static bool result = true; - - //TODO: Remove this, code should be refactored to not use so many threads - // Although... if it works, it works... - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - - pthread_mutex_lock(&mutex); //TODO: Reduce the critical section - - usleep(10); - - // Set time stamp - struct timeval t; - gettimeofday(&t, NULL); - d->time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime()); - - // Read value based on Sensor Id - int value; bool success = true; - //TODO: Can probably do this nicer than a switch (define a function pointer for each sensor) - // Can probably make the whole sensor thing a lot nicer with a linked list of sensors... - // (Then to add more sensors to the software, someone just writes an appropriate read function and calls Sensor_Add(...) at init) - // (I will do this. Don't do it before I get a chance, I don't trust you :P) - switch (s->id) - { - //TODO: Strain gauges should have their own critical section, rest of sensors probably don't need to be in a critical section - case STRAIN0: - success &= GPIO_Set(GPIO0_30, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO0_30, false); - if (!success) - Fatal("Error reading strain gauge 0"); - break; - case STRAIN1: - success &= GPIO_Set(GPIO1_28, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO1_28, false); - if (!success) - Fatal("Error reading strain gauge 1"); - break; - case STRAIN2: - success &= GPIO_Set(GPIO0_31, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO0_31, false); - case STRAIN3: - success &= GPIO_Set(GPIO1_16, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO1_16, false); - if (!success) - Fatal("Error reading strain gauge 2"); - break; - case PRESSURE0: - success &= ADC_Read(ADC1, &value); - break; - case PRESSURE1: - success &= ADC_Read(ADC5, &value); - break; - case PRESSURE_FEEDBACK: - success &= ADC_Read(ADC4, &value); - break; - case MICROPHONE: - success &= ADC_Read(ADC2, &value); - break; - case ENCLOSURE: - { - bool why_do_i_need_to_do_this = false; - success &= GPIO_Read(GPIO0_31, &why_do_i_need_to_do_this); - value = (int)why_do_i_need_to_do_this; - break; - } - case DILATOMETER: - { - // Will definitely cause issues included in the same critical section as ADC reads - // (since it will be the longest sensor to sample, everything else will have to keep waiting on it) - value = 0; - break; - } - - } - - d->value = (double)(value); //TODO: Calibration? Or do calibration in GUI - - pthread_mutex_unlock(&mutex); //TODO: Reduce the critical section - - - // Perform sanity check based on Sensor's ID and the DataPoint - Sensor_CheckData(s->id, d->value); - - // Update latest DataPoint if necessary - - if (result) - { - s->newest_data.time_stamp = d->time_stamp; - s->newest_data.value = d->value; - } - -#ifdef _BBB - //Not all cases have usleep, easiest here. - //TODO: May want to add a control option to adjust the sampling rate for each sensor? - // Also, we can get a more accurate sampling rate if instead of a fixed sleep, we calculate how long to sleep each time. - usleep(100000); -#endif - - /* - if (success) - Log(LOGDEBUG, "Successfully read sensor %d (for once)", s->id); - else - Log(LOGDEBUG, "Failed to read sensor %d (again)", s->id); - */ - return result && success; -} - /** * Record data from a single Sensor; to be run in a seperate thread * @param arg - Cast to Sensor* - Sensor that the thread will handle @@ -310,41 +206,43 @@ void * Sensor_Loop(void * arg) while (s->activated) { DataPoint d; - //Log(LOGDEBUG, "Sensor %d reads data [%f,%f]", s->id, d.time_stamp, d.value); - if (Sensor_Read(s, &d)) // If new DataPoint is read: + d.value = 0; + bool success = s->read(s->user_id, &(d.value)); + + struct timeval t; + gettimeofday(&t, NULL); + d.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime()); + + if (success) { - //Log(LOGDEBUG, "Sensor %d saves data [%f,%f]", s->id, d.time_stamp, d.value); + + + Sensor_CheckData(s, d.value); Data_Save(&(s->data_file), &d, 1); // Record it } + else + Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id); + + usleep(1e5); //TODO: Adjust appropriately } // Needed to keep pthreads happy - - Log(LOGDEBUG, "Sensor %d finished", s->id); + Log(LOGDEBUG, "Sensor %s (%d,%d) finished", s->name,s->id,s->user_id); return NULL; } /** - * Get a Sensor given an ID string - * @param id_str ID string - * @returns Sensor* identified by the string; NULL on error + * Get a Sensor given its name + * @returns Sensor with the given name, NULL if there isn't one */ -Sensor * Sensor_Identify(const char * id_str) -{ - char * end; - // Parse string as integer - int id = strtol(id_str, &end, 10); - if (*end != '\0') +Sensor * Sensor_Identify(const char * name) +{ + for (int i = 0; i < g_num_sensors; ++i) { - return NULL; + if (strcmp(g_sensors[i].name, name) == 0) + return &(g_sensors[i]); } - // Bounds check - if (id < 0 || id >= NUMSENSORS) - return NULL; - - - Log(LOGDEBUG, "Sensor \"%s\" identified", g_sensor_names[id]); - return g_sensors+id; + return NULL; } /** @@ -353,15 +251,16 @@ Sensor * Sensor_Identify(const char * id_str) * @param id - ID of sensor * @param format - Format */ -void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format) +void Sensor_BeginResponse(FCGIContext * context, Sensor * s, DataFormat format) { // Begin response switch (format) { case JSON: FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONLong("id", id); - FCGI_JSONPair("name", g_sensor_names[id]); + FCGI_JSONLong("id", s->id); + FCGI_JSONLong("user_id", s->user_id); //NOTE: Might not want to expose this? + FCGI_JSONPair("name", s->name); break; default: FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); @@ -375,7 +274,7 @@ void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format) * @param id - ID of the sensor * @param format - Format */ -void Sensor_EndResponse(FCGIContext * context, SensorId id, DataFormat format) +void Sensor_EndResponse(FCGIContext * context, Sensor * s, DataFormat format) { // End response switch (format) @@ -400,13 +299,15 @@ void Sensor_Handler(FCGIContext *context, char * params) double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime()); int id = 0; + const char * name = ""; double start_time = 0; double end_time = current_time; const char * fmt_str; // key/value pairs FCGIValue values[] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, + {"id", &id, FCGI_INT_T}, + {"name", &name, FCGI_STRING_T}, {"format", &fmt_str, FCGI_STRING_T}, {"start_time", &start_time, FCGI_DOUBLE_T}, {"end_time", &end_time, FCGI_DOUBLE_T}, @@ -415,6 +316,7 @@ void Sensor_Handler(FCGIContext *context, char * params) // enum to avoid the use of magic numbers typedef enum { ID, + NAME, FORMAT, START_TIME, END_TIME, @@ -427,24 +329,46 @@ void Sensor_Handler(FCGIContext *context, char * params) return; } - // Error checking on sensor id - if (id < 0 || id >= NUMSENSORS) + Sensor * s = &(g_sensors[id]); // If id was not supplied, this defaults to &(g_sensors[0]) + if (FCGI_RECEIVED(values[NAME].flags)) + { + if (FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "Can't supply both sensor id and name"); + return; + } + s = Sensor_Identify(name); + if (s == NULL) + { + FCGI_RejectJSON(context, "Unknown sensor name"); + return; + } + } + else if (!FCGI_RECEIVED(values[ID].flags)) { - FCGI_RejectJSON(context, "Invalid sensor id"); + FCGI_RejectJSON(context, "No sensor id or name supplied"); return; } - Sensor * s = g_sensors+id; DataFormat format = Data_GetFormat(&(values[FORMAT])); // Begin response - Sensor_BeginResponse(context, id, format); + Sensor_BeginResponse(context, s, format); // Print Data Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); // Finish response - Sensor_EndResponse(context, id, format); + Sensor_EndResponse(context, s, format); +} + +/** + * Get the Name of a Sensor + * @param id - ID number + */ +const char * Sensor_GetName(int id) +{ + return g_sensors[id].name; } diff --git a/server/sensor.h b/server/sensor.h index 32e1e2d..5cdfbaa 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -9,68 +9,79 @@ #include "data.h" +/** + * Maximum number of sensors program can be compiled with + * (If you get an error "Increase SENSORS_MAX from %d" this is what it refers to) + */ +#define SENSORS_MAX 10 +extern int g_num_sensors; // in sensor.c -/** Number of sensors **/ -#define NUMSENSORS 10 -/** Sensor ids - there should be correspondence with the names in g_sensor_names **/ -typedef enum SensorId +/** Structure to define the warning and error thresholds of the sensors **/ +typedef struct { - STRAIN0, - STRAIN1, - STRAIN2, - STRAIN3, - PRESSURE0, - PRESSURE1, - PRESSURE_FEEDBACK, - MICROPHONE, - ENCLOSURE, - DILATOMETER -} SensorId; - - + /** Maximum safe value **/ + double max_error; + /** Minimum safe value **/ + double min_error; + /** Maximum value before a warning is reported **/ + double max_warn; + /** Minimum value before a warning is reported **/ + double min_warn; +} SensorThreshold; -/** Human readable names for the sensors **/ -extern const char * g_sensor_names[NUMSENSORS]; +/** Function pointer for sensor reading **/ +typedef bool (*ReadFn)(int, double * val); +/** Function pointer for sensor initialisation **/ +typedef bool (*InitFn)(const char *, int); +/** Function pointer for sensor cleanup **/ +typedef bool (*CleanFn)(int); /** Structure to represent a sensor **/ typedef struct { /** ID number of the sensor **/ - SensorId id; + int id; + /** User defined ID number **/ + int user_id; /** DataFile to store sensor values in **/ DataFile data_file; /** Indicates whether the Sensor is active or not **/ bool activated; /** Thread the Sensor is running in **/ pthread_t thread; - /** Most recently recorded data **/ - DataPoint newest_data; - + /** Function to read the sensor **/ + ReadFn read; + /** Function to initialise the sensor **/ + InitFn init; + /** Function to cleanup the sensor **/ + CleanFn cleanup; + /** Human readable name of the sensor **/ + const char * name; + /** Thresholds on the sensor **/ + SensorThreshold thresholds; } Sensor; -// Structure to define the warning and error thresholds of the sensors -typedef struct -{ - double max_error; - double min_error; - double max_warn; - double min_warn; -} SensorThreshold; + extern void Sensor_Init(); // One off initialisation of *all* sensors +extern void Sensor_Cleanup(); // Cleanup all sensors extern void Sensor_SetModeAll(ControlModes mode, void * arg); extern void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg); extern void * Sensor_Loop(void * args); // Main loop for a thread that handles a Sensor extern bool Sensor_Read(Sensor * s, DataPoint * d); // Read a single DataPoint, indicating if it has changed since the last one -extern void Sensor_CheckData(SensorId id, double value); // Check a DataPoint -extern Sensor * Sensor_Identify(const char * str); // Identify a Sensor from a string Id +extern void Sensor_CheckData(Sensor * s, double value); // Check a DataPoint +extern Sensor * Sensor_Identify(const char * str); // Identify a Sensor from a string extern void Sensor_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Sensor data +extern const char * Sensor_GetName(int id); + + + #endif //_SENSOR_H diff --git a/server/sensors/Makefile b/server/sensors/Makefile new file mode 100644 index 0000000..b72201d --- /dev/null +++ b/server/sensors/Makefile @@ -0,0 +1,27 @@ +# Makefile for server software +CXX = gcc +FLAGS = -std=c99 -Wall -pedantic -g -I../ +#-I/usr/include/opencv -I/usr/include/opencv2/highgui For OpenCV +LIB = -lpthread +OBJ = strain.o resource.o piped.o +HEADERS = $(wildcard *.h) +RM = rm -f + +all.o : $(OBJ) + ar rvs sensors.a $(OBJ) + + +%.o : %.c + $(CXX) $(FLAGS) -c $< + +clean : + $(RM) $(BIN) + $(RM) *.o + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) + $(RM) *.*~ + $(RM) *~ + + + diff --git a/server/sensors/README b/server/sensors/README new file mode 100644 index 0000000..f267d5a --- /dev/null +++ b/server/sensors/README @@ -0,0 +1,20 @@ +To add a new type of sensor to the program: + +1. Create a .c and .h file +2. In the .h file, define an enum representing the id number of each sensor of that type (you can skip this if you only need a single sensor) +3. Implement a function for reading a sensor value; it should be of the form: bool Read(int id, double * value) + - id indicates which of the sensors of that type is being read (if you only have a single sensor you can ignore it) + - The function stores the result in the double pointed to by value + - It returns true on success and false on a (non fatal) error +4. Optionally add an initialisation function: bool Init(int id, char * name) +5. In ../sensor.c make a call to Sensor_Add in the Sensor_Init function, passing the appropriate arguments. + - They are: The name of the sensor (passed to Init, see 4.) + - The id number according to the enum (just pass 0 if you don't need more than one sensor) + - The Read function + - The Init function (or NULL if you don't need one) + - Threshold values in the order: + max_error, min_error, max_warn, min_warn (as documented in ../sensor.h) + - If your sensor doesn't have/need safety thresholds, just pass ridiculous values like 1e50 or 1e-50 + - Yeah, it's hacky, but it works. + - You may need to increase SENSORS_MAX in ../sensor.h if you go insane with sensor adding power +6. Add the .o file to Makefile (the OBJ variable) diff --git a/server/sensors/piped.c b/server/sensors/piped.c new file mode 100644 index 0000000..6444e8b --- /dev/null +++ b/server/sensors/piped.c @@ -0,0 +1,101 @@ +/** + * @file piped.h + * @brief Sensor run by an external process and sent to this one through a pipe + * PURELY INCLUDED FOR TESTING PURPOSES + * This will work with any sensor that can unbuffer stdout + * ... So, although it's not recommended, you could write a sensor purely in something like python + * The FastCGI process will handle all the time stamps and stuff + */ + +#include "../log.h" +#include "../common.h" + +#include "piped.h" + +#include +#include +#include +#include + + +typedef struct +{ + int pid; + int sv[2]; + FILE * stream; + +} Piped; + +static Piped g_piped[PIPED_MAX]; +static int g_num_piped = 0; + +bool Piped_Init(const char * name, int id) +{ + if (++g_num_piped > PIPED_MAX) + { + Fatal("Too many sensors; Increase PIPED_MAX from %d in piped.h and recompile", PIPED_MAX); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, g_piped[id].sv) != 0) + Fatal("socketpair failed - %s", strerror(errno)); + + g_piped[id].pid = fork(); + if (g_piped[id].pid == 0) + { + dup2(g_piped[id].sv[0], fileno(stdin)); + dup2(g_piped[id].sv[0], fileno(stdout)); + + if (access(name, X_OK) == 0) //Check we STILL have permissions to start the file + { + execl(name, name, (char*)(NULL)); ///Replace process with desired executable + //execv(executablePath,arguments); ///Replace process with desired executable + } + else + { + Fatal("Can't execute file %s", name); + } + Fatal("execl error - %s", strerror(errno)); + } + else + { + g_piped[id].stream = fdopen(g_piped[id].sv[1], "r"); + setbuf(g_piped[id].stream, NULL); + } + return true; + +} + +bool Piped_Read(int id, double * value) +{ + if (g_piped[id].stream == NULL) + return false; + + static char line[BUFSIZ]; + + fgets(line, BUFSIZ, g_piped[id].stream); + int len = strlen(line); + //Log(LOGDEBUG, "Read %s (%d) chars", line, len); + while (--len >= 0 && len < BUFSIZ && isspace(line[len])) + { + line[len] = '\0'; + } + char * end = line; + *value = strtod(line, &end); + if (*end != '\0') + { + Log(LOGERR, "Couldn't interpret %s as double - %s", line, strerror(errno)); + return false; + } + return true; +} + +bool Piped_Cleanup(int id) +{ + fclose(g_piped[id].stream); + if (kill(g_piped[id].pid, 15) != 0) + { + Log(LOGWARN, "Couldn't kill piped %d - %s", g_piped[id].pid, strerror(errno)); + return false; + } + return true; +} diff --git a/server/sensors/piped.h b/server/sensors/piped.h new file mode 100644 index 0000000..92348f3 --- /dev/null +++ b/server/sensors/piped.h @@ -0,0 +1,15 @@ +#ifndef _PIPED_H +#define _PIPED_H + +/** + * @file piped.h + * @brief Sensor run by an external process and sent to this one through a pipe + * ... This is here as an example so that if people really hate our program they can still use the FastCGI API but make an entirely seperate sensor program + */ + +#define PIPED_MAX 1 + +extern bool Piped_Init(const char * name, int id); +extern bool Piped_Read(int id, double * value); +extern bool Piped_Cleanup(int id); +#endif //_PIPED_H diff --git a/server/sensors/resource.c b/server/sensors/resource.c new file mode 100644 index 0000000..fa52562 --- /dev/null +++ b/server/sensors/resource.c @@ -0,0 +1,30 @@ +#include "resource.h" + +#include "../log.h" +#include "../common.h" + +#include +#include + +bool Resource_Read(int id, double * value) +{ + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err != 0) + { + Log(LOGWARN, "Couldn't get resource information - %s", strerror(errno)); + } + switch (id) + { + case RESOURCE_CPU_USER: + *value = TIMEVAL_TO_DOUBLE(usage.ru_utime); + break; + case RESOURCE_CPU_SYS: + *value = TIMEVAL_TO_DOUBLE(usage.ru_stime); + break; + default: + Log(LOGWARN, "Unknown id %d", id); + return false; + } + return true; +} diff --git a/server/sensors/resource.h b/server/sensors/resource.h new file mode 100644 index 0000000..f605385 --- /dev/null +++ b/server/sensors/resource.h @@ -0,0 +1,17 @@ +/** + * @file resource.h + * @brief Testing sensors; gets rescource usage + */ + +#include + +/** + * Enum of sensor ids + */ +typedef enum +{ + RESOURCE_CPU_USER, + RESOURCE_CPU_SYS +} ResourceID; + +extern bool Resource_Read(int id, double * value); diff --git a/server/sensors/strain.c b/server/sensors/strain.c new file mode 100644 index 0000000..3ee1624 --- /dev/null +++ b/server/sensors/strain.c @@ -0,0 +1,102 @@ +#include "strain.h" + +#include "../log.h" +#include "../bbb_pin.h" + +#include + +#define STRAIN_ADC ADC0 + +/** + * Convert Strain gauge id number to a GPIO pin on the Mux + * @param id - The strain gauge id + * @returns - GPIO pin number + */ +static int Strain_To_GPIO(StrainID id) +{ + // Could use a lookup table; that would assume strict ordering of the enum in strain.h + //static int lookup[] = {GPIO0_30, GPIO1_28,GPIO0_31,GPIO1_16}; + //if (id < STRAIN0 || id > STRAIN3) + // Fatal("unknown strain id %d", id); + //return lookup[id]; + + switch (id) + { + case STRAIN0: + return GPIO0_30; + case STRAIN1: + return GPIO1_28; + case STRAIN2: + return GPIO0_31; + case STRAIN3: + return GPIO1_16; + default: + Fatal("Unknown StrainID %d", id); + return -1; // Should never happen + } +} + +/** + * Convert ADC Strain reading to a physically meaningful value + * @param reading - The ADC reading + * @returns Something more useful + */ +static double Strain_Calibrated(int reading) +{ + return (double)(reading); +} + +/** + * Initialise a Strain gauge + * @param id - The strain gauge to initialise + * @param name - Name of the strain gauge; ignored + * @returns true if initialisation was successful + */ +bool Strain_Init(const char * name, int id) +{ + int gpio_num = Strain_To_GPIO(id); + GPIO_Export(gpio_num); + if (!GPIO_Set(gpio_num, false)) + Fatal("Couldn't set GPIO%d for strain sensor %d to LOW", gpio_num, id); + + static bool init_adc = false; + if (!init_adc) + { + init_adc = true; + ADC_Export(STRAIN_ADC); + } + return true; +} + +/** + * Read from a Strain gauge + * @param id - The strain gauge to read + * @param val - Will store the read value if successful + * @returns true on successful read + */ +bool Strain_Read(int id, double * value) +{ + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // CRITICAL SECTION - Only one Strain gauge can be read at a time + pthread_mutex_lock(&mutex); + + int gpio_num = Strain_To_GPIO(id); + + // Set the multiplexer + if (!GPIO_Set(gpio_num, true)) + Fatal("Couldn't set GPIO%d for strain sensor %d to HIGH (before reading)", gpio_num,id); + + // Read the ADC + int tmp = 0; + bool result = ADC_Read(STRAIN_ADC, &tmp); // If this fails, it's not fatal + + //TODO: Callibrate? + *value = Strain_Calibrated(tmp); + + // Unset the multiplexer + if (!GPIO_Set(gpio_num, false)) + Fatal("Couldn't set GPIO%d for strain sensor %d to LOW (after reading)", gpio_num, id); + + pthread_mutex_unlock(&mutex); + + return result; +} diff --git a/server/sensors/strain.h b/server/sensors/strain.h new file mode 100644 index 0000000..1afb625 --- /dev/null +++ b/server/sensors/strain.h @@ -0,0 +1,22 @@ +#ifndef _STRAIN_H +#define _STRAIN_H + +#include + +/** + * Enum of strain IDs + */ +typedef enum +{ + STRAIN0, + STRAIN1, + STRAIN2, + STRAIN3 +} StrainID; + +// Initialise a strain gauge +extern bool Strain_Init(const char * name, int id); +// Read from a strain gauge +extern bool Strain_Read(int id, double * value); + +#endif //_STRAIN_H diff --git a/server/valgrind.sh b/server/valgrind.sh index fc5c8d3..4ebea4a 100755 --- a/server/valgrind.sh +++ b/server/valgrind.sh @@ -1,3 +1,4 @@ #!/bin/bash . parameters -valgrind ./server $parameters +valgrind --leak-check=full --track-origins=yes ./server $parameters +#valgrind ./server $parameters