From: Sam Moore Date: Fri, 13 Sep 2013 13:42:00 +0000 (+0800) Subject: Add Actuator related code X-Git-Url: https://git.ucc.asn.au/?p=matches%2FMCTX3420.git;a=commitdiff_plain;h=ea228f75c1f6d83f9a02a5ad5a7341caabac65c8 Add Actuator related code Made generalised Data_Handler function to reduce copy-paste. (So you can query an Actuator for what values it has been set to). Still a lot of copy-paste going on though. Also added a few reports while I'm at it. I suspect I will have to merge this with stuff other people did? --- diff --git a/reports/week3/sam.pdf b/reports/week3/sam.pdf new file mode 100644 index 0000000..1122c95 Binary files /dev/null and b/reports/week3/sam.pdf differ diff --git a/reports/week3/summary.pdf b/reports/week3/summary.pdf new file mode 100644 index 0000000..06e0e82 Binary files /dev/null and b/reports/week3/summary.pdf differ diff --git a/reports/week4/summary.pdf b/reports/week4/summary.pdf new file mode 100644 index 0000000..a5547a0 Binary files /dev/null and b/reports/week4/summary.pdf differ diff --git a/reports/week5/gui.png b/reports/week5/gui.png new file mode 100644 index 0000000..5f1b170 Binary files /dev/null and b/reports/week5/gui.png differ diff --git a/reports/week5/gui2.pdf b/reports/week5/gui2.pdf new file mode 100644 index 0000000..fd7aa5a Binary files /dev/null and b/reports/week5/gui2.pdf differ diff --git a/reports/week5/gui2.png b/reports/week5/gui2.png new file mode 100644 index 0000000..65cef7b Binary files /dev/null and b/reports/week5/gui2.png differ diff --git a/reports/week5/summary.pdf b/reports/week5/summary.pdf new file mode 100644 index 0000000..7fd7382 Binary files /dev/null and b/reports/week5/summary.pdf differ diff --git a/reports/week6/summary.pdf b/reports/week6/summary.pdf new file mode 100644 index 0000000..cf6c40c Binary files /dev/null and b/reports/week6/summary.pdf differ diff --git a/server/Makefile b/server/Makefile index 74ba473..bb70c4f 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,7 +2,7 @@ CXX = gcc FLAGS = -std=c99 -Wall -Werror -pedantic -g LIB = -lfcgi -lssl -lcrypto -lpthread -lm -OBJ = log.o control.o data.o fastcgi.o main.o sensor.o +OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o RM = rm -f BIN = server diff --git a/server/actuator.c b/server/actuator.c new file mode 100644 index 0000000..b3cb361 --- /dev/null +++ b/server/actuator.c @@ -0,0 +1,278 @@ +/** + * @file actuator.c + * @purpose Implementation of Actuator related functionality + */ + +#include "actuator.h" +#include "options.h" + +/** Array of Actuators (global to this file) initialised by Actuator_Init **/ +static Actuator g_actuators[NUMACTUATORS]; + +/** Human readable names for the Actuators **/ +const char * g_actuator_names[NUMACTUATORS] = { + "actuator_test0", "actuator_test1" +}; + +/** + * One off initialisation of *all* Actuators + */ +void Actuator_Init() +{ + for (int i = 0; i < NUMACTUATORS; ++i) + { + g_actuators[i].id = i; + Data_Init(&(g_actuators[i].data_file)); + pthread_mutex_init(&(g_actuators[i].mutex), NULL); + } +} + +/** + * Start an Actuator + * @param a - The Actuator to start + * @param experiment_name - Prepended to DataFile filename + */ +void Actuator_Start(Actuator * a, const char * experiment_name) +{ + // Set filename + char filename[BUFSIZ]; + if (sprintf(filename, "%s_a%d", experiment_name, a->id) >= BUFSIZ) + { + Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); + } + + Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename); + // Open DataFile + Data_Open(&(a->data_file), filename); + + a->activated = true; // Don't forget this + + a->control_changed = false; + + // Create the thread + pthread_create(&(a->thread), NULL, Actuator_Loop, (void*)(a)); +} + +/** + * Stop an Actuator + * @param s - The Actuator to stop + */ +void Actuator_Stop(Actuator * a) +{ + // Stop + a->activated = false; + Actuator_SetControl(a, NULL); + pthread_join(a->thread, NULL); // Wait for thread to exit + Data_Close(&(a->data_file)); // Close DataFile + +} + +/** + * Stop all Actuators + */ +void Actuator_StopAll() +{ + for (int i = 0; i < NUMACTUATORS; ++i) + Actuator_Stop(g_actuators+i); +} + +/** + * Start all Actuators + */ +void Actuator_StartAll(const char * experiment_name) +{ + for (int i = 0; i < NUMACTUATORS; ++i) + Actuator_Start(g_actuators+i, experiment_name); +} + +/** + * Actuator control thread + * @param arg - Cast to an Actuator* + * @returns NULL to keep pthreads happy + */ +void * Actuator_Loop(void * arg) +{ + Actuator * a = (Actuator*)(arg); + + // Loop until stopped + while (a->activated) + { + pthread_mutex_lock(&(a->mutex)); + while (!a->control_changed) + { + pthread_cond_wait(&(a->cond), &(a->mutex)); + } + a->control_changed = false; + pthread_mutex_unlock(&(a->mutex)); + if (!a->activated) + break; + + Actuator_SetValue(a, a->control.value); + } + + //TODO: Cleanup? + + // Keep pthreads happy + return NULL; +} + +/** + * Set an Actuators control variable + * @param a - Actuator to control + * @param c - Control to set to + */ +void Actuator_SetControl(Actuator * a, ActuatorControl * c) +{ + pthread_mutex_lock(&(a->mutex)); + if (c != NULL) + a->control = *c; + a->control_changed = true; + pthread_cond_broadcast(&(a->cond)); + pthread_mutex_unlock(&(a->mutex)); + +} + +/** + * Set an Actuator value + * @param a - The Actuator + * @param value - The value to set + */ +void Actuator_SetValue(Actuator * a, double value) +{ + // Set time stamp + struct timeval t; + gettimeofday(&t, NULL); + + DataPoint d = {TIMEVAL_DIFF(t, g_options.start_time), value}; + //TODO: Set actuator + switch (a->id) + { + case ACTUATOR_TEST0: + break; + case ACTUATOR_TEST1: + break; + } + + Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value); + + // Record the value + Data_Save(&(a->data_file), &d, 1); +} + +/** + * Helper: Begin Actuator response in a given format + * @param context - the FCGIContext + * @param format - Format + * @param id - ID of Actuator + */ +void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat format) +{ + // Begin response + switch (format) + { + case JSON: + FCGI_BeginJSON(context, STATUS_OK); + FCGI_JSONLong("id", id); + break; + default: + FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); + break; + } +} + +/** + * Helper: End Actuator response in a given format + * @param context - the FCGIContext + * @param id - ID of the Actuator + * @param format - Format + */ +void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat format) +{ + // End response + switch (format) + { + case JSON: + FCGI_EndJSON(); + break; + default: + break; + } +} + + + + +/** + * Handle a request for an Actuator + * @param context - FCGI context + * @param params - Parameters passed + */ +void Actuator_Handler(FCGIContext * context, char * params) +{ + struct timeval now; + gettimeofday(&now, NULL); + double current_time = TIMEVAL_DIFF(now, g_options.start_time); + int id = 0; + double set = 0; + double start_time = 0; + double end_time = current_time; + char * fmt_str; + + // key/value pairs + FCGIValue values[] = { + {"id", &id, FCGI_REQUIRED(FCGI_LONG_T)}, + {"set", &set, FCGI_DOUBLE_T}, + {"start_time", &start_time, FCGI_DOUBLE_T}, + {"end_time", &end_time, FCGI_DOUBLE_T}, + {"format", &fmt_str, FCGI_STRING_T} + }; + + // enum to avoid the use of magic numbers + typedef enum { + ID, + SET, + START_TIME, + END_TIME, + FORMAT + } ActuatorParams; + + // Fill values appropriately + if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue))) + { + // Error occured; FCGI_RejectJSON already called + return; + } + + // Get the Actuator identified + Actuator * a = NULL; + if (id < 0 || id >= NUMACTUATORS) + { + FCGI_RejectJSON(context, "Invalid Actuator id"); + return; + } + + a = g_actuators+id; + + DataFormat format = Data_GetFormat(&(values[FORMAT])); + + // Begin response + Actuator_BeginResponse(context, id, format); + + // Set? + if (FCGI_RECEIVED(values[SET].flags)) + { + if (format == JSON) + FCGI_JSONDouble("set", set); + + ActuatorControl c; + c.value = set; + + Actuator_SetControl(a, &c); + } + + // Print Data + Data_Handler(&(a->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); + + // Finish response + Actuator_EndResponse(context, id, format); +} diff --git a/server/actuator.h b/server/actuator.h new file mode 100644 index 0000000..df946a0 --- /dev/null +++ b/server/actuator.h @@ -0,0 +1,77 @@ +/** + * @file actuator.h + * @purpose Declarations for actuator control + */ + +#ifndef _ACTUATOR_H +#define _ACTUATOR_H + +#include "common.h" +#include "data.h" + +//NOTE: Functionality is very similar to Sensor stuff +// BUT it's probably very unwise to try and generalise Sensors and Actuators to the same thing (ie: Device) +// Might be OK in C++ but not easy in C + +/** Number of actuators **/ +#define NUMACTUATORS 2 + +/** List of actuator ids (should be of size NUMACTUATORS) **/ +typedef enum +{ + ACTUATOR_TEST0, + ACTUATOR_TEST1 +} ActuatorId; + +/** Human readable names for the Actuators **/ +extern const char * g_actuator_names[NUMACTUATORS]; + +/** Control structure for Actuator setting **/ +typedef struct +{ + //TODO: Add functionality as needed + /** Simple value for Actuator **/ + double value; +} ActuatorControl; + +typedef struct +{ + /** ID number of the actuator **/ + ActuatorId id; + /** Control parameters for the Actuator **/ + ActuatorControl control; + /** Flag indicates if ActuatorControl has been changed **/ + bool control_changed; + /** DataFile to store actuator settings **/ + DataFile data_file; + /** Thread the Actuator is controlled by **/ + pthread_t thread; + /** Mutex around ActuatorControl **/ + pthread_mutex_t mutex; + /** Used to wake up Actuator control thread **/ + pthread_cond_t cond; + /** Indicates whether the Actuator is running **/ + bool activated; + +} Actuator; + +extern void Actuator_Init(); // One off initialisation of *all* Actuators + + +extern void Actuator_StartAll(const char * experiment_name); // Start all Actuators +extern void Actuator_StopAll(); // Stop all Actuators + +extern void Actuator_Start(Actuator * a, const char * experiment_name); // Start a Actuator +extern void Actuator_Stop(Actuator * a); // Stop an Actuator + + +extern void * Actuator_Loop(void * args); // Main loop for a thread that handles an Actuator +extern void Actuator_SetValue(Actuator * a, double value); // Set an actuator by value +extern void Actuator_SetControl(Actuator * a, ActuatorControl * c); // Set the control for an Actuator +extern Actuator * Actuator_Identify(const char * str); // Identify a Sensor from a string Id + +extern void Actuator_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Actuator control + +#endif //_ACTUATOR_H + +//EOF diff --git a/server/control.c b/server/control.c index 4da1f75..1f33eea 100644 --- a/server/control.c +++ b/server/control.c @@ -5,13 +5,10 @@ #include "common.h" #include "control.h" -const char * g_actuator_names[NUMACTUATORS] = { - "Pressure regulator", "Solenoid 1" -}; /** * Handles control of the actuators. - */ + * void ActuatorHandler(FCGIContext *context, ActuatorId id, const char *set_value) { char *ptr; @@ -49,6 +46,7 @@ void ActuatorHandler(FCGIContext *context, ActuatorId id, const char *set_value) STATUS_ERROR, "Invalid actuator id specified."); } } +*/ /** * System control handler. This covers control over all aspects of the system. @@ -79,6 +77,7 @@ void Control_Handler(FCGIContext *context, char *params) { set_value = value; } } + Log(LOGDEBUG, "Id %d", id); // to stop compiler complaining for now if (action == NULL) { //Must have an action FCGI_RejectJSON(context, "No action specified"); @@ -99,8 +98,9 @@ void Control_Handler(FCGIContext *context, char *params) { if (set_value == NULL || *set_value == '\0') { FCGI_RejectJSONEx(context, STATUS_ERROR, "Set called but no value specified."); - } else { - ActuatorHandler(context, id, set_value); + } else + { +// ActuatorHandler(context, id, set_value); } } } diff --git a/server/control.h b/server/control.h index a1aa5c7..2de9b7f 100644 --- a/server/control.h +++ b/server/control.h @@ -5,17 +5,7 @@ #ifndef _CONTROL_H #define _CONTROL_H -/** Number of actuators **/ -#define NUMACTUATORS 2 -/** List of actuator ids (should be of size NUMACTUATORS) **/ -typedef enum ActuatorId { - ACT_PRESSURE, - ACT_SOLENOID1 -} ActuatorId; - -/** Human readable names for the actuator ids **/ -extern const char * g_actuator_names[NUMACTUATORS]; /** ID codes for all the actuators **/ extern void Control_Handler(FCGIContext *context, char *params); diff --git a/server/data.c b/server/data.c index 82aadb6..8bfd8b1 100644 --- a/server/data.c +++ b/server/data.c @@ -16,6 +16,7 @@ void Data_Init(DataFile * df) df->filename = NULL; df->read_file = NULL; df->write_file = NULL; + pthread_mutex_init(&(df->mutex), NULL); } /** @@ -35,7 +36,7 @@ void Data_Open(DataFile * df, const char * filename) df->num_points = 0; // Set write FILE* - df->write_file = fopen(filename, "w+"); + df->write_file = fopen(filename, "wb+"); if (df->write_file == NULL) { Fatal("Error opening DataFile %s - %s", filename, strerror(errno)); @@ -47,7 +48,7 @@ void Data_Open(DataFile * df, const char * filename) //NOTE: Opening the same file in read mode gives funny results; fread generally reads less than expected // The strerror is: "Transport endpoint is not connected" /* - fopen(filename, "r"); + df->read_file = fopen(filename, "rb"); if (df->read_file == NULL) { Fatal("Error opening DataFile %s - %s", filename, strerror(errno)); @@ -318,3 +319,74 @@ int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest) return index; } + +/** + * Helper; handle FCGI response that requires data + * Should be called first. + * @param df - DataFile to access + * @param start - Info about start_time param + * @param end - Info about end_time param + * @param fmt - Info about format param + * @param current_time - Current time + */ +void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time) +{ + double start_time = *(double*)(start->value); + double end_time = *(double*)(end->value); + + if (format == JSON) + { + FCGI_JSONKey("data"); + } + + // If a time was specified + if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags)) + { + // Wrap times relative to the current time + if (start_time < 0) + start_time += current_time; + if (end_time < 0) + end_time += current_time; + + // Print points by time range + Data_PrintByTimes(df, start_time, end_time, format); + + } + else // No time was specified; just return a recent set of points + { + pthread_mutex_lock(&(df->mutex)); + int start_index = df->num_points-DATA_BUFSIZ; + int end_index = df->num_points-1; + pthread_mutex_unlock(&(df->mutex)); + + // Bounds check + if (start_index < 0) + start_index = 0; + if (end_index < 0) + end_index = 0; + + // Print points by indexes + Data_PrintByIndexes(df, start_index, end_index, format); + } + +} + +/** + * Helper - Convert human readable format string to DataFormat + * @param fmt - FCGIValue to use + */ +DataFormat Data_GetFormat(FCGIValue * fmt) +{ + char * fmt_str = *(char**)(fmt->value); + // Check if format type was specified + if (FCGI_RECEIVED(fmt->flags)) + { + if (strcmp(fmt_str, "json") == 0) + return JSON; + else if (strcmp(fmt_str, "tsv") == 0) + return TSV; + else + Log(LOGERR, "Unknown format type \"%s\"", fmt_str); + } + return JSON; +} diff --git a/server/data.h b/server/data.h index e52dadc..9731a2d 100644 --- a/server/data.h +++ b/server/data.h @@ -51,4 +51,7 @@ extern void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, D extern void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format); // Print data between time values extern int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest); // Find index of DataPoint with the closest timestamp to that given +extern void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time); // Helper; given FCGI params print data +extern DataFormat Data_GetFormat(FCGIValue * fmt); // Helper; convert human readable format string to DataFormat + #endif //_DATAPOINT_H diff --git a/server/fastcgi.c b/server/fastcgi.c index b58ba79..989d16a 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -12,6 +12,7 @@ #include "common.h" #include "sensor.h" +#include "actuator.h" #include "control.h" #include "options.h" @@ -464,6 +465,8 @@ void * FCGI_RequestLoop (void *data) module_handler = Control_Handler; } else if (!strcmp("sensors", module)) { module_handler = Sensor_Handler; + } else if (!strcmp("actuators", module)) { + module_handler = Actuator_Handler; } context.current_module = module; diff --git a/server/main.c b/server/main.c index d1aaa4c..6ea256c 100644 --- a/server/main.c +++ b/server/main.c @@ -7,6 +7,7 @@ #include "common.h" #include "options.h" #include "sensor.h" +#include "actuator.h" // --- Standard headers --- // #include // for signal handling @@ -76,12 +77,15 @@ int main(int argc, char ** argv) } */ Sensor_Init(); + Actuator_Init(); Sensor_StartAll("test"); + Actuator_StartAll("test"); // run request thread in the main thread FCGI_RequestLoop(NULL); Sensor_StopAll(); + Actuator_StopAll(); Cleanup(); return 0; diff --git a/server/sensor.c b/server/sensor.c index 227ea42..55a9eaa 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -40,7 +40,7 @@ void Sensor_Start(Sensor * s, const char * experiment_name) { // Set filename char filename[BUFSIZ]; - if (sprintf(filename, "%s_%d", experiment_name, s->id) >= BUFSIZ) + if (sprintf(filename, "%s_s%d", experiment_name, s->id) >= BUFSIZ) { Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); } @@ -236,7 +236,6 @@ void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format) case JSON: FCGI_BeginJSON(context, STATUS_OK); FCGI_JSONLong("id", id); - FCGI_JSONKey("data"); break; default: FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); @@ -302,67 +301,22 @@ void Sensor_Handler(FCGIContext *context, char * params) return; } - // Get Sensor - Sensor * s = NULL; // Error checking on sensor id if (id < 0 || id >= NUMSENSORS) { - Log(LOGERR, "Invalid id %d", id); - } - else - { - s = g_sensors+id; + FCGI_RejectJSON(context, "Invalid sensor id"); + return; } + Sensor * s = g_sensors+id; - DataFormat format = JSON; + DataFormat format = Data_GetFormat(&(values[FORMAT])); - // Check if format type was specified - if (FCGI_RECEIVED(values[FORMAT].flags)) - { - if (strcmp(fmt_str, "json") == 0) - format = JSON; - else if (strcmp(fmt_str, "tsv") == 0) - format = TSV; - else - Log(LOGERR, "Unknown format type \"%s\"", fmt_str); - } - - - // Begin response Sensor_BeginResponse(context, id, format); - - // If a time was specified - if ((s != NULL) && (FCGI_RECEIVED(values[START_TIME].flags) || FCGI_RECEIVED(values[END_TIME].flags))) - { - // Wrap times relative to the current time - if (start_time < 0) - start_time += current_time; - if (end_time < 0) - end_time += current_time; - - // Print points by time range - Data_PrintByTimes(&(s->data_file), start_time, end_time, format); - } - else if (s != NULL) // No time was specified; just return a recent set of points - { - pthread_mutex_lock(&(s->data_file.mutex)); - int start_index = s->data_file.num_points-DATA_BUFSIZ; - int end_index = s->data_file.num_points-1; - pthread_mutex_unlock(&(s->data_file.mutex)); - - // Bounds check - if (start_index < 0) - start_index = 0; - if (end_index < 0) - end_index = 0; - - // Print points by indexes - Log(LOGDEBUG, "Sensor %d file \"%s\" indexes %d->%d", s->id, s->data_file.filename, start_index, end_index); - Data_PrintByIndexes(&(s->data_file), start_index, end_index, format); - } + // Print Data + Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); // Finish response Sensor_EndResponse(context, id, format); diff --git a/server/valgrind.sh b/server/valgrind.sh index 3262b72..062c490 100755 --- a/server/valgrind.sh +++ b/server/valgrind.sh @@ -1,2 +1,2 @@ #!/bin/bash -valgrind --leak-check=full --track-origins=yes ./server +valgrind --leak-check=full --track-origins=yes --show-reachable=yes ./server