Made generalised Data_Handler function to reduce copy-paste.
(So you can query an Actuator for what values it has been set to).
Still a lot of copy-paste going on though.
Also added a few reports while I'm at it.
I suspect I will have to merge this with stuff other people did?
CXX = gcc
FLAGS = -std=c99 -Wall -Werror -pedantic -g
LIB = -lfcgi -lssl -lcrypto -lpthread -lm
-OBJ = log.o control.o data.o fastcgi.o main.o sensor.o
+OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o
RM = rm -f
BIN = server
--- /dev/null
+/**
+ * @file actuator.c
+ * @purpose Implementation of Actuator related functionality
+ */
+
+#include "actuator.h"
+#include "options.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
+ */
+void Actuator_Start(Actuator * a, const char * experiment_name)
+{
+ // Set filename
+ char filename[BUFSIZ];
+ if (sprintf(filename, "%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);
+
+ a->activated = true; // Don't forget this
+
+ a->control_changed = false;
+
+ // Create the thread
+ pthread_create(&(a->thread), NULL, Actuator_Loop, (void*)(a));
+}
+
+/**
+ * Stop an Actuator
+ * @param s - The Actuator to stop
+ */
+void Actuator_Stop(Actuator * a)
+{
+ // 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
+
+}
+
+/**
+ * Stop all Actuators
+ */
+void Actuator_StopAll()
+{
+ for (int i = 0; i < NUMACTUATORS; ++i)
+ Actuator_Stop(g_actuators+i);
+}
+
+/**
+ * Start all Actuators
+ */
+void Actuator_StartAll(const char * experiment_name)
+{
+ for (int i = 0; i < NUMACTUATORS; ++i)
+ Actuator_Start(g_actuators+i, experiment_name);
+}
+
+/**
+ * Actuator control thread
+ * @param arg - Cast to an Actuator*
+ * @returns NULL to keep pthreads happy
+ */
+void * Actuator_Loop(void * arg)
+{
+ Actuator * a = (Actuator*)(arg);
+
+ // Loop until stopped
+ while (a->activated)
+ {
+ pthread_mutex_lock(&(a->mutex));
+ while (!a->control_changed)
+ {
+ pthread_cond_wait(&(a->cond), &(a->mutex));
+ }
+ a->control_changed = false;
+ pthread_mutex_unlock(&(a->mutex));
+ if (!a->activated)
+ break;
+
+ Actuator_SetValue(a, a->control.value);
+ }
+
+ //TODO: Cleanup?
+
+ // Keep pthreads happy
+ return NULL;
+}
+
+/**
+ * Set an Actuators control variable
+ * @param a - Actuator to control
+ * @param c - Control to set to
+ */
+void Actuator_SetControl(Actuator * a, ActuatorControl * c)
+{
+ pthread_mutex_lock(&(a->mutex));
+ if (c != NULL)
+ a->control = *c;
+ a->control_changed = true;
+ pthread_cond_broadcast(&(a->cond));
+ pthread_mutex_unlock(&(a->mutex));
+
+}
+
+/**
+ * Set an Actuator value
+ * @param a - The Actuator
+ * @param value - The value to set
+ */
+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)
+ {
+ case ACTUATOR_TEST0:
+ break;
+ case ACTUATOR_TEST1:
+ break;
+ }
+
+ Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value);
+
+ // Record the value
+ Data_Save(&(a->data_file), &d, 1);
+}
+
+/**
+ * Helper: Begin Actuator response in a given format
+ * @param context - the FCGIContext
+ * @param format - Format
+ * @param id - ID of Actuator
+ */
+void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat format)
+{
+ // Begin response
+ switch (format)
+ {
+ case JSON:
+ FCGI_BeginJSON(context, STATUS_OK);
+ FCGI_JSONLong("id", id);
+ break;
+ default:
+ FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
+ break;
+ }
+}
+
+/**
+ * Helper: End Actuator response in a given format
+ * @param context - the FCGIContext
+ * @param id - ID of the Actuator
+ * @param format - Format
+ */
+void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat format)
+{
+ // End response
+ switch (format)
+ {
+ case JSON:
+ FCGI_EndJSON();
+ break;
+ default:
+ break;
+ }
+}
+
+
+
+
+/**
+ * Handle a request for an Actuator
+ * @param context - FCGI context
+ * @param params - Parameters passed
+ */
+void Actuator_Handler(FCGIContext * context, char * params)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ double current_time = TIMEVAL_DIFF(now, g_options.start_time);
+ int id = 0;
+ double set = 0;
+ 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},
+ {"start_time", &start_time, FCGI_DOUBLE_T},
+ {"end_time", &end_time, FCGI_DOUBLE_T},
+ {"format", &fmt_str, FCGI_STRING_T}
+ };
+
+ // enum to avoid the use of magic numbers
+ typedef enum {
+ ID,
+ SET,
+ START_TIME,
+ END_TIME,
+ FORMAT
+ } ActuatorParams;
+
+ // Fill values appropriately
+ if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
+ {
+ // Error occured; FCGI_RejectJSON already called
+ return;
+ }
+
+ // Get the Actuator identified
+ Actuator * a = NULL;
+ if (id < 0 || id >= NUMACTUATORS)
+ {
+ FCGI_RejectJSON(context, "Invalid Actuator id");
+ return;
+ }
+
+ 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;
+
+ Actuator_SetControl(a, &c);
+ }
+
+ // Print Data
+ Data_Handler(&(a->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time);
+
+ // Finish response
+ Actuator_EndResponse(context, id, format);
+}
--- /dev/null
+/**
+ * @file actuator.h
+ * @purpose Declarations for actuator control
+ */
+
+#ifndef _ACTUATOR_H
+#define _ACTUATOR_H
+
+#include "common.h"
+#include "data.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 2
+
+/** List of actuator ids (should be of size NUMACTUATORS) **/
+typedef enum
+{
+ ACTUATOR_TEST0,
+ ACTUATOR_TEST1
+} 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;
+} ActuatorControl;
+
+typedef struct
+{
+ /** ID number of the actuator **/
+ ActuatorId id;
+ /** Control parameters for the Actuator **/
+ ActuatorControl control;
+ /** Flag indicates if ActuatorControl has been changed **/
+ bool control_changed;
+ /** DataFile to store actuator settings **/
+ DataFile data_file;
+ /** Thread the Actuator is controlled by **/
+ pthread_t thread;
+ /** Mutex around ActuatorControl **/
+ pthread_mutex_t mutex;
+ /** Used to wake up Actuator control thread **/
+ pthread_cond_t cond;
+ /** Indicates whether the Actuator is running **/
+ bool activated;
+
+} Actuator;
+
+extern void Actuator_Init(); // One off initialisation of *all* Actuators
+
+
+extern void Actuator_StartAll(const char * experiment_name); // Start all Actuators
+extern void Actuator_StopAll(); // Stop all Actuators
+
+extern void Actuator_Start(Actuator * a, const char * experiment_name); // Start a Actuator
+extern void Actuator_Stop(Actuator * a); // Stop an Actuator
+
+
+extern void * Actuator_Loop(void * args); // Main loop for a thread that handles an Actuator
+extern void Actuator_SetValue(Actuator * a, double value); // Set an actuator by value
+extern void Actuator_SetControl(Actuator * a, ActuatorControl * c); // Set the control for an Actuator
+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
+
+#endif //_ACTUATOR_H
+
+//EOF
#include "common.h"
#include "control.h"
-const char * g_actuator_names[NUMACTUATORS] = {
- "Pressure regulator", "Solenoid 1"
-};
/**
* Handles control of the actuators.
- */
+ *
void ActuatorHandler(FCGIContext *context, ActuatorId id, const char *set_value) {
char *ptr;
STATUS_ERROR, "Invalid actuator id specified.");
}
}
+*/
/**
* System control handler. This covers control over all aspects of the system.
set_value = value;
}
}
+ Log(LOGDEBUG, "Id %d", id); // to stop compiler complaining for now
if (action == NULL) { //Must have an action
FCGI_RejectJSON(context, "No action specified");
if (set_value == NULL || *set_value == '\0') {
FCGI_RejectJSONEx(context,
STATUS_ERROR, "Set called but no value specified.");
- } else {
- ActuatorHandler(context, id, set_value);
+ } else
+ {
+// ActuatorHandler(context, id, set_value);
}
}
}
#ifndef _CONTROL_H
#define _CONTROL_H
-/** Number of actuators **/
-#define NUMACTUATORS 2
-/** List of actuator ids (should be of size NUMACTUATORS) **/
-typedef enum ActuatorId {
- ACT_PRESSURE,
- ACT_SOLENOID1
-} ActuatorId;
-
-/** Human readable names for the actuator ids **/
-extern const char * g_actuator_names[NUMACTUATORS];
/** ID codes for all the actuators **/
extern void Control_Handler(FCGIContext *context, char *params);
df->filename = NULL;
df->read_file = NULL;
df->write_file = NULL;
+ pthread_mutex_init(&(df->mutex), NULL);
}
/**
df->num_points = 0;
// Set write FILE*
- df->write_file = fopen(filename, "w+");
+ df->write_file = fopen(filename, "wb+");
if (df->write_file == NULL)
{
Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
//NOTE: Opening the same file in read mode gives funny results; fread generally reads less than expected
// The strerror is: "Transport endpoint is not connected"
/*
- fopen(filename, "r");
+ df->read_file = fopen(filename, "rb");
if (df->read_file == NULL)
{
Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
return index;
}
+
+/**
+ * Helper; handle FCGI response that requires data
+ * Should be called first.
+ * @param df - DataFile to access
+ * @param start - Info about start_time param
+ * @param end - Info about end_time param
+ * @param fmt - Info about format param
+ * @param current_time - Current time
+ */
+void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
+{
+ double start_time = *(double*)(start->value);
+ double end_time = *(double*)(end->value);
+
+ if (format == JSON)
+ {
+ FCGI_JSONKey("data");
+ }
+
+ // If a time was specified
+ if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
+ {
+ // Wrap times relative to the current time
+ if (start_time < 0)
+ start_time += current_time;
+ if (end_time < 0)
+ end_time += current_time;
+
+ // Print points by time range
+ Data_PrintByTimes(df, start_time, end_time, format);
+
+ }
+ else // No time was specified; just return a recent set of points
+ {
+ pthread_mutex_lock(&(df->mutex));
+ int start_index = df->num_points-DATA_BUFSIZ;
+ int end_index = df->num_points-1;
+ pthread_mutex_unlock(&(df->mutex));
+
+ // Bounds check
+ if (start_index < 0)
+ start_index = 0;
+ if (end_index < 0)
+ end_index = 0;
+
+ // Print points by indexes
+ Data_PrintByIndexes(df, start_index, end_index, format);
+ }
+
+}
+
+/**
+ * Helper - Convert human readable format string to DataFormat
+ * @param fmt - FCGIValue to use
+ */
+DataFormat Data_GetFormat(FCGIValue * fmt)
+{
+ char * fmt_str = *(char**)(fmt->value);
+ // Check if format type was specified
+ if (FCGI_RECEIVED(fmt->flags))
+ {
+ if (strcmp(fmt_str, "json") == 0)
+ return JSON;
+ else if (strcmp(fmt_str, "tsv") == 0)
+ return TSV;
+ else
+ Log(LOGERR, "Unknown format type \"%s\"", fmt_str);
+ }
+ return JSON;
+}
extern void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format); // Print data between time values
extern int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest); // Find index of DataPoint with the closest timestamp to that given
+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
#include "common.h"
#include "sensor.h"
+#include "actuator.h"
#include "control.h"
#include "options.h"
module_handler = Control_Handler;
} else if (!strcmp("sensors", module)) {
module_handler = Sensor_Handler;
+ } else if (!strcmp("actuators", module)) {
+ module_handler = Actuator_Handler;
}
context.current_module = module;
#include "common.h"
#include "options.h"
#include "sensor.h"
+#include "actuator.h"
// --- Standard headers --- //
#include <signal.h> // for signal handling
}
*/
Sensor_Init();
+ Actuator_Init();
Sensor_StartAll("test");
+ Actuator_StartAll("test");
// run request thread in the main thread
FCGI_RequestLoop(NULL);
Sensor_StopAll();
+ Actuator_StopAll();
Cleanup();
return 0;
{
// Set filename
char filename[BUFSIZ];
- if (sprintf(filename, "%s_%d", experiment_name, s->id) >= BUFSIZ)
+ if (sprintf(filename, "%s_s%d", experiment_name, s->id) >= BUFSIZ)
{
Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
}
case JSON:
FCGI_BeginJSON(context, STATUS_OK);
FCGI_JSONLong("id", id);
- FCGI_JSONKey("data");
break;
default:
FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
return;
}
- // Get Sensor
- Sensor * s = NULL;
// Error checking on sensor id
if (id < 0 || id >= NUMSENSORS)
{
- Log(LOGERR, "Invalid id %d", id);
- }
- else
- {
- s = g_sensors+id;
+ FCGI_RejectJSON(context, "Invalid sensor id");
+ return;
}
+ Sensor * s = g_sensors+id;
- DataFormat format = JSON;
+ DataFormat format = Data_GetFormat(&(values[FORMAT]));
- // Check if format type was specified
- if (FCGI_RECEIVED(values[FORMAT].flags))
- {
- if (strcmp(fmt_str, "json") == 0)
- format = JSON;
- else if (strcmp(fmt_str, "tsv") == 0)
- format = TSV;
- else
- Log(LOGERR, "Unknown format type \"%s\"", fmt_str);
- }
-
-
-
// Begin response
Sensor_BeginResponse(context, id, format);
-
- // If a time was specified
- if ((s != NULL) && (FCGI_RECEIVED(values[START_TIME].flags) || FCGI_RECEIVED(values[END_TIME].flags)))
- {
- // Wrap times relative to the current time
- if (start_time < 0)
- start_time += current_time;
- if (end_time < 0)
- end_time += current_time;
-
- // Print points by time range
- Data_PrintByTimes(&(s->data_file), start_time, end_time, format);
- }
- else if (s != NULL) // No time was specified; just return a recent set of points
- {
- pthread_mutex_lock(&(s->data_file.mutex));
- int start_index = s->data_file.num_points-DATA_BUFSIZ;
- int end_index = s->data_file.num_points-1;
- pthread_mutex_unlock(&(s->data_file.mutex));
-
- // Bounds check
- if (start_index < 0)
- start_index = 0;
- if (end_index < 0)
- end_index = 0;
-
- // Print points by indexes
- Log(LOGDEBUG, "Sensor %d file \"%s\" indexes %d->%d", s->id, s->data_file.filename, start_index, end_index);
- Data_PrintByIndexes(&(s->data_file), start_index, end_index, format);
- }
+ // Print Data
+ Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time);
// Finish response
Sensor_EndResponse(context, id, format);
#!/bin/bash
-valgrind --leak-check=full --track-origins=yes ./server
+valgrind --leak-check=full --track-origins=yes --show-reachable=yes ./server