CXX = gcc
FLAGS = -std=c99 -Wall -Werror -pedantic -g
LIB = -lpthread
-OBJ = log.o main.o
+OBJ = log.o sensor.o test_request.o main.o
RM = rm -f
BIN = server
$(BIN) : $(OBJ)
- $(CXX) $(FLAGS) -o $(BIN) $(OBJ)
+ $(CXX) $(FLAGS) -o $(BIN) $(OBJ) $(LIB)
%.o : %.c
--- /dev/null
+/**
+ * @file common.h
+ * @purpose Common header includes
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#define _POSIX_C_SOURCE 200809L
+#define _BSD_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#endif //_COMMON_H
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
+#include "common.h"
//To get around a 'pedantic' C99 rule that you must have at least 1 variadic arg, combine fmt into that.
#define Log(level, ...) LogEx(level, __func__, __VA_ARGS__)
* @file main.c
* @purpose main and its helper functions, signal handling and cleanup functions
*/
+#include "common.h"
-#define _POSIX_C_SOURCE 200809L // For strsignal to work
// --- Standard headers --- //
-#include <stdlib.h>
-#include <stdio.h>
#include <signal.h> // for signal handling
-#include <string.h> // string functions
-#include <pthread.h>
// --- Custom headers --- //
+
#include "log.h"
#include "options.h"
+#include "sensor.h"
// --- Variable definitions --- //
Options g_options; // options passed to program through command line arguments
+Sensor g_sensors[NUMSENSORS]; // sensors array
// --- Function definitions --- //
* Handle a signal
* @param signal - The signal number
*/
+//TODO: Something that gets massively annoying with threads is that you can't predict which one gets the signal
+// There are ways to deal with this, but I can't remember them
void SignalHandler(int signal)
{
// At the moment just always exit.
// Call `exit` so that Cleanup will be called to... clean up.
- Log(LOGWARN, "Got signal %d (%s). Exiting.", sig, strsignal(sig));
- exit(sig);
+ Log(LOGWARN, "Got signal %d (%s). Exiting.", signal, strsignal(signal));
+ exit(signal);
}
/**
*/
int main(int argc, char ** argv)
{
- ParseArguments(argc, argv, &g_options);
+ ParseArguments(argc, argv);
+
+ // start sensor threads
+ for (int i = 0; i < NUMSENSORS; ++i)
+ {
+ Sensor_Init(g_sensors+i, i);
+ pthread_create(&(g_sensors[i].thread), NULL, Sensor_Main, (void*)(g_sensors+i));
+ }
+
+ // run request thread in the main thread
+ Query_Request(NULL); //TODO: Replace with FastCGI code
return 0;
}
--- /dev/null
+/**
+ * @file query.c
+ * @purpose Temporary file to run a test thread that will query a sensors thread
+ * Code will probably be combined with Jeremy's FastCGI API
+ */
+
+
+
+
+
+#include "sensor.h"
+#include "log.h"
+
+static DataPoint buffer[QUERY_BUFSIZ];
+
+/**
+ * Query sensor with id
+ * @param id - The index of the sensor in g_sensors
+ */
+void QuerySensor(int id) //TODO: This code will form the SensorHandler FastCGI function (I think?)
+{
+ Sensor * s = g_sensors+id;
+
+ int amount_read = 0;
+ //CRITICAL SECTION (Don't access file while sensor thread is writing to it!)
+ pthread_mutex_lock(&(s->mutex));
+
+ FILE * file = fopen(s->filename, "rb");
+ if (file == NULL)
+ {
+ Log(LOGWARN, "Couldn't open file \"%s\" mode rb - %s", s->filename, strerror(errno));
+ }
+ else
+ {
+ fseek(file, 0, SEEK_SET);
+ rewind(file);
+ amount_read = fread(&buffer, sizeof(DataPoint), QUERY_BUFSIZ, file);
+ s->read_offset += amount_read;
+ Log(LOGDEBUG, "Read %d data points; offset now at %d", amount_read, s->read_offset);
+
+ fclose(file);
+ }
+
+ pthread_mutex_unlock(&(s->mutex));
+ //End critical section
+
+ // So... we have a buffer
+ // I guess we'll want to JSON it or something?
+ // Just print it out for now
+ for (int i = 0; i < amount_read; ++i)
+ {
+ printf("%f\t%f\n", buffer[i].time, buffer[i].value);
+ }
+
+ // Will want to handle case where there actually wasn't anything new to respond with
+ // (In case we have a sensor that is slower than the rate of jQuery requests)
+ if (amount_read == 0)
+ {
+ Log(LOGWARN, "No data points read from sensor%s file");
+ printf("# No data\n");
+ }
+}
+
+/**
+ * Test function to simulate responding to HTTP requests
+ * @param args - IGNORED (void* required to pass function to pthread_create)
+ * @returns NULL (void* required to pass function to pthread_create)
+ */
+void * Query_Main(void * args)
+{
+ while (true) //TODO: Exit condition
+ {
+
+ for (int i = 0; i < NUMSENSORS; ++i)
+ {
+ printf("# Sensor %d\n", i);
+ QuerySensor(i);
+ printf("\n");
+ }
+ usleep(REQUEST_RATE);
+ }
+}
--- /dev/null
+/**
+ * @file query.h
+ * @purpose Header for query stuff
+ */
+
+#ifndef _QUERY_H
+#define _QUERY_H
+
+#define REQUEST_RATE 2000000 // rate to make requests
+#define QUERY_BUFSIZ 10 // size of query buffer
+
+extern void * Query_Main(void * args);
+
+#endif //QUERY_H
* TODO: Finalise implementation
*/
+
+
#include "sensor.h"
+#include "log.h"
+#include <math.h>
+
+/**
+ * Read a data value from a sensor; block until value is read
+ * @param sensor_id - The ID of the sensor
+ * @returns The current value of the sensor
+ */
+DataPoint GetData(int sensor_id)
+{
+ // switch based on the sensor_id at the moment for testing;
+ // might be able to just directly access ADC from sensor_id?
+ //TODO: Implement for real sensors
+
+ DataPoint d;
+ //TODO: Deal with time stamps properly
+ static int count = 0;
+ d.time = count++;
+ switch (sensor_id)
+ {
+ case SENSOR_TEST0:
+ d.value = count;
+ break;
+ case SENSOR_TEST1:
+ d.value = (float)(rand() % 100) / 100;
+ break;
+ default:
+ Fatal("Unknown sensor id: %d", sensor_id);
+ break;
+ }
+ usleep(100000); // simulate delay in sensor polling
+ return d;
+}
+
+
+/**
+ * Destroy a sensor
+ * @param s - Sensor to destroy
+ */
+void Destroy(Sensor * s)
+{
+ //TODO: Surely we'll need to do something here?
+ // Maybe move the binary file into long term file storage?
+}
+
+
+
+/**
+ * Initialise a sensor
+ * @param s - Sensor to initialise
+ */
+void Sensor_Init(Sensor * s, int id)
+{
+ s->write_index = 0;
+ s->read_offset = 0;
+ s->id = id;
+
+ if (s->id >= pow(10, FILENAMESIZE))
+ {
+ Fatal("Too many sensors! FILENAMESIZE is %d; increase it and recompile.", FILENAMESIZE);
+ }
+ sprintf(s->filename, "%d", s->id);
+ unlink(s->filename); //TODO: Move old files somewhere
+
+ Log(LOGDEBUG, "Initialised sensor %d; binary file is \"%s\"", id, s->filename);
+}
+
/**
* Run the main sensor polling loop
- * @param args - IGNORED (void* required to use the function with pthreads)
+ * @param arg - Cast to Sensor* - Sensor that the thread will handle
* @returns NULL (void* required to use the function with pthreads)
*/
-void * Sensor_Main(void * args)
+void * Sensor_Main(void * arg)
{
-
+ Sensor * s = (Sensor*)(arg);
+
+ while (true) //TODO: Exit condition
+ {
+ // The sensor will write data to a buffer until it is full
+ // Then it will open a file and dump the buffer to the end of it.
+ // Rinse and repeat
+
+ // The reason I've added the buffer is because locks are expensive
+ // But maybe it's better to just write data straight to the file
+ // I'd like to do some tests by changing SENSOR_DATABUFSIZ
+
+ while (s->write_index < SENSOR_DATABUFSIZ)
+ {
+ s->buffer[s->write_index] = GetData(s->id);
+ s->write_index += 1;
+ }
+
+ //Log(LOGDEBUG, "Filled buffer");
+
+ // CRITICAL SECTION (no threads should be able to read/write the file at the same time)
+ pthread_mutex_lock(&(s->mutex));
+
+ // Open binary file and dump buffer into it
+ FILE * file = fopen(s->filename, "wb");
+ if (file == NULL)
+ {
+ Fatal("Couldn't open file \"%s\" mode wb - %s", s->filename, strerror(errno));
+ }
+ fseek(file, 0, SEEK_END);
+ int amount_written = fwrite(s->buffer, sizeof(DataPoint), SENSOR_DATABUFSIZ, file);
+ if (amount_written != SENSOR_DATABUFSIZ)
+ {
+ Fatal("Wrote %d data points and expected to write %d to \"%s\" - %s", amount_written, SENSOR_DATABUFSIZ, strerror(errno));
+ }
+
+ Log(LOGDEBUG, "Wrote %d data points for sensor %d", amount_written, s->id);
+
+ fclose(file);
+
+ pthread_mutex_unlock(&(s->mutex));
+ // End of critical section
+
+ s->write_index = 0; // reset position in buffer
+
+ }
+ return NULL;
}
+
+
#ifndef _SENSOR_H
#define _SENSOR_H
+#include "common.h"
+
+/** Number of data points to keep in sensor buffers **/
+#define SENSOR_DATABUFSIZ 10
+
+/** Number of sensors **/
+#define NUMSENSORS 1
+
+#define FILENAMESIZE 10
+
+/** Structure to represent data recorded by a sensor at an instant in time **/
+typedef struct
+{
+ /** Time at which data was taken **/
+ float time;
+ /** Value of data **/
+ float value;
+} DataPoint;
+
+/** Structure to represent a sensor **/
+typedef struct
+{
+ /** ID number of the sensor **/
+ enum {SENSOR_TEST0=0, SENSOR_TEST1=1} id;
+ /** Buffer to store data from the sensor **/
+ DataPoint buffer[SENSOR_DATABUFSIZ];
+ /** Index of last point written in the data buffer **/
+ int write_index;
+ /** Offset position in binary file for query thread to read from**/
+ int read_offset;
+ /** File to write data into when buffer is full **/
+ char filename[FILENAMESIZE];
+ /** Thread running the sensor **/
+ pthread_t thread;
+ /** Mutex to protect access to stuff **/
+ pthread_mutex_t mutex;
+
+
+} Sensor;
+
+/** Array of Sensors **/
+extern Sensor g_sensors[];
+
+extern void Sensor_Init(Sensor * s, int id); // Initialise sensor
+extern void * Sensor_Main(void * args); // main loop for sensor thread; pass a Sensor* cast to void*
-extern void * Sensor_Main(void * args); // main loop for sensor thread
-
#endif //_SENSOR_H