simple test function
[matches/MCTX3420.git] / server / actuator.c
index ff51732..3e26e37 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"
 
-/** 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);
 }
 
 /**
@@ -107,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?
@@ -139,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);
 }
 
@@ -165,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");
@@ -186,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)
@@ -200,8 +269,6 @@ void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat forma
 }
 
 
-
-
 /**
  * Handle a request for an Actuator
  * @param context - FCGI context
@@ -211,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}
@@ -230,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,
@@ -245,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;
 }

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