X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=server%2Factuator.c;h=771631849dd7d0f4621a73cffa11f15863fa3f36;hb=HEAD;hp=40f9b46f292a7687d5abf8a6a357eb5f52f35360;hpb=0ef0945e8d83258400fabc61fa81725c5e8e533f;p=matches%2FMCTX3420.git diff --git a/server/actuator.c b/server/actuator.c index 40f9b46..7716318 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -8,34 +8,82 @@ // Files containing GPIO and PWM definitions #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 user_id - Caller specified ID to be associated with this actuator + * @param set - Function to call whenever the actuator should be set + * @param init - Function to call to initialise the actuator (may be NULL) + * @param cleanup - Function to call to deinitialise the actuator (may be NULL) + * @param sanity - Function to call to check that a user specified value is sane (may be NULL) + * @param initial_value - The initial value to set the actuator to + * @returns Number of actuators added so far + */ +int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity, double initial_value) +{ + 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 + + a->sanity = sanity; + a->cleanup = cleanup; + pthread_mutex_init(&(a->mutex), NULL); + + if (init != NULL) + { + if (!init(name, user_id)) + Fatal("Couldn't initialise actuator %s", name); + } + + Actuator_SetValue(a, initial_value, false); + + 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 + * Initialisation of *all* Actuators */ +#include "actuators/pregulator.h" +#include "actuators/relays.h" void Actuator_Init() { - for (int i = 0; i < NUMACTUATORS; ++i) + //Actuator_Add("ledtest",0, Ledtest_Set, NULL,NULL,NULL); + //Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity, 0); + Actuator_Add("pregulator", 0, Pregulator_Set, Pregulator_Init, Pregulator_Cleanup, Pregulator_Sanity, 0); + Actuator_Add("can_select", RELAY_CANSELECT, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0); + Actuator_Add("can_enable", RELAY_CANENABLE, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0); + Actuator_Add("main_pressure", RELAY_MAIN, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 1); +} + +/** + * Deinitialise actuators + */ +void Actuator_Cleanup() +{ + for (int i = 0; i < g_num_actuators; ++i) { - g_actuators[i].id = i; - Data_Init(&(g_actuators[i].data_file)); - pthread_mutex_init(&(g_actuators[i].mutex), NULL); + Actuator * a = g_actuators+i; + if (a->cleanup != NULL) + a->cleanup(a->user_id); } - - // Initialise pins used - GPIO_Export(GPIO1_16); - PWM_Export(EHRPWM0A); - PWM_Export(EHRPWM0B); - + g_num_actuators = 0; } + /** * Sets the actuator to the desired mode. No checks are * done to see if setting to the desired mode will conflict with @@ -51,12 +99,17 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) { case CONTROL_START: { + // Set filename char filename[BUFSIZ]; - const char *experiment_name = (const char*) arg; + const char *experiment_path = (const char*) arg; + int ret; + + ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id); - if (snprintf(filename, BUFSIZ, "%s_a%d", experiment_name, a->id) >= BUFSIZ) + if (ret >= BUFSIZ) { - Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); + Fatal("Experiment path \"%s\" too long (%d, limit %d)", + experiment_path, ret, BUFSIZ); } Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename); @@ -113,8 +166,12 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) */ void Actuator_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMACTUATORS; i++) + if (mode == CONTROL_START) + Actuator_Init(); + for (int i = 0; i < g_num_actuators; i++) Actuator_SetMode(&g_actuators[i], mode, arg); + if (mode == CONTROL_STOP) + Actuator_Cleanup(); } /** @@ -139,7 +196,28 @@ void * Actuator_Loop(void * arg) if (!a->activated) break; - Actuator_SetValue(a, a->control.value); + Actuator_SetValue(a, a->control.start, true); + // Currently does discrete steps after specified time intervals + + struct timespec wait; + DOUBLE_TO_TIMEVAL(a->control.stepwait, &wait); + while (!a->control_changed && a->control.steps > 0 && a->activated) + { + clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL); + a->control.start += a->control.stepsize; + Actuator_SetValue(a, a->control.start, true); + + a->control.steps--; + } + if (a->control_changed) + continue; + clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL); + + //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? @@ -168,74 +246,53 @@ void Actuator_SetControl(Actuator * a, ActuatorControl * c) * Set an Actuator value * @param a - The Actuator * @param value - The value to set + * @param record - Whether or not to record the value to the Actuator's DataFile. */ -void Actuator_SetValue(Actuator * a, double value) +void Actuator_SetValue(Actuator * a, double value, bool record) { - // Set time stamp - struct timeval t; - gettimeofday(&t, NULL); - - DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), value}; - //TODO: Set actuator - switch (a->id) + if (a->sanity != NULL && !a->sanity(a->user_id, value)) { - 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; - } + //ARE YOU INSANE? + Log(LOGERR,"Insane value %lf for actuator %s", value, a->name); + return; + } + 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 - Data_Save(&(a->data_file), &d, 1); + // Set time stamp + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), a->last_setting.value}; + // Record value change + if (record) + { + d.time_stamp -= 1e-6; + Data_Save(&(a->data_file), &d, 1); + d.value = value; + d.time_stamp += 1e-6; + Data_Save(&(a->data_file), &d, 1); + } + a->last_setting = d; } /** * Helper: Begin Actuator response in a given format * @param context - the FCGIContext + * @param a - the actuator to begin a response for * @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"); @@ -246,10 +303,10 @@ void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat for /** * Helper: End Actuator response in a given format * @param context - the FCGIContext - * @param id - ID of the Actuator + * @param a - the actuator to end a response for * @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) @@ -270,19 +327,21 @@ void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat forma */ void Actuator_Handler(FCGIContext * context, char * params) { - struct timeval now; - gettimeofday(&now, NULL); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); 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} @@ -291,6 +350,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, @@ -306,34 +366,110 @@ 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 >= g_num_actuators) { 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, 4); + } + // 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; +} + +/** + * Returns the last DataPoint that is currently available. + * @param id - The actuator ID for which to retrieve data from + * @return The last DataPoint + */ +DataPoint Actuator_LastData(int id) +{ + Actuator * a = &(g_actuators[id]); + return a->last_setting; }