From: Sam Moore Date: Thu, 10 Oct 2013 16:14:36 +0000 (+0800) Subject: Refactor Actuators X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=83a3a266ff3cfecea7a6275924f3bdd15dfe6436;p=matches%2FMCTX3420.git Refactor Actuators Done fairly similar to Sensors now. Included the LED test and a new test that just writes to a file. `tail -f` the file and change the control to see how Actuator_Loop automagically does its job. New features / Notes: - ActuatorControl is a simple step increase (not just a single value) which Actuator_Loop automatically follows - That required very small changes :) - Control_Handler no longer checks for key (since that's passed as a cookie) - Will want to look at preventing different users from stopping an experiment in progress (by preventing them from logging in entirely?) - Will probably want to look at implementing "admin" and "regular" users, or at least providing the basis for it in the server - Can change sensor sampling rate through api - May want to look at implementing an "average over time period" to reduce the load on the client - eg: Sensor records at a sampling rate << 1s, but only records a DataPoint (average) every second - Actuators have a Sanity Check function; do something similar for Sensors? - Will want to look at safety stuff more carefully, since at the moment things just call "Fatal" - This should in turn call "Cleanup" which should in turn deinitialise any active Sensors/Actuators - Perhaps we need "Fatal" and "Reset" to give different return codes, and then ./run.sh will restart depending on the return code Looking at getting values plotted against values (instead of time). - For each time stamp on the dependent variable, take the independent variable with time stamps between it and the next, then average - Seems reasonable, not sure whether to do in server or client though, will write something to test the algorithm first. Things are getting there, but still a lot to do. I just realised I temporarily uncommented security in fastcgi.c ... It's a good thing the system still hasn't been deployed, because I can't be bothered changing it back at the moment. --- diff --git a/server/Makefile b/server/Makefile index 34d604e..c5be05f 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,7 +2,7 @@ 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 sensors/sensors.a +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 actuators/actuators.a RM = rm -f BIN = server @@ -10,6 +10,7 @@ BIN = server all : $(MAKE) -C sensors + $(MAKE) -C actuators $(MAKE) $(BIN) diff --git a/server/actuator.c b/server/actuator.c index f5d7cd6..3e26e37 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -9,30 +9,52 @@ #include "bbb_pin.h" + + +/** Number of actuators **/ +int g_num_actuators = 0; + /** Array of Actuators (global to this file) initialised by Actuator_Init **/ -static Actuator g_actuators[NUMACTUATORS]; +static Actuator g_actuators[ACTUATORS_MAX]; +/** + * Add and initialise an Actuator + * @param name - Human readable name of the actuator + * @param read - Function to call whenever the actuator should be read + * @param init - Function to call to initialise the actuator (may be NULL) + * @returns Number of actuators added so far + */ +int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity) +{ + if (++g_num_actuators > ACTUATORS_MAX) + { + Fatal("Too many sensors; Increase ACTUATORS_MAX from %d in actuator.h and recompile", ACTUATORS_MAX); + } + Actuator * a = &(g_actuators[g_num_actuators-1]); + a->id = g_num_actuators-1; + a->user_id = user_id; + Data_Init(&(a->data_file)); + a->name = name; + a->set = set; // Set read function + a->init = init; // Set init function + if (init != NULL) + init(name, user_id); // Call it + a->sanity = sanity; + + pthread_mutex_init(&(a->mutex), NULL); + + return g_num_actuators; +} -/** Human readable names for the Actuators **/ -const char * g_actuator_names[NUMACTUATORS] = { - "actuator_test0", "gpio1_16", "EHRPWM0A_duty@60Hz" -}; /** * One off initialisation of *all* Actuators */ +#include "actuators/ledtest.h" +#include "actuators/filetest.h" 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); - } - - // Initialise pins used - GPIO_Export(GPIO1_16); - PWM_Export(EHRPWM0A); - + //Actuator_Add("ledtest",0, Ledtest_Set, NULL,NULL,NULL); + Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity); } /** @@ -112,7 +134,7 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) */ void Actuator_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMACTUATORS; i++) + for (int i = 0; i < ACTUATORS_MAX; i++) Actuator_SetMode(&g_actuators[i], mode, arg); } @@ -138,7 +160,23 @@ void * Actuator_Loop(void * arg) if (!a->activated) break; - Actuator_SetValue(a, a->control.value); + Actuator_SetValue(a, a->control.start); + // Currently does discrete steps after specified time intervals + while (a->control.steps > 0 && a->activated) + { + usleep(1e6*(a->control.stepwait)); + a->control.start += a->control.stepsize; + Actuator_SetValue(a, a->control.start); + + a->control.steps--; + } + usleep(1e6*(a->control.stepwait)); + + //TODO: + // Note that although this loop has a sleep in it which would seem to make it hard to enforce urgent shutdowns, + // You can call the Actuator's cleanup function immediately (and this loop should later just exit) + // tl;dr This function isn't/shouldn't be responsible for the emergency Actuator stuff + // (That should be handled by the Fatal function... at some point) } //TODO: Cleanup? @@ -170,53 +208,21 @@ void Actuator_SetControl(Actuator * a, ActuatorControl * c) */ void Actuator_SetValue(Actuator * a, double value) { + if (a->sanity != NULL && !a->sanity(a->user_id, value)) + { + //ARE YOU INSANE? + Fatal("Insane value %lf for actuator %s", value, a->name); + } + if (!(a->set(a->user_id, value))) + { + Fatal("Failed to set actuator %s to %lf", a->name, value); + } + // Set time stamp struct timeval t; gettimeofday(&t, NULL); - + // Record and save DataPoint DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), value}; - //TODO: Set actuator - switch (a->id) - { - case ACTUATOR_TEST0: - { - // Onboard LEDs test actuator - FILE *led_handle = NULL; //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds-using-c/ - const char *led_format = "/sys/class/leds/beaglebone:green:usr%d/brightness"; - char buf[50]; - bool turn_on = value; - - for (int i = 0; i < 4; i++) - { - snprintf(buf, 50, led_format, i); - if ((led_handle = fopen(buf, "w")) != NULL) - { - if (turn_on) - fwrite("1", sizeof(char), 1, led_handle); - else - fwrite("0", sizeof(char), 1, led_handle); - fclose(led_handle); - } - else - Log(LOGDEBUG, "LED fopen failed: %s", strerror(errno)); - } - } - break; - case ACTUATOR_TEST1: - GPIO_Set(GPIO1_16, (bool)(value)); - break; - case ACTUATOR_TEST2: - { - // PWM analogue actuator (currently generates one PWM signal with first PWM module) - static long freq = 16666666; // This is 60Hz - PWM_Set(EHRPWM0A, true, freq, value * freq); // Set the duty cycle - break; - } - } - - Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value); - - // Record the value Data_Save(&(a->data_file), &d, 1); } @@ -226,15 +232,16 @@ void Actuator_SetValue(Actuator * a, double value) * @param format - Format * @param id - ID of Actuator */ -void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat format) +void Actuator_BeginResponse(FCGIContext * context, Actuator * a, DataFormat format) { // Begin response switch (format) { case JSON: FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONLong("id", id); - FCGI_JSONPair("name", g_actuator_names[id]); + FCGI_JSONLong("id", a->id); + FCGI_JSONLong("user_id", a->user_id); //TODO: Don't need to show this? + FCGI_JSONPair("name", a->name); break; default: FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); @@ -248,7 +255,7 @@ void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat for * @param id - ID of the Actuator * @param format - Format */ -void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat format) +void Actuator_EndResponse(FCGIContext * context, Actuator * a, DataFormat format) { // End response switch (format) @@ -273,15 +280,17 @@ void Actuator_Handler(FCGIContext * context, char * params) gettimeofday(&now, NULL); double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime()); int id = 0; - double set = 0; + char * name = ""; + char * set = ""; double start_time = 0; double end_time = current_time; char * fmt_str; // key/value pairs FCGIValue values[] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, - {"set", &set, FCGI_DOUBLE_T}, + {"id", &id, FCGI_INT_T}, + {"name", &name, FCGI_STRING_T}, + {"set", &set, FCGI_STRING_T}, {"start_time", &start_time, FCGI_DOUBLE_T}, {"end_time", &end_time, FCGI_DOUBLE_T}, {"format", &fmt_str, FCGI_STRING_T} @@ -290,6 +299,7 @@ void Actuator_Handler(FCGIContext * context, char * params) // enum to avoid the use of magic numbers typedef enum { ID, + NAME, SET, START_TIME, END_TIME, @@ -305,34 +315,100 @@ void Actuator_Handler(FCGIContext * context, char * params) // Get the Actuator identified Actuator * a = NULL; - if (id < 0 || id >= NUMACTUATORS) + + if (FCGI_RECEIVED(values[NAME].flags)) + { + if (FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "Can't supply both id and name"); + return; + } + a = Actuator_Identify(name); + if (a == NULL) + { + FCGI_RejectJSON(context, "Unknown actuator name"); + return; + } + + } + else if (!FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "No id or name supplied"); + return; + } + else if (id < 0 || id >= ACTUATORS_MAX) { FCGI_RejectJSON(context, "Invalid Actuator id"); return; } + else + { + a = &(g_actuators[id]); + } - 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; - + ActuatorControl c = {0.0, 0.0, 0.0, 0}; // Need to set default values (since we don't require them all) + // sscanf returns the number of fields successfully read... + int n = sscanf(set, "%lf,%lf,%lf,%d", &(c.start), &(c.stepwait), &(c.stepsize), &(c.steps)); // Set provided values in order + if (n != 4) + { + // If the user doesn't provide all 4 values, the Actuator will get set *once* using the first of the provided values + // (see Actuator_Loop) + // Not really a problem if n = 1, but maybe generate a warning for 2 <= n < 4 ? + Log(LOGDEBUG, "Only provided %d values (expect %d) for Actuator setting", n); + } + // SANITY CHECKS + if (c.stepwait < 0 || c.steps < 0 || (a->sanity != NULL && !a->sanity(a->user_id, c.start))) + { + FCGI_RejectJSON(context, "Bad Actuator setting"); + return; + } Actuator_SetControl(a, &c); + } + + // Begin response + Actuator_BeginResponse(context, a, format); + if (format == JSON) + FCGI_JSONPair("set", set); // Print Data Data_Handler(&(a->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); // Finish response - Actuator_EndResponse(context, id, format); + Actuator_EndResponse(context, a, format); +} + +/** + * Get the name of an Actuator given its id + * @param id - ID of the actuator + * @returns The Actuator's name + */ +const char * Actuator_GetName(int id) +{ + return g_actuators[id].name; +} + +/** + * Identify an Actuator from its name string + * @param name - The name of the Actuator + * @returns Actuator + */ +Actuator * Actuator_Identify(const char * name) +{ + for (int i = 0; i < g_num_actuators; ++i) + { + if (strcmp(g_actuators[i].name, name) == 0) + return &(g_actuators[i]); + } + return NULL; } diff --git a/server/actuator.h b/server/actuator.h index 48e0f29..de81eea 100644 --- a/server/actuator.h +++ b/server/actuator.h @@ -8,37 +8,44 @@ #include "common.h" #include "data.h" +#include "device.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 3 +/** + * Maximum number of actuators program can be compiled with + * (If you get an error "Increase ACTUATORS_MAX from %d" this is what it refers to) + */ +#define ACTUATORS_MAX 5 +extern int g_num_actuators; // in actuator.c -/** List of actuator ids (should be of size NUMACTUATORS) **/ -typedef enum -{ - ACTUATOR_TEST0, - ACTUATOR_TEST1, - ACTUATOR_TEST2 -} 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; + // Currently implements a simple piecewise step increase + // Would be cool to have a function specified as a string... eg: "1.0 + 0.5*s^2" with "s" the step number, and then give "stepwait" and "steps" + // ... But that, like so many things, is probably overkill + /** Current value of Actuator **/ + double start; + /** Time to maintain Actuator at each value **/ + double stepwait; + /** Amount to increase/decrease Actuator on each step **/ + double stepsize; + /** Number of steps still to perform **/ + int steps; // Note that after it is first set, this will be decremented until it is zero + } ActuatorControl; typedef struct { /** ID number of the actuator **/ - ActuatorId id; + int id; + /** User ID number **/ + int user_id; + /** Name **/ + const char * name; /** Control parameters for the Actuator **/ ActuatorControl control; /** Flag indicates if ActuatorControl has been changed **/ @@ -53,7 +60,15 @@ typedef struct pthread_cond_t cond; /** Indicates whether the Actuator is running **/ bool activated; - + /** Initialisation function **/ + InitFn init; + /** Set function **/ + SetFn set; + /** Sanity check function **/ + SanityFn sanity; + /** Cleanup function **/ + CleanFn clean; + } Actuator; extern void Actuator_Init(); // One off initialisation of *all* Actuators @@ -67,6 +82,7 @@ extern void Actuator_SetControl(Actuator * a, ActuatorControl * c); // Set the c 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 +extern const char * Actuator_GetName(int id); #endif //_ACTUATOR_H diff --git a/server/actuators/Makefile b/server/actuators/Makefile new file mode 100644 index 0000000..a2ba5dc --- /dev/null +++ b/server/actuators/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 = ledtest.o filetest.o +HEADERS = $(wildcard *.h) +RM = rm -f + +all : $(OBJ) + ar rvs actuators.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/actuators/filetest.c b/server/actuators/filetest.c new file mode 100644 index 0000000..697753a --- /dev/null +++ b/server/actuators/filetest.c @@ -0,0 +1,25 @@ +#include "filetest.h" + +static FILE * f = NULL; +bool Filetest_Init(const char * name, int id) +{ + f = fopen(name, "w"); + setbuf(f, NULL); // Unbuffer + return (f != NULL); +} + +bool Filetest_Set(int id, double value) +{ + Log(LOGDEBUG, "Writing %lf to file", value); + return (fprintf(f, "%lf\n", value) > 1); +} + +bool Filetest_Cleanup(int id) +{ + return (fclose(f) == 0); +} + +bool Filetest_Sanity(int id, double value) +{ + return (abs(value) <= 1e4); +} diff --git a/server/actuators/filetest.h b/server/actuators/filetest.h new file mode 100644 index 0000000..a389c99 --- /dev/null +++ b/server/actuators/filetest.h @@ -0,0 +1,13 @@ +#ifndef _FILETEST_H +#define _FILETEST_H + +#include "../common.h" + +extern bool Filetest_Init(const char * name, int id); +extern bool Filetest_Set(int id, double value); +extern bool Filetest_Cleanup(int id); +extern bool Filetest_Sanity(int id, double value); + +#endif //_FILETEST_H + + diff --git a/server/actuators/ledtest.c b/server/actuators/ledtest.c new file mode 100644 index 0000000..c2f366c --- /dev/null +++ b/server/actuators/ledtest.c @@ -0,0 +1,29 @@ +#include "ledtest.h" + +bool Ledtest_Set(int id, double value) +{ + + FILE *led_handle = NULL; //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds + const char *led_format = "/sys/class/leds/beaglebone:green:usr%d/brightness"; + char buf[50]; + bool turn_on = value; + + for (int i = 0; i < 4; i++) + { + snprintf(buf, 50, led_format, i); + if ((led_handle = fopen(buf, "w")) != NULL) + { + if (turn_on) + fwrite("1", sizeof(char), 1, led_handle); + else + fwrite("0", sizeof(char), 1, led_handle); + fclose(led_handle); + } + else + { + Log(LOGDEBUG, "LED fopen failed: %s", strerror(errno)); + return false; + } + } + return true; +} diff --git a/server/actuators/ledtest.h b/server/actuators/ledtest.h new file mode 100644 index 0000000..46ce7a4 --- /dev/null +++ b/server/actuators/ledtest.h @@ -0,0 +1,11 @@ +#ifndef _LED_TEST_H +#define _LED_TEST_H + +#include "../common.h" + +extern bool Ledtest_Set(int id, double value); + + +#endif //_LED_TEST_H + +//EOF diff --git a/server/control.c b/server/control.c index 4b4a1e6..820c37c 100644 --- a/server/control.c +++ b/server/control.c @@ -32,13 +32,19 @@ bool PathExists(const char *path) * @param params The input parameters */ void Control_Handler(FCGIContext *context, char *params) { - const char *action, *key = "", *name = ""; + const char *action = ""; + const char *name = ""; bool force = false; ControlModes desired_mode; + + + // Login/auth now handled entirely in fastcgi.c and login.c + //TODO: Need to not have the ability for any user to stop someone else' experiment... + // (achieve by storing the username of the person running the current experiment, even when they log out?) + // (Our program should only realisitically support a single experiment at a time, so that should be sufficient) FCGIValue values[4] = { {"action", &action, FCGI_REQUIRED(FCGI_STRING_T)}, - {"key", &key, FCGI_STRING_T}, {"force", &force, FCGI_BOOL_T}, {"name", &name, FCGI_STRING_T} }; @@ -51,27 +57,22 @@ void Control_Handler(FCGIContext *context, char *params) { return; } else if (!strcmp(action, "emergency")) { desired_mode = CONTROL_EMERGENCY; - } else if (FCGI_HasControl(context, key)) { - if (!strcmp(action, "release")) { - FCGI_ReleaseControl(context); - } else if (!strcmp(action, "start")) { - desired_mode = CONTROL_START; - } else if (!strcmp(action, "pause")) { - desired_mode = CONTROL_PAUSE; - } else if (!strcmp(action, "resume")) { - desired_mode = CONTROL_RESUME; - } else if (!strcmp(action, "stop")) { - desired_mode = CONTROL_STOP; - } else { - FCGI_RejectJSON(context, "Unknown action specified."); - return; - } + } + else if (!strcmp(action, "release")) { + FCGI_ReleaseControl(context); + } else if (!strcmp(action, "start")) { + desired_mode = CONTROL_START; + } else if (!strcmp(action, "pause")) { + desired_mode = CONTROL_PAUSE; + } else if (!strcmp(action, "resume")) { + desired_mode = CONTROL_RESUME; + } else if (!strcmp(action, "stop")) { + desired_mode = CONTROL_STOP; } else { - FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, - "Invalid control key specified."); + FCGI_RejectJSON(context, "Unknown action specified."); return; } - + void *arg = NULL; if (desired_mode == CONTROL_START) { if (PathExists(name) && !force) { diff --git a/server/data.h b/server/data.h index e8d887f..df16efe 100644 --- a/server/data.h +++ b/server/data.h @@ -53,4 +53,5 @@ extern int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest 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/device.h b/server/device.h new file mode 100644 index 0000000..28b2406 --- /dev/null +++ b/server/device.h @@ -0,0 +1,19 @@ +/** + * @file device.h + * @brief Declare code/typedefs common to both Sensors and Actuators + */ +#ifndef _DEVICE_H +#define _DEVICE_H + +/** Function pointer for sensor reading **/ +typedef bool (*ReadFn)(int, double *); +/** Function pointer for actuator setting **/ +typedef bool (*SetFn)(int, double); +/** Function pointer for sensor initialisation **/ +typedef bool (*InitFn)(const char *, int); +/** Function pointer for sensor cleanup **/ +typedef bool (*CleanFn)(int); +/** Function to check the sanity of a value **/ +typedef bool (*SanityFn)(int, double); + +#endif //_DEVICE_H diff --git a/server/fastcgi.c b/server/fastcgi.c index 4b69f89..fe461b8 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -63,11 +63,11 @@ static void IdentifyHandler(FCGIContext *context, char *params) { if (ident_actuators) { FCGI_JSONKey("actuators"); FCGI_JSONValue("{\n\t\t"); - for (i = 0; i < NUMACTUATORS; i++) { + for (i = 0; i < g_num_actuators; i++) { if (i > 0) { FCGI_JSONValue(",\n\t\t"); } - FCGI_JSONValue("\"%d\" : \"%s\"", i, g_actuator_names[i]); + FCGI_JSONValue("\"%d\" : \"%s\"", i, Actuator_GetName(i)); } FCGI_JSONValue("\n\t}"); } @@ -507,7 +507,8 @@ void * FCGI_RequestLoop (void *data) if (module_handler) { - if (module_handler != Login_Handler && module_handler != IdentifyHandler) + //if (module_handler != Login_Handler && module_handler != IdentifyHandler) + if (false) // Testing { if (cookie[0] == '\0') { diff --git a/server/main.c b/server/main.c index 92f76b3..c2e5161 100644 --- a/server/main.c +++ b/server/main.c @@ -128,15 +128,19 @@ int main(int argc, char ** argv) Pin_Init(); // Try and start things + /* const char *ret; if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL) Fatal("Control_SetMode failed with '%s'", ret); + */ // run request thread in the main thread FCGI_RequestLoop(NULL); + /* if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL) Fatal("Control_SetMode failed with '%s'", ret); + */ //Sensor_StopAll(); //Actuator_StopAll(); diff --git a/server/run.sh b/server/run.sh index 1563d2e..76a7a48 100755 --- a/server/run.sh +++ b/server/run.sh @@ -10,9 +10,9 @@ fi if [[ "$(uname -m)" != *arm* ]]; then echo Not running on the BBB # Use this to quickly test run the server in valgrind - spawn-fcgi -p9005 -n ./valgrind.sh + #spawn-fcgi -p9005 -n ./valgrind.sh # Use this to run the server normally - #spawn-fcgi -p9005 -n ./server + spawn-fcgi -p9005 -n ./server exit 0 fi diff --git a/server/sensor.c b/server/sensor.c index 8406870..792bbbd 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -20,13 +20,14 @@ int g_num_sensors = 0; /** * Add and initialise a Sensor * @param name - Human readable name of the sensor + * @param user_id - User identifier * @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 + * @returns Number of actuators added so far */ 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) { @@ -47,6 +48,10 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn if (init != NULL) init(name, user_id); // Call it + // Start by averaging values taken over a second + s->sample_us = 1e6; + s->averages = 1; + // Set warning/error thresholds s->thresholds.max_error = max_error; s->thresholds.min_error = min_error; @@ -223,7 +228,7 @@ void * Sensor_Loop(void * arg) else Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id); - usleep(1e5); //TODO: Adjust appropriately + usleep(s->sample_us); } // Needed to keep pthreads happy @@ -303,6 +308,7 @@ void Sensor_Handler(FCGIContext *context, char * params) double start_time = 0; double end_time = current_time; const char * fmt_str; + double sample_s = 0; // key/value pairs FCGIValue values[] = { @@ -311,6 +317,7 @@ void Sensor_Handler(FCGIContext *context, char * params) {"format", &fmt_str, FCGI_STRING_T}, {"start_time", &start_time, FCGI_DOUBLE_T}, {"end_time", &end_time, FCGI_DOUBLE_T}, + {"sample_s", &sample_s, FCGI_DOUBLE_T} }; // enum to avoid the use of magic numbers @@ -320,6 +327,7 @@ void Sensor_Handler(FCGIContext *context, char * params) FORMAT, START_TIME, END_TIME, + SAMPLE_S } SensorParams; // Fill values appropriately @@ -329,7 +337,7 @@ void Sensor_Handler(FCGIContext *context, char * params) return; } - Sensor * s = &(g_sensors[id]); // If id was not supplied, this defaults to &(g_sensors[0]) + Sensor * s = NULL; if (FCGI_RECEIVED(values[NAME].flags)) { if (FCGI_RECEIVED(values[ID].flags)) @@ -349,7 +357,28 @@ void Sensor_Handler(FCGIContext *context, char * params) FCGI_RejectJSON(context, "No sensor id or name supplied"); return; } + else if (id < 0 || id >= g_num_sensors) + { + FCGI_RejectJSON(context, "Invalid sensor id"); + return; + } + else + { + s = &(g_sensors[id]); + } + // Adjust sample rate if necessary + if (FCGI_RECEIVED(values[SAMPLE_S].flags)) + { + if (sample_s < 0) + { + FCGI_RejectJSON(context, "Negative sampling speed!"); + return; + } + s->sample_us = 1e6*sample_s; + } + + DataFormat format = Data_GetFormat(&(values[FORMAT])); // Begin response @@ -360,6 +389,7 @@ void Sensor_Handler(FCGIContext *context, char * params) // Finish response Sensor_EndResponse(context, s, format); + } /** diff --git a/server/sensor.h b/server/sensor.h index 5cdfbaa..bb2fb9e 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -7,6 +7,7 @@ #define _SENSOR_H #include "data.h" +#include "device.h" /** @@ -18,6 +19,7 @@ extern int g_num_sensors; // in sensor.c /** Structure to define the warning and error thresholds of the sensors **/ +//TODO: Replace with a call to an appropriate "Sanity" function? (see the actuator code) typedef struct { /** Maximum safe value **/ @@ -30,13 +32,6 @@ typedef struct double min_warn; } SensorThreshold; -/** 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 { @@ -60,6 +55,11 @@ typedef struct const char * name; /** Thresholds on the sensor **/ SensorThreshold thresholds; + /** Sampling rate **/ + int sample_us; + /** Averages per DataPoint **/ + int averages; + } Sensor;