# Makefile for server software
CXX = gcc
-FLAGS = -std=c99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib
+FLAGS = -std=gnu99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib
LIB = -lfcgi -lssl -lcrypto -lpthread -lm -lopencv_highgui -lopencv_core -lopencv_ml -lopencv_imgproc -lldap -lcrypt
OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o image.o bbb_pin.o pin_test.o login.o sensors/sensors.a actuators/actuators.a
RM = rm -f
Actuator_SetValue(a, a->control.start, true);
// Currently does discrete steps after specified time intervals
+
+ struct timespec wait;
+ DOUBLE_TO_TIMEVAL(a->control.stepsize, &wait);
while (!a->control_changed && a->control.steps > 0 && a->activated)
{
- usleep(1e6*(a->control.stepwait));
+ clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
a->control.start += a->control.stepsize;
Actuator_SetValue(a, a->control.start, true);
}
if (a->control_changed)
continue;
- usleep(1e6*(a->control.stepwait));
+ 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,
}
// Set time stamp
- struct timeval t;
- gettimeofday(&t, NULL);
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), a->last_setting.value};
// Record value change
if (record)
*/
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;
char * name = "";
/** Defines required to allow various C standard functions to be used **/
#define _POSIX_C_SOURCE 200809L
-#define _BSD_SOURCE
+//#define _BSD_SOURCE
#define _XOPEN_SOURCE 600
-
+#define _GNU_SOURCE
/** Determine if we're running on the BBB **/
#ifdef __arm__
#define _BBB
/** The current API version **/
#define API_VERSION 0
+//#define REALTIME_VERSION
#include <assert.h>
#include <sys/time.h>
#include <time.h>
+#include <string.h>
#include "log.h"
#include "fastcgi.h"
#include "control.h"
/**Converts a timeval to a double**/
-#define TIMEVAL_TO_DOUBLE(tv) ((tv).tv_sec + 1e-6 * ((tv).tv_usec))
+#define TIMEVAL_TO_DOUBLE(tv) ((tv).tv_sec + 1e-9 * ((tv).tv_nsec))
/**Takes the tv1-tv2 between two timevals and returns the result as a double*/
-#define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-6 * ((tv1).tv_usec - (tv2).tv_usec))
-
+#define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-9 * ((tv1).tv_nsec - (tv2).tv_nsec))
+/** Converts a double time value (in seconds) to a timespec **/
+#define DOUBLE_TO_TIMEVAL(value, tv) { \
+ (tv)->tv_sec = (int)(value); \
+ (tv)->tv_nsec = ((value) - (int)(value))*1e9; \
+ }
extern bool PathExists(const char * path);
extern bool DirExists(const char * path);
-
#endif //_COMMON_H
typedef struct ControlData {
ControlModes current_mode;
pthread_mutex_t mutex;
- struct timeval start_time;
+ struct timespec start_time;
char user_name[31]; // The user who owns the currently running experiment
char experiment_dir[BUFSIZ]; //Directory for experiment
char experiment_name[BUFSIZ];
path, strerror(errno));
ret = "Couldn't create experiment directory.";
} else {
- gettimeofday(&(g_controls.start_time), NULL);
+ clock_gettime(CLOCK_MONOTONIC, &(g_controls.start_time));
}
} else
ret = "Cannot start when not in a stopped state.";
* Gets the start time for the current experiment
* @return the start time
*/
-const struct timeval* Control_GetStartTime() {
+const struct timespec * Control_GetStartTime() {
return &g_controls.start_time;
}
extern const char * Control_GetModeName();
//extern bool Control_Lock();
//extern void Control_Unlock();
-extern const struct timeval* Control_GetStartTime();
+extern const struct timespec* Control_GetStartTime();
#endif
switch (format)
{
case JSON:
- fmt_string = "[%f,%f]";
+ fmt_string = "[%.9f,%f]";
separator = ',';
// For JSON we need an opening bracket
FCGI_PrintRaw("[");
break;
case TSV:
- fmt_string = "%f\t%f";
+ fmt_string = "%.9f\t%f";
separator = '\n';
break;
}
FCGI_BeginJSON(context, STATUS_OK);
FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
+ struct timespec t;
+ t.tv_sec = 0; t.tv_nsec = 0;
+ clock_getres(CLOCK_MONOTONIC, &t);
+ FCGI_JSONDouble("clock_getres", TIMEVAL_TO_DOUBLE(t));
FCGI_JSONLong("api_version", API_VERSION);
FCGI_JSONBool("logged_in", has_control);
FCGI_JSONPair("user_name", has_control ? context->user_name : "");
+
//Sensor and actuator information
if (ident_sensors) {
printf("\t\"module\" : \"%s\"", context->current_module);
FCGI_JSONLong("status", status_code);
//Time and running statistics
- struct timeval now;
- gettimeofday(&now, NULL);
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
FCGI_JSONDouble("start_time", TIMEVAL_TO_DOUBLE(g_options.start_time));
FCGI_JSONDouble("current_time", TIMEVAL_TO_DOUBLE(now));
FCGI_JSONDouble("running_time", TIMEVAL_DIFF(now, g_options.start_time));
*/
void FCGI_JSONDouble(const char *key, double value)
{
- printf(",\r\n\t\"%s\" : %f", key, value);
+ printf(",\r\n\t\"%s\" : %.9f", key, value);
}
/**
if (module_handler)
{
- if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler)
- //if (false) // Testing
+ //if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler)
+ if (false) // Testing
{
if (!FCGI_HasControl(&context, control_key))
{
#include "common.h"
+extern void Image_Init();
extern void Image_Handler(FCGIContext * context, char * params);
+extern void Image_Cleanup();
#endif //_IMAGE_H
#include <signal.h> // for signal handling
+#ifdef REALTIME_VERSION
+#include <time.h>
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#endif //REALTIME_VERSION
+
// --- Variable definitions --- //
Options g_options; // options passed to program through command line arguments
//if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL)
// Fatal("Couldn't get current working directory - %s", strerror(errno));
- gettimeofday(&(g_options.start_time), NULL); // Start time
+ clock_gettime(CLOCK_MONOTONIC, &(g_options.start_time)); // Start time
g_options.auth_method = AUTH_NONE; // Don't use authentication
Log(LOGDEBUG, "Finish cleanup.");
}
+
+#ifdef REALTIME_VERSION
+
+#define MAX_SAFE_STACK (8*1024)
+#define NSEC_PER_SEC (1000000000)
+
+void stack_prefault()
+{
+ unsigned char dummy[MAX_SAFE_STACK];
+ memset(dummy, 0, MAX_SAFE_STACK);
+ return;
+}
+
+bool is_realtime()
+{
+ struct utsname u;
+ bool crit1 = 0;
+ bool crit2 = 0;
+ FILE * f;
+ uname(&u);
+ crit1 = (strcasestr(u.version, "PREEMPT RT") != NULL);
+ if (crit1 && ((f = fopen("/sys/kernel/realtime", "r")) != NULL))
+ {
+ int flag;
+ crit2 = ((fscanf(f, "%d", &flag) == 1) && (flag == 1));
+ fclose(f);
+ }
+ return (crit1 && crit2);
+}
+
+#endif //REALTIME_VERSION
+
/**
* Main entry point; start worker threads, setup signal handling, wait for threads to exit, exit
* @param argc - Num args
*/
int main(int argc, char ** argv)
{
+
// Open log before calling ParseArguments (since ParseArguments may call the Log functions)
openlog("mctxserv", LOG_PID | LOG_PERROR, LOG_USER);
- Log(LOGINFO, "Server started");
ParseArguments(argc, argv); // Setup the g_options structure from program arguments
+ Log(LOGINFO, "Server started");
+
+
+
+ #ifdef REALTIME_VERSION
+
+ if (is_realtime())
+ {
+ Log(LOGDEBUG, "Running under realtime kernel");
+ }
+ else
+ {
+ Fatal("Not running under realtime kernel");
+ }
+ struct sched_param param;
+ param.sched_priority = 49;
+ if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0)
+ Fatal("sched_setscheduler failed - %s", strerror(errno));
+ if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1)
+ Fatal("mlockall failed - %s", strerror(errno));
+ stack_prefault();
+ #endif //REALTIME_VERSION
+
+
+
Sensor_Init();
Actuator_Init();
Pin_Init();
// Try and start things
- /*
+
const char *ret;
if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL)
Fatal("Control_SetMode failed with '%s'", ret);
- */
+
// run request thread in the main thread
FCGI_RequestLoop(NULL);
- /*
+
if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL)
Fatal("Control_SetMode failed with '%s'", ret);
- */
+
//Sensor_StopAll();
//Actuator_StopAll();
/** Determines at what level log messages are shown **/
int verbosity;
/** Time at which program begins to run **/
- struct timeval start_time;
+ struct timespec start_time;
/** Time at which program exits **/
- struct timeval end_time;
+ struct timespec end_time;
/** Whether or not to enable the pin_test module **/
bool enable_pin;
s->init = init; // Set init function
// Start by averaging values taken over a second
- s->sample_us = 1e6;
+ DOUBLE_TO_TIMEVAL(1e-4, &(s->sample_time));
s->averages = 1;
+ s->num_read = 0;
// Set sanity function
s->sanity = sanity;
Fatal("Couldn't init sensor %s", name);
}
-
+ s->current_data.time_stamp = 0;
+ s->current_data.value = 0;
return g_num_sensors;
}
{
Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, NULL);
Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, NULL);
- Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
- Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
- Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+ //Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+ //Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+ //Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
//Sensor_Add("../testing/count.py", 0, Piped_Read, Piped_Init, Piped_Cleanup, 1e50,-1e50,1e50,-1e50);
//Sensor_Add("strain0", STRAIN0, Strain_Read, Strain_Init, 5000,0,5000,0);
//Sensor_Add("strain1", STRAIN1, Strain_Read, Strain_Init, 5000,0,5000,0);
d.value = 0;
bool success = s->read(s->user_id, &(d.value));
- struct timeval t;
- gettimeofday(&t, NULL);
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
d.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());
if (success)
Fatal("Sensor %s (%d,%d) reads unsafe value", s->name, s->id, s->user_id);
}
}
- Data_Save(&(s->data_file), &d, 1); // Record it
+ s->current_data.time_stamp += d.time_stamp;
+ s->current_data.value += d.value;
+
+ if (++(s->num_read) >= s->averages)
+ {
+ s->current_data.time_stamp /= s->averages;
+ s->current_data.value /= s->averages;
+ Data_Save(&(s->data_file), &(s->current_data), 1); // Record it
+ s->num_read = 0;
+ s->current_data.time_stamp = 0;
+ s->current_data.value = 0;
+ }
}
else
Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id);
- usleep(s->sample_us);
+
+ clock_nanosleep(CLOCK_MONOTONIC, 0, &(s->sample_time), NULL);
+
}
// Needed to keep pthreads happy
*/
void Sensor_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;
const char * name = "";
FCGI_RejectJSON(context, "Negative sampling speed!");
return;
}
- s->sample_us = 1e6*sample_s;
+ DOUBLE_TO_TIMEVAL(sample_s, &(s->sample_time));
}
/** Human readable name of the sensor **/
const char * name;
/** Sampling rate **/
- int sample_us;
+ struct timespec sample_time;
/** Number of averages per sample **/
int averages;
case PRES_HIGH1:
{
static const double Vs = 5e3; // In mVs
- static const double Pmin = 0.0;
+ static const double Pmin = 0.0 * PSI_TO_KPA;
static const double Pmax = 150.0 * PSI_TO_KPA;
double Vout = ADC_TO_MVOLTS(adc);
return ((Vout - 0.1*Vs)/(0.8*Vs))*(Pmax - Pmin) + Pmin;
//static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_lock(&mutex);
bool result = false;
- if (ADC_Read(Pressure_GetADC(id), value))
+ int adc = 0;
+ if (ADC_Read(Pressure_GetADC(id), &adc))
{
- *value = Pressure_Callibrate(id, *value);
+ *value = Pressure_Callibrate(id, adc);
result = true;
}
//pthread_mutex_unlock(&mutex);
switch (id)
{
case RESOURCE_CPU_USER:
- *value = TIMEVAL_TO_DOUBLE(usage.ru_utime);
+ *value = usage.ru_utime.tv_sec + 1e-6*usage.ru_utime.tv_usec;
break;
case RESOURCE_CPU_SYS:
- *value = TIMEVAL_TO_DOUBLE(usage.ru_stime);
+ *value = usage.ru_stime.tv_sec + 1e-6*usage.ru_stime.tv_usec;
break;
default:
Log(LOGWARN, "Unknown id %d", id);
default:
Fatal("Unknown StrainID %d", id);
return -1; // Should never happen
- }
+ }
}
/**