Automatic commit of irc logs
[matches/MCTX3420.git] / server / actuator.c
index 9c21eb7..7716318 100644 (file)
 /**
  * @file actuator.c
- * @purpose Implementation of Actuator related functionality
+ * @brief Implementation of Actuator related functionality
  */
 
 #include "actuator.h"
 #include "options.h"
+// 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", "actuator_test1"
-};
 
 /**
- * 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)
-       {
-               g_actuators[i].id = i;
-               Data_Init(&(g_actuators[i].data_file));
-               pthread_mutex_init(&(g_actuators[i].mutex), NULL);
-       }
+       //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);
 }
 
 /**
- * Start an Actuator
- * @param a - The Actuator to start
- * @param experiment_name - Prepended to DataFile filename
+ * Deinitialise actuators
  */
-void Actuator_Start(Actuator * a, const char * experiment_name)
+void Actuator_Cleanup()
 {
-       // Set filename
-       char filename[BUFSIZ];
-       if (sprintf(filename, "%s_a%d", experiment_name, a->id) >= BUFSIZ)
+       for (int i = 0; i < g_num_actuators; ++i)
        {
-               Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
+               Actuator * a = g_actuators+i;
+               if (a->cleanup != NULL)
+                       a->cleanup(a->user_id);
        }
-
-       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));
+       g_num_actuators = 0;
 }
 
+
 /**
- * Stop an Actuator
- * @param s - The Actuator to stop
+ * 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_Stop(Actuator * a)
+void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
 {
-       // 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
+       switch (mode)
+       {
+               case CONTROL_START:
+                       {
+                               // Set filename
+                               char filename[BUFSIZ];
+                               const char *experiment_path = (const char*) arg;
+                               int ret;
+
+                               ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id);
+
+                               if (ret >= BUFSIZ) 
+                               {
+                                       Fatal("Experiment path \"%s\" too long (%d, limit %d)",
+                                                       experiment_path, ret, 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);
+                               }
 
-/**
- * Stop all Actuators
- */
-void Actuator_StopAll()
-{
-       for (int i = 0; i < NUMACTUATORS; ++i)
-               Actuator_Stop(g_actuators+i);
+                               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);
+       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();
 }
 
 /**
@@ -107,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?
@@ -136,56 +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, g_options.start_time), value};
-       //TODO: Set actuator
-       switch (a->id)
+       if (a->sanity != NULL && !a->sanity(a->user_id, value))
        {
-               case ACTUATOR_TEST0:                    //LED actuator test code, should blink onboard LED next to Ethernet port
-                       FILE *LEDHandle = NULL;         //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds-using-c/
-                       char *LEDBrightness = "/sys/class/leds/beaglebone\:green\:usr0/brightness";
-                       if(value == 1) {
-                               if((LEDHandle = fopen(LEDBrightness, "r+")) != NULL) {
-                                       fwrite("1", sizeof(char), 1, LEDHandle);
-                                       fclose(LEDHandle);
-                               }
-                       else if(value == 0) {
-                               if((LEDHandle = fopen(LEDBrightness, "r+")) != NULL) {
-                                       fwrite("0", sizeof(char), 1, LEDHandle);
-                                       fclose(LEDHandle);
-                       }
-                       else perror("Pin value should be 1 or 0");
-                       break;
-               case ACTUATOR_TEST1:
-                       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_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");
@@ -196,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)
@@ -213,8 +320,6 @@ void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat forma
 }
 
 
-
-
 /**
  * Handle a request for an Actuator
  * @param context - FCGI context
@@ -222,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);
-       double current_time = TIMEVAL_DIFF(now, g_options.start_time);
+       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_LONG_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}
@@ -243,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,
@@ -258,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;
 }

UCC git Repository :: git.ucc.asn.au