X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=server%2Factuator.c;h=3e26e376e349a7f6dae5c9d09a81b47934f3db19;hb=83a3a266ff3cfecea7a6275924f3bdd15dfe6436;hp=3c09d0558ede9825ca213e4c06d803ae9e5cae79;hpb=46c219c69676ea4e6f467692b7db6e48a708ab80;p=matches%2FMCTX3420.git diff --git a/server/actuator.c b/server/actuator.c index 3c09d05..3e26e37 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -1,89 +1,141 @@ /** * @file actuator.c - * @purpose Implementation of Actuator related functionality + * @brief Implementation of Actuator related functionality */ #include "actuator.h" -#include "control.h" #include "options.h" +// Files containing GPIO and PWM definitions +#include "bbb_pin.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 +/** Number of actuators **/ +int g_num_actuators = 0; + +/** Array of Actuators (global to this file) initialised by Actuator_Init **/ +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 */ -void Actuator_Start(Actuator * a, const char * experiment_name) +int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity) { - // Set filename - char filename[BUFSIZ]; - if (sprintf(filename, "%s_a%d", experiment_name, a->id) >= BUFSIZ) + if (++g_num_actuators > ACTUATORS_MAX) { - Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); + Fatal("Too many sensors; Increase ACTUATORS_MAX from %d in actuator.h and recompile", ACTUATORS_MAX); } - - 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)); + 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; } + /** - * Stop an Actuator - * @param s - The Actuator to stop + * One off initialisation of *all* Actuators */ -void Actuator_Stop(Actuator * a) +#include "actuators/ledtest.h" +#include "actuators/filetest.h" +void Actuator_Init() { - // 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 - + //Actuator_Add("ledtest",0, Ledtest_Set, NULL,NULL,NULL); + Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity); } /** - * Stop all Actuators + * Sets the actuator to the desired mode. No checks are + * done to see if setting to the desired mode will conflict with + * the current mode - the caller must guarantee this itself. + * @param a The actuator whose mode is to be changed + * @param mode The mode to be changed to + * @param arg An argument specific to the mode to be set. + * e.g for CONTROL_START it represents the experiment name. */ -void Actuator_StopAll() +void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) { - for (int i = 0; i < NUMACTUATORS; ++i) - Actuator_Stop(g_actuators+i); + switch (mode) + { + case CONTROL_START: + { + char filename[BUFSIZ]; + const char *experiment_name = (const char*) arg; + + if (snprintf(filename, BUFSIZ, "%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); + } + case CONTROL_RESUME: //Case fallthrough; no break before + { + int ret; + a->activated = true; // Don't forget this + a->control_changed = false; + + ret = pthread_create(&(a->thread), NULL, Actuator_Loop, (void*)(a)); + if (ret != 0) + { + Fatal("Failed to create Actuator_Loop for Actuator %d", a->id); + } + + Log(LOGDEBUG, "Resuming actuator %d", a->id); + } + break; + + case CONTROL_EMERGENCY: //TODO add proper case for emergency + case CONTROL_PAUSE: + a->activated = false; + Actuator_SetControl(a, NULL); + pthread_join(a->thread, NULL); // Wait for thread to exit + + Log(LOGDEBUG, "Paused actuator %d", a->id); + break; + + break; + case CONTROL_STOP: + if (a->activated) //May have been paused before + { + a->activated = false; + Actuator_SetControl(a, NULL); + pthread_join(a->thread, NULL); // Wait for thread to exit + } + Data_Close(&(a->data_file)); // Close DataFile + + Log(LOGDEBUG, "Stopped actuator %d", a->id); + break; + default: + Fatal("Unknown control mode: %d", mode); + } } /** - * Start all Actuators + * Sets all actuators to the desired mode. + * @see Actuator_SetMode for more information. + * @param mode The mode to be changed to + * @param arg An argument specific to the mode to be set. */ -void Actuator_StartAll(const char * experiment_name) +void Actuator_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMACTUATORS; ++i) - Actuator_Start(g_actuators+i, experiment_name); + for (int i = 0; i < ACTUATORS_MAX; i++) + Actuator_SetMode(&g_actuators[i], mode, arg); } /** @@ -108,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? @@ -140,23 +208,21 @@ void Actuator_SetControl(Actuator * a, ActuatorControl * c) */ 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) + if (a->sanity != NULL && !a->sanity(a->user_id, value)) { - case ACTUATOR_TEST0: - break; - case ACTUATOR_TEST1: - break; + //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); } - Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value); - - // Record the value + // Set time stamp + struct timeval t; + gettimeofday(&t, NULL); + // Record and save DataPoint + DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), value}; Data_Save(&(a->data_file), &d, 1); } @@ -166,14 +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_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"); @@ -187,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) @@ -201,8 +269,6 @@ void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat forma } - - /** * Handle a request for an Actuator * @param context - FCGI context @@ -212,17 +278,19 @@ void Actuator_Handler(FCGIContext * context, char * params) { struct timeval now; gettimeofday(&now, NULL); - double current_time = TIMEVAL_DIFF(now, g_options.start_time); + 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} @@ -231,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, @@ -246,43 +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])); - if (Control_Lock()) - { - // 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); + if (FCGI_RECEIVED(values[SET].flags)) + { - // Finish response - Actuator_EndResponse(context, id, format); + + 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); - Control_Unlock(); } - else + + // 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, 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) { - FCGI_RejectJSON(context, "Experiment is not running."); + if (strcmp(g_actuators[i].name, name) == 0) + return &(g_actuators[i]); } + return NULL; }