Merge remote-tracking branch 'upstream/master'
authorCallum <callum@callum-ubuntu.(none)>
Fri, 13 Sep 2013 04:37:04 +0000 (12:37 +0800)
committerCallum <callum@callum-ubuntu.(none)>
Fri, 13 Sep 2013 04:37:04 +0000 (12:37 +0800)
Conflicts:
server/sensor.c
server/sensor.h

16 files changed:
irc/log
server/Makefile
server/common.h
server/data.c [new file with mode: 0644]
server/data.h [new file with mode: 0644]
server/fastcgi.c
server/fastcgi.h
server/main.c
server/run.sh
server/sensor.c [deleted file]
server/sensor.h [deleted file]
server/thread.c [deleted file]
server/thread.h [deleted file]
server/valgrind.sh
testing/qunit/index.html
testing/qunit/unit-tests.js

diff --git a/irc/log b/irc/log
index de5919d..9e4c5e8 100644 (file)
--- a/irc/log
+++ b/irc/log
 19:32 < jtanx> I've moved the 'current_time', 'start_time' and 'running_time' to the identify module instead of sticking it on every json response, because it's probably not information that will be required most of the time
 19:37 < jtanx> actually, we may have to rethink those, especially if we introduce start/stop control
 20:38 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 23.0.1/20130814063812]"]
+--- Day changed Thu Sep 12 2013
+08:49 -!- MctxBot [[email protected]] has quit [Connection reset by peer]
+09:16 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+09:36 < sam_moore> I think I have some time after the tutorial today to work on stuff
+09:36 < sam_moore> I take it we have the bbb now?
+09:37 < sam_moore> I'll bring that crappy webcam
+09:37 < sam_moore> Maybe look into sending images as part of the API, see if it's feasable to do it that way
+09:38 < sam_moore> I think we can introduce start/stop control fairly easily
+09:40 < sam_moore> "Mush Mush"
+09:40 < sam_moore> Heh
+09:41 < sam_moore> To reduce the load on the client, we might be able to send averaged data points instead of every data point
+09:41 < sam_moore> You specify "averages per point"
+09:42 < sam_moore> So each [time, value] pair is actually an average of X recordings
+09:42 < sam_moore> In fact it's better to send [mean_time, mean_value, std_dev]
+09:42 < sam_moore> The std_dev gives you an idea of noise and/or if the value is changing a lot
+09:43 < sam_moore> And of course you can always specify a single average and it becomes equivelant to what we currently do
+09:50 < jtanx> yeah
+09:50 < jtanx> I'm free after the tute
+09:50 < jtanx> I'm bringing in a router to make it easier
+09:50 < jtanx> to work on the bbb together
+09:51 < jtanx> yesterday it was so painful trying to get shit working 
+09:51 < jtanx> dhcp issues, dns issues, internet connection sharing issues...
+09:51 < jtanx> (mostly the fault of my own computer though)
+09:51 < jtanx> btw I'm working on adding an extra function to FCGI to makeit easier to parse parameters
+09:51 < jtanx> (hoepfully)
+09:55 < jtanx> gtg see you at the tute
+09:55 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 23.0.1/20130814063812]"]
+18:24 -!- Callum [[email protected]] has joined #mctxuwa_softdev
+18:26 < Callum> would be easier to talk here...if everyone was on
+18:35 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+18:41 < jtanx> did you bring the bbb home?
+18:43 < sam_moore> Yep
+18:44 < sam_moore> I'll put it back tomorrow morning
+18:49 < jtanx> ok
+19:15 -!- Callum_ [[email protected]] has joined #mctxuwa_softdev
+19:28 -!- Callum [[email protected]] has quit [Ping timeout]
+19:47 < jtanx> I'm so tempted to use bitflags for that request function
+19:52 < jtanx> one thing that I need to do is to sanitise json string fields
+20:18 < sam_moore> Sanitise? As in "make sane" or "make clean"?
+20:18 < sam_moore> Um... if you think bitflags are necessary, I fully support you :P
+20:19 < jtanx> as in
+20:19 < jtanx> If I allow FCGI_JSONPair to also be passed a format string
+20:19 < jtanx> then having stuff like \ or " or newline characters for eg in the string
+20:20 < jtanx> is not a good idea
+20:20 < sam_moore> Hmm
+20:20 < jtanx> it's sorta like this:
+20:20 < sam_moore> Well, writing the function to print out data (either as TSV or JSON)
+20:20 < sam_moore> I've only been using FCGI_PrintRaw
+20:20 < jtanx> yeah
+20:20 < jtanx> but right now, you're logging messages in Server_handler
+20:20 < jtanx> about invalid values
+20:21 < sam_moore> Yes, I'm not sure how to deal with that
+20:21 < jtanx> Sensor_handler*
+20:21 < jtanx> so
+20:21 < jtanx> eg Log(LOGERR, "Require \"all\" or an integer value: %s = %s", key, value);
+20:21 < sam_moore> Yes
+20:21 < jtanx> if I wanted to send that back to the client in FCGI_RejectJSON
+20:21 < jtanx> there's the potential (...) that the user could hvae included a " in the value
+20:22 < jtanx> so if you return it as part of your json response, then suddenly it's broken
+20:22 < sam_moore> Oh, that sucks
+20:23 < sam_moore> Perhaps treat Log messages seperately from JSON responses?
+20:23 < jtanx> eg:  "description":" required an id, got 'blah-stray"'"
+20:23 < jtanx> yeah
+20:24 < jtanx> What I could do is just replace any special characters with space 
+20:24 < jtanx> and return that in description
+20:24 < jtanx> but keep the actual value in log
+20:24 < sam_moore> Maybe
+20:24 < sam_moore> You could also call FCGI_RejectJSON as soon as you get a special character
+20:25 < sam_moore> Since we shouldn't have any key/values that use them
+20:25 < sam_moore> (I think :P)
+20:25 < jtanx> lol
+20:25 < sam_moore> Whatever seems best
+20:25 < jtanx> let me think about it some more
+20:25 < sam_moore> Yeah, fair enough
+20:25 < sam_moore> Um, another thing (sorry) is error messages that you might get independent of any request
+20:26 < sam_moore> Like you suddenly have an I/O error on a file
+20:26 < sam_moore> Currently we handle that with Fatal
+20:26 < sam_moore> Which it would be convenient to keep; but instead of terminating the whole program, we need to notify the client somehow
+20:26 < jtanx> that won't happen until the next request
+20:27 < jtanx> what you could do is keep it exiting
+20:27 < jtanx> when that happens, nginx sends 502 bad gateway
+20:27 < jtanx> then if that's detected, send a link to the log file
+20:27 < sam_moore> Yeah, that seems good
+20:27 < jtanx> and a method to restart the software
+20:28 < sam_moore> Do we want to notify the client of non-fatal errors or warnings though?
+20:28 < sam_moore> Hmm
+20:29 < jtanx> hmm
+20:29 < jtanx> probably a good idea
+20:29 < jtanx> but how
+20:29 < sam_moore> The client could periodically request the log file and deal with messages?
+20:30 < jtanx> It could just display it I guess
+20:30 < jtanx> onus on the user to take action if required
+20:30 < sam_moore> Sure, an alert might be nice though
+20:30 < sam_moore> Perhaps have an extra log file that just has the most recent message in it (with a timestamp)
+20:31 < jtanx> you could perform a regex on the text and highlight lines with warning or something
+20:31 < sam_moore> The client just periodically looks at that and if it's a new message can alert
+20:31 < sam_moore> Yeah that would work, as long as the file doesn't get too big
+20:31 < sam_moore> It probably won't
+20:31 < jtanx> well you could autoscroll it anyway
+20:32 < jtanx> but ok maybe that's getting a bit ahead of ourselves
+20:32 < sam_moore> Yeah, sure
+20:32 < sam_moore> I'll get back to rewriting 90% of the sensor code :P
+20:32 < jtanx> haha
+20:32 < jtanx> currently, I've got
+20:32 < jtanx> bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count);
+20:32 < jtanx> (any better suggestion for FCGIValue?)
+20:32 < jtanx> (the name that is)
+20:33 < jtanx> with 
+20:33 < sam_moore> That's probably fine
+20:33 < jtanx> typedef struct FCGIValue {
+20:33 < jtanx>         const char *key;
+20:33 < jtanx>         void *value;
+20:33 < jtanx>         unsigned flags;
+20:33 < jtanx> } FCGIValue;
+20:33 < jtanx> you then do
+20:33 < jtanx> something like:
+20:33 < jtanx> FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_REQUIRED(FCGI_BOOL_T)},
+20:33 < jtanx>                                          {"actuators", &ident_actuators, FCGI_BOOL_T}};
+20:33 < sam_moore> Looks good
+20:33 < jtanx> although I'm open to suggestions on those type names too
+20:34 < jtanx> if you really have to
+20:34 < jtanx> you can do something like FCGI_RECEIVED(values[0].flags)
+20:34 < jtanx> to check if you received the argument or not
+20:34 < sam_moore> If it's not too hard to implement, then OK
+20:35 < jtanx> ?
+20:35 < sam_moore> That looks reasonably nice
+20:35 < jtanx> the #defines look less so:
+20:35 < jtanx> #define FCGI_PARAM_REQUIRED (1 << 0)
+20:35 < jtanx> #define FCGI_PARAM_RECEIVED (1 << 1)
+20:35 < jtanx> #define FCGI_BOOL_T (1 << 2)
+20:35 < jtanx> #define FCGI_LONG_T (1 << 3)
+20:35 < jtanx> #define FCGI_DOUBLE_T (1 << 4)
+20:35 < jtanx> #define FCGI_STRING_T (1 << 5)
+20:35 < jtanx> #define FCGI_REQUIRED(x) ((x) | FCGI_PARAM_REQUIRED)
+20:35 < jtanx> #define FCGI_IS_REQUIRED(x) ((x) & FCGI_PARAM_REQUIRED)
+20:35 < jtanx> #define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED)
+20:35 < jtanx> #define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED))
+20:35 < sam_moore> In fact it's similar to how you use select(2)
+20:35 < sam_moore> Wow, macros
+20:35 < jtanx> :P
+20:35 < jtanx> makes life easier
+20:36 < sam_moore> Alright, this is C
+20:36 < sam_moore> Yeah, FCGI_RECEIVED(values[i].flags) looks nice (from the point of view of actually using it anyway)
+20:37 < jtanx> yeah it's ok
+20:37 < jtanx> the only stickler is that hardcoded index
+20:37 < jtanx> can't really do much about that though
+20:37 < sam_moore> I can put an enum in the function
+20:38 < jtanx> if you want; it might be more work to maintain the enum than anything though
+20:39 < jtanx> but yeah, essentially if you require a parameter, you surround the type in FCGI_REQUIRED()
+20:39 < jtanx> hopefully that part was clear
+20:39 < sam_moore> Yep, I'm happy with that
+20:39 < jtanx> cool
+20:43 < jtanx> for the bool value, what behaviour do you think is better
+20:43 < jtanx> always set the value to true, or invert the current value
+20:44 < jtanx> right now it's set to always be true
+21:06 < sam_moore> Um... always set to true?
+21:09 < jtanx> yeah
+21:29 < jtanx> oh
+21:30 < jtanx> well I didn't need to escape after all
+21:30 < jtanx> If you do something like this: http://mctx.us.to:8080/api?afa%22%22%22%22
+21:30 < jtanx> it gets url encoded
+21:30 < jtanx> http://mctx.us.to:8080/api?afa""""
+21:30 < jtanx> I wonder if that's guaranteed
+21:32 < jtanx> well, apparently it's not because IE doesn't escape it for you
+21:32 < jtanx> so I did need to escape 
+21:33 < sam_moore> There's no escape
+21:46 < jtanx> wait wat
+21:59 < sam_moore> (It's a pun)
+22:00 < sam_moore> So I've made a DataFile structure to isolate it a bit more from the higher level Sensor related stuff
+22:00 < sam_moore> Hopefully things make more sense
+22:00 < sam_moore> I put in 2 seperate FILE* as well
+22:00 < sam_moore> And got rid of the confusing "fill a buffer then dump the buffer to a file" loop
+22:01 < sam_moore> Now it just writes each point to a file
+22:01 < sam_moore> Hopefully it's thread safe enough
+22:01 < sam_moore> Now... I just need to simplify the Sensor_Handler function...
+22:02 < jtanx> > (It's a pun) 
+22:02  * jtanx facepalm
+22:03 < jtanx> hoho
+22:03 < jtanx> ok 
+22:03 < jtanx> I'll submit what I've done so far to git
+22:08 < jtanx> callum
+22:08 < jtanx> that code you submitted
+22:08 < jtanx> I don't think it's right
+22:08 -!- Irssi: #mctxuwa_softdev: Total of 3 nicks [0 ops, 0 halfops, 0 voices, 3 normal]
+22:09 < jtanx> the stuff with g_sensor_names, that's the human readable name of each sensor
+22:10 < sam_moore> I've changed the name and how the sanity check gets called, but I've left the body unimplemented for now
+22:12 < jtanx> it's with the pull request he submitted ~30mins ago
+22:12 < sam_moore> Oh, ok
+22:13 < sam_moore> Good thing I made a new branch for my changes
+22:14 < jtanx> the pull request isn't merged yet
+22:15 < sam_moore> Yeah, I don't think we should merge that, sorry Callum
+22:17 < jtanx> ok just submitted the pull request
+22:17 < sam_moore> I never put documentation on g_sensor_names I guess, sorry
+22:17 < jtanx> I think I did though
+22:17 < jtanx> but in the header file
+22:18 < jtanx> which is annoying :/
+22:18 < jtanx> (as in there's more than one spot to put documentation)
+22:19 < sam_moore> Yeah... does one overwrite another?
+22:19 < sam_moore> I'll just copy paste the same thing into the .c file
+22:19 < jtanx> No idea how doxygen handles that
+22:20 < sam_moore> I wonder how much fun I'll have merging these branches...
+22:20 < jtanx> hehe
+22:20 < jtanx> I'm not really sure if FCGI_ParseRequest will be that helpful or not
+22:20 < jtanx> if you find it doesn't really help then we can scrap it
+22:21 < sam_moore> Well it makes sense to seperate the code for getting key,value pairs from the code that does stuff with them
+22:23 < jtanx> I guess
+22:23 < sam_moore> I've basically moved a lot of functionality from sensor.c into a new file data.c under functions with the prefix "Data_"
+22:24 < jtanx> ok
+22:24 < sam_moore> data.c doesn't care anything about what the data is or where it came from, it just wraps around saving/reading/selecting ranges of DataPoints
+22:25 < jtanx> sounds like a good idea
+22:25 < jtanx> it might be of use in actuator code I guess
+22:25 < sam_moore> Yeah, there's a DataFile structure that basically wraps around the binary file
+22:25 < sam_moore> Yes, I was thinking that too
+22:25 < sam_moore> Because we'll probably want to store DataPoints when an actuator gets changed
+22:26 < jtanx> yeah true
+22:27 < sam_moore> If the idea about using 2 FILE* is right... I don't think we really need any mutexes?
+22:28 < sam_moore> There is an index that gets incremented, but since only one thread can write to it, the worst that can happen is you return one (maybe two at the extreme) less point than actually exists
+22:29 < sam_moore> If you had 2 threads incrementing the index you'd need a mutex though
+22:29 < jtanx> the problem is that setting a value is not guaranteed to be atomic
+22:29 < jtanx>  i think
+22:29 < sam_moore> Yes, sure
+22:29 < jtanx> so while setting and you read it it could be indeterminate
+22:29 < sam_moore> Mmm
+22:30 < jtanx> I think you need mutexes around reading/writing the points_written(?)
+22:30 < jtanx> index
+22:30 < jtanx> but that's it
+22:31 < jtanx> http://stackoverflow.com/questions/54188/are-c-reads-and-writes-of-an-int-atomic
+22:31 < sam_moore> I'm not sure you do if you are writing in one thread and reading in another
+22:31 < sam_moore> Ah, stackoverflow, OK
+22:32 < jtanx> it's actually very architecture specific
+22:32 < sam_moore> Yeah "Yes, no, hmmm, well it depends" is probably right
+22:32 < jtanx> haha
+22:32 < sam_moore> Well... there are no mutexes at the moment, but maybe I should get around to putting them in
+22:32 < sam_moore> I think I had one thread writing and another reading in my parallel programming assignment and got full marks :S
+22:33 < sam_moore> Threads are strange and magical
+22:33 < jtanx> hey well it probably works 99% of the time
+22:34 < sam_moore> Ah, it probably works if the size of the integer is smaller than the bus
+22:34 < sam_moore> smaller or equal
+22:34 < jtanx> too bad linux doesn't have something like InterlockedExchange
+22:35 < sam_moore> I'll add mutexes in later then
+22:35 < jtanx> One thing I liked about C++ was auto locks
+22:36 < jtanx> (you could call some lock function and once that scope ended the lock ended too)
+22:36 < sam_moore> That's nice
+22:40 < jtanx> Ok, that's all I'm probably going to get done today
+22:41 < sam_moore> Yeah, thanks
+22:42 < jtanx> what's with the meeting tomorrow though
+22:43 < sam_moore> I'm busy from 2:00pm until at least 4:00pm (maybe later)
+22:43 < sam_moore> But I think I should be preparing for something before 2:00pm
+22:44 < jtanx> ok, I'll probably come in around 1.30pm so I can work with Callum, and I'll stay on until whatever time
+22:44 < sam_moore> Alright
+22:45 < sam_moore> Check github in the morning, I'll push all my changes
+22:45 < jtanx> ok I'll do that
+22:45 < sam_moore> Hopefully they make sense, otherwise you can change everything back :P
+22:45 < sam_moore> The wonders of git
+22:45 < jtanx> haha I'm sure it'll make sense
+22:46 < jtanx> after staring for a while
+22:46 < sam_moore> Yeah it should be a bit simpler
+22:46 < jtanx> thanks
+22:47 < jtanx> see you tomorrow (probably)
+22:48 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 23.0.1/20130814063812]"]
+23:28 -!- Callum_ [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 23.0.1/20130814063812]"]
index b674ab0..74ba473 100644 (file)
@@ -2,7 +2,7 @@
 CXX = gcc
 FLAGS = -std=c99 -Wall -Werror -pedantic -g
 LIB = -lfcgi -lssl -lcrypto -lpthread -lm
-OBJ = log.o control.o sensor.o fastcgi.o thread.o main.o
+OBJ = log.o control.o data.o fastcgi.o main.o sensor.o
 RM = rm -f
 
 BIN = server
index 1275032..a690149 100644 (file)
 
 #include "log.h"
 #include "fastcgi.h"
-#include "thread.h"
+
+/**Converts a timeval to a double**/
+#define TIMEVAL_TO_DOUBLE(tv) ((tv).tv_sec + 1e-6 * ((tv).tv_usec))
+/**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))
+
 
 #endif //_COMMON_H
diff --git a/server/data.c b/server/data.c
new file mode 100644 (file)
index 0000000..82aadb6
--- /dev/null
@@ -0,0 +1,320 @@
+/**
+ * @file data.c
+ * @purpose Implementation of data handling functions; saving, loading, displaying, selecting.
+ */
+
+#include "data.h"
+#include <assert.h> //TODO: Remove asserts
+
+/**
+ * One off initialisation of DataFile
+ * @param df - The DataFile
+ */
+void Data_Init(DataFile * df)
+{
+       // Everything is NULL
+       df->filename = NULL;
+       df->read_file = NULL;
+       df->write_file = NULL;
+}
+
+/**
+ * Initialise a DataFile from a filename; opens read/write FILE*
+ * @param df - DataFile to initialise
+ * @param filename - Name of file; overwritten if it exists
+ */
+void Data_Open(DataFile * df, const char * filename)
+{
+       assert(filename != NULL);
+       assert(df != NULL);
+
+       // Set the filename
+       df->filename = strdup(filename);
+
+       // Set number of DataPoints
+       df->num_points = 0; 
+
+       // Set write FILE*
+       df->write_file = fopen(filename, "w+");
+       if (df->write_file == NULL)
+       {
+               Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
+       }
+       
+       // Set read FILE*
+       df->read_file = df->write_file;
+
+       //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");
+       if (df->read_file == NULL)
+       {
+               Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
+       }
+       */
+
+}
+
+/**
+ * Close a DataFile
+ * @param df - The DataFile to close
+ */
+void Data_Close(DataFile * df)
+{
+       assert(df != NULL);
+
+       //TODO: Write data to TSV?
+
+       // Clear the FILE*s
+       df->read_file = NULL;
+       df->write_file = NULL;
+
+       fclose(df->write_file);
+
+       // Clear the filename
+       free(df->filename);
+       df->filename = NULL;
+}
+
+/**
+ * Save DataPoints to a DataFile
+ * @param df - The DataFile to save to
+ * @param buffer - Array of DataPoint(s) to save
+ * @param amount - Number of DataPoints in the buffer
+ */
+void Data_Save(DataFile * df, DataPoint * buffer, int amount)
+{
+       pthread_mutex_unlock(&(df->mutex));
+       assert(df != NULL);
+       assert(buffer != NULL);
+       assert(amount >= 0);
+
+       // Go to the end of the file
+       if (fseek(df->write_file, 0, SEEK_END) < 0)
+       {
+               Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
+       }
+
+       // Attempt to write the DataPoints
+       int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->write_file);
+       
+       // Check if the correct number of points were written
+       if (amount_written != amount)
+       {
+               Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
+       }
+
+       // Update number of DataPoints
+       df->num_points += amount_written;
+
+       pthread_mutex_unlock(&(df->mutex));
+       
+}
+
+/**
+ * Read DataPoints from a DataFile
+ * @param df - The DataFile to read from
+ * @param buffer - Array to fill with DataPoints
+ * @param index - Index to start reading at (inclusive)
+ * @param amount - Maximum number of DataPoints to read
+ * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
+ */
+int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
+{
+       pthread_mutex_lock(&(df->mutex));
+
+       assert(df != NULL);
+       assert(buffer != NULL);
+       assert(index >= 0);
+       assert(amount > 0);
+       
+       // If we would read past the end of the file, reduce the amount of points to read
+       
+               if (index + amount > df->num_points)
+               {
+                       Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF (%d)", amount, df->num_points - index, df->num_points);
+                       amount = df->num_points - index;
+               }
+       
+
+       // Go to position in file
+       if (fseek(df->read_file, index*sizeof(DataPoint), SEEK_SET))
+       {
+               Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
+       }
+
+       // Attempt to read the DataPoints
+       int amount_read = fread(buffer, sizeof(DataPoint), amount, df->read_file);
+
+       // Check if correct number of points were read
+       if (amount_read != amount)
+       {
+               Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
+       }
+
+       pthread_mutex_unlock(&(df->mutex));
+       return amount_read;
+}
+
+/**
+ * Print data points between two indexes using a given format
+ * @param df - DataFile to print
+ * @param start_index - Index to start at (inclusive)
+ * @param end_index - Index to end at (inclusive)
+ * @param format - The format to use
+ */
+void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
+{
+       assert(df != NULL);
+       assert(start_index >= 0);
+       assert(end_index >= 0);
+       assert(end_index <= df->num_points-1 || df->num_points == 0);
+
+       const char * fmt_string; // Format for each data point
+       char seperator; // Character used to seperate successive data points
+       
+       // Determine what format string and seperator character to use
+       switch (format)
+       {
+               case JSON:
+                       fmt_string = "[%f,%f]";
+                       seperator = ',';
+                       // For JSON we need an opening bracket
+                       FCGI_PrintRaw("["); 
+                       break;
+               case TSV:
+                       fmt_string = "%f\t%f";
+                       seperator = '\n';
+                       break;
+       }
+
+       DataPoint buffer[DATA_BUFSIZ]; // Buffer
+       // initialise buffer to stop stuff complaining
+       memset(buffer, 0, sizeof(DataPoint)*DATA_BUFSIZ);
+       
+       if (start_index < end_index)
+       {
+
+               int index = start_index;
+               // Repeat until all DataPoints are printed
+               while (index <= end_index)
+               {
+                       // Fill the buffer from the DataFile
+                       int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
+       
+                       // Print all points in the buffer
+                       for (int i = 0; i < amount_read && index <= end_index; ++i)
+                       {
+                               // Print individual DataPoint
+                               FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
+                               
+                               // Last seperator is not required
+                               if (index+1 <= end_index)
+                                       FCGI_PrintRaw("%c", seperator);
+       
+                               // Advance the position in the DataFile
+                               ++index;
+                       }
+               }
+       }
+       
+       switch (format)
+       {
+               case JSON:
+                       // For JSON we need a closing bracket
+                       FCGI_PrintRaw("]"); 
+                       break;
+               default:
+                       break;
+       }
+}
+
+/**
+ * Print data points between two time stamps using a given format.
+ * Prints nothing if the time stamp
+ * @param df - DataFile to print
+ * @param start_time - Time to start from (inclusive)
+ * @param end_time - Time to end at (inclusive)
+ * @param format - The format to use
+ */
+void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
+{
+       assert(df != NULL);
+       assert(start_time >= 0);
+       assert(end_time >= 0);
+       assert(end_time >= start_time);
+       
+       DataPoint closest;
+
+       // Get starting index
+       int start_index = Data_FindByTime(df, start_time, &closest);
+
+       // Start time is greater than most recent time stamp
+       if (start_index >= df->num_points-1)
+       {
+               if (start_index == 0 || closest.time_stamp < start_time)
+               {
+                       Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset
+                       return;
+               }
+       }
+
+       // Get finishing index
+       int end_index = Data_FindByTime(df, end_time, &closest);
+
+       // Print data between the indexes
+       Data_PrintByIndexes(df, start_index, end_index, format);
+}
+
+/**
+ * Get the index of the DataPoint closest to a given time stamp
+ * @param df - DataFile to search
+ * @param time_stamp - The time stamp to search for
+ * @param closest - If not NULL, will be filled with the DataPoint chosen
+ * @returns index of DataPoint with the *closest* time stamp to that given
+ */
+int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
+{
+       assert(df != NULL);
+       assert(time_stamp >= 0);
+       assert(closest != NULL);
+
+       DataPoint tmp; // Current DataPoint in binary search
+
+       int lower = 0; // lower index in binary search
+       pthread_mutex_lock(&(df->mutex));
+               int upper = df->num_points - 1; // upper index in binary search
+       pthread_mutex_unlock(&(df->mutex));
+       int index = 0; // current index in binary search
+
+       // Commence binary search:
+       while (upper - lower > 1)
+       {
+               // Pick midpoint
+               index = lower + ((upper - lower)/2);
+
+               // Look at DataPoint
+               if (Data_Read(df, &tmp, index, 1) != 1)
+               {
+                       Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
+               }
+
+               // Change search interval to either half appropriately
+               if (tmp.time_stamp > time_stamp)
+               {
+                       upper = index;
+               }
+               else if (tmp.time_stamp < time_stamp)
+               {
+                       lower = index;
+               }
+       }
+
+       // Store closest DataPoint
+       if (closest != NULL)
+               *closest = tmp;
+       
+       return index;
+       
+}
diff --git a/server/data.h b/server/data.h
new file mode 100644 (file)
index 0000000..e52dadc
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * @file datapoint.h
+ * @purpose Declaration of data handling functions; saving, loading, displaying, selecting.
+ */
+
+#ifndef _DATAPOINT_H
+#define _DATAPOINT_H
+
+#define DATA_BUFSIZ 10 // Size to use for DataPoint buffers (TODO: Optimise)
+
+
+#include "common.h"
+
+/** Structure to represent a time, value DataPoint **/
+typedef struct
+{
+       /** Time at which data was taken **/
+       double time_stamp; 
+       /** Value of data **/
+       double value;
+} DataPoint;
+
+/** Enum of output format types for DataPoints **/
+typedef enum
+{
+       JSON, // JSON data
+       TSV // Tab seperated vector
+} DataFormat;
+
+/** 
+ * Structure to represent a collection of data. 
+ * All operations involving this structure are thread safe.
+ * NOTE: It is essentially a wrapper around a binary file.
+ */
+typedef struct
+{
+       FILE * read_file; // used for reading
+       FILE * write_file; // used for writing
+       int num_points; // Number of DataPoints in the file
+       char * filename; // Name of the file
+       pthread_mutex_t mutex; // Mutex around num_points
+} DataFile;
+
+
+extern void Data_Init(DataFile * df);  // One off initialisation of DataFile
+extern void Data_Open(DataFile * df, const char * filename); // Open data file
+extern void Data_Close(DataFile * df);
+extern void Data_Save(DataFile * df, DataPoint * buffer, int amount); // Save data to file
+extern int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount); // Retrieve data from file
+extern void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format);  // Print data buffer
+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
+
+#endif //_DATAPOINT_H
index 1bd520b..b58ba79 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <fcgi_stdio.h>
 #include <openssl/sha.h>
+#include <stdarg.h>
 
 #include "common.h"
 #include "sensor.h"
@@ -37,23 +38,32 @@ struct FCGIContext {
  * @param params User specified paramters: [actuators, sensors]
  */ 
 static void IdentifyHandler(FCGIContext *context, char *params) {
-       bool identSensors = false, identActuators = false;
-       const char *key, *value;
+       bool ident_sensors = false, ident_actuators = false;
+       //const char *key, *value;
+
        int i;
 
-       while ((params = FCGI_KeyPair(params, &key, &value))) {
+       FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
+                                        {"actuators", &ident_actuators, FCGI_BOOL_T}};
+
+       if (!FCGI_ParseRequest(context, params, values, 2))
+               return;
+
+       /*while ((params = FCGI_KeyPair(params, &key, &value))) {
                if (!strcmp(key, "sensors")) {
-                       identSensors = !identSensors;
+                       ident_sensors = !ident_sensors;
                } else if (!strcmp(key, "actuators")) {
-                       identActuators = !identActuators;
+                       ident_actuators = !ident_actuators;
                }
-       }
+       }*/
 
        FCGI_BeginJSON(context, STATUS_OK);
        FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
        FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
        FCGI_JSONLong("api_version", API_VERSION);
-       if (identSensors) {
+
+       //Sensor and actuator information
+       if (ident_sensors) {
                FCGI_JSONKey("sensors");
                FCGI_JSONValue("{\n\t\t");
                for (i = 0; i < NUMSENSORS; i++) {
@@ -64,7 +74,7 @@ static void IdentifyHandler(FCGIContext *context, char *params) {
                }
                FCGI_JSONValue("\n\t}");
        }
-       if (identActuators) {
+       if (ident_actuators) {
                FCGI_JSONKey("actuators");
                FCGI_JSONValue("{\n\t\t");
                for (i = 0; i < NUMACTUATORS; i++) {
@@ -189,6 +199,87 @@ char *FCGI_KeyPair(char *in, const char **key, const char **value)
        return ptr;
 }
 
+/**
+ * Aids in parsing request parameters. Expected keys along with their type
+ * and whether or not they're required are provided. This function will then
+ * parse the parameter string to find these keys.
+ * @param context The context to work in
+ * @param params The parameter string to be parsed
+ * @param values An array of FCGIValue's that specify expected keys
+ * @param count The number of elements in 'values'.
+ * @return true If the parameter string was parsed successfully, false otherwise.
+ *         Modes of failure include: Invalid a parsing error on the value,
+ *                                   an unknown key is specified,
+ *                                   a key/value pair is specified more than once, or
+ *                                   not all required keys were present.
+ *         If this function returns false, it is guaranteed that FCGI_RejectJSON
+ *         has already been called with the appropriate description message.
+ */
+bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count)
+{
+       const char *key, *value;
+       char buf[BUFSIZ], *ptr;
+       size_t i;
+       
+       while ((params = FCGI_KeyPair(params, &key, &value))) {
+               for (i = 0; i < count; i++) {
+                       if (!strcmp(key, values[i].key)) {
+                               FCGIValue *val = &values[i];
+
+                               if (FCGI_RECEIVED(val->flags)) {
+                                       snprintf(buf, BUFSIZ, "Value already specified for '%s'.", key);
+                                       FCGI_RejectJSON(context, buf);
+                                       return false;
+                               }
+                               val->flags |= FCGI_PARAM_RECEIVED;
+
+                               switch(FCGI_TYPE(val->flags)) {
+                                       case FCGI_BOOL_T:
+                                               *((bool*) val->value) = true;
+                                               break;
+                                       case FCGI_LONG_T:
+                                               *((long*) val->value) = strtol(value, &ptr, 10);
+                                               if (!*value || *ptr) {
+                                                       snprintf(buf, BUFSIZ, "Expected int for '%s' but got '%s'", key, value);
+                                                       FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+                                                       return false;
+                                               }
+                                               break;
+                                       case FCGI_DOUBLE_T:
+                                               *((double*) val->value) = strtod(value, &ptr);
+                                               if (!*value || *ptr) {
+                                                       snprintf(buf, BUFSIZ, "Expected float for '%s' but got '%s'", key, value);
+                                                       FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+                                                       return false;
+                                               }
+                                               break;
+                                       case FCGI_STRING_T:
+                                               *((const char**) val->value) = value;
+                                               break;
+                                       default:
+                                               Fatal("Invalid type %d given", FCGI_TYPE(val->flags));
+                               }
+                               break; //No need to search any more
+                       }
+               } //End for loop
+               if (i == count) {
+                       snprintf(buf, BUFSIZ, "Unknown key '%s' specified", key);
+                       FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+                       return false;
+               }
+       }
+
+       //Check that required parameters are received
+       for (i = 0; i < count; i++) {
+               if (FCGI_IS_REQUIRED(values[i].flags) && !FCGI_RECEIVED(values[i].flags)) {
+                       snprintf(buf, BUFSIZ, "Key '%s' required, but was not given.", values[i].key);
+                       FCGI_RejectJSON(context, buf);
+                       return false;
+               }
+       }
+       return true;
+}
+
 /**
  * Begins a response to the client in JSON format.
  * @param context The context to work in.
@@ -200,20 +291,17 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
        printf("{\r\n");
        printf("\t\"module\" : \"%s\"", context->current_module);
        FCGI_JSONLong("status", status_code);
-
-       // Jeremy: Should we include a timestamp in the JSON; something like this?
-       double start_time = g_options.start_time.tv_sec + 1e-6*(g_options.start_time.tv_usec);
+       //Time and running statistics
        struct timeval now;
        gettimeofday(&now, NULL);
-       double current_time = now.tv_sec + 1e-6*(now.tv_usec);
-       FCGI_JSONDouble("start_time", start_time);
-       FCGI_JSONDouble("current_time", current_time);
-       FCGI_JSONDouble("running_time", current_time - start_time);
+       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));
 }
 
 /**
  * Adds a key/value pair to a JSON response. The response must have already
- * been initiated by FCGI_BeginJSON. Note that characters are not escaped.
+ * been initiated by FCGI_BeginJSON. Special characters are not escaped.
  * @param key The key of the JSON entry
  * @param value The value associated with the key.
  */
@@ -270,6 +358,36 @@ void FCGI_EndJSON()
        printf("\r\n}\r\n");
 }
 
+/**
+ * Escapes a string so it can be used as a JSON string value.
+ * Does not support unicode specifiers in the form of \uXXXX.
+ * @param buf The string to be escaped
+ * @return The escaped string (return value == buf)
+ */
+char *FCGI_EscapeJSON(char *buf)
+{
+       int length, i;
+       length = strlen(buf);
+       
+       //Escape special characters. Must count down to escape properly
+       for (i = length - 1; i >= 0; i--) {
+               if (buf[i] < 0x20) { //Control characters
+                       buf[i] = ' ';
+               } else if (buf[i] == '"') {
+                       if (i-1 >= 0 && buf[i-1] == '\\') 
+                               i--;
+                       else
+                               buf[i] = '\'';
+               } else if (buf[i] == '\\') {
+                       if (i-1 >= 0 && buf[i-1] == '\'')
+                               i--;
+                       else
+                               buf[i] = ' ';
+               }
+       }
+       return buf;
+}
+
 /**
  * To be used when the input parameters are rejected. The return data
  * will also have debugging information provided.
@@ -320,18 +438,8 @@ void * FCGI_RequestLoop (void *data)
        FCGIContext context = {0};
        
        Log(LOGDEBUG, "First request...");
-       //TODO: The FCGI_Accept here is blocking. 
-       //              That means that if another thread terminates the program, this thread
-       //               will not terminate until the next request is made.
        while (FCGI_Accept() >= 0) {
 
-               if (Thread_Runstate() != RUNNING)
-               {
-                       //TODO: Yeah... deal with this better :P
-                       Log(LOGERR, "FIXME; FCGI gets request after other threads have finished.");
-                       printf("Content-type: text/plain\r\n\r\n+++OUT OF CHEESE ERROR+++\n");
-                       break;
-               }
                
                Log(LOGDEBUG, "Got request #%d", context.response_number);
                ModuleHandler module_handler = NULL;
@@ -370,7 +478,6 @@ void * FCGI_RequestLoop (void *data)
        }
 
        Log(LOGDEBUG, "Thread exiting.");
-       Thread_QuitProgram(false);
        // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return.
        return NULL;
 }
index f52a20a..965cf5c 100644 (file)
 typedef enum StatusCodes {
        STATUS_OK = 1,
        STATUS_ERROR = -1,
-       STATUS_UNAUTHORIZED = -2
+       STATUS_UNAUTHORIZED = -2,
+       STATUS_OUTOFRANGE = -3
 } StatusCodes;
 
+#define FCGI_PARAM_REQUIRED (1 << 0)
+#define FCGI_PARAM_RECEIVED (1 << 1)
+#define FCGI_BOOL_T (1 << 2)
+#define FCGI_LONG_T (1 << 3)
+#define FCGI_DOUBLE_T (1 << 4)
+#define FCGI_STRING_T (1 << 5)
+#define FCGI_REQUIRED(x) ((x) | FCGI_PARAM_REQUIRED)
+#define FCGI_IS_REQUIRED(x) ((x) & FCGI_PARAM_REQUIRED)
+#define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED)
+#define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED))
+
+typedef struct FCGIValue {
+       const char *key;
+       void *value;
+       unsigned flags;
+} FCGIValue;
+
 typedef struct FCGIContext FCGIContext;
 typedef void (*ModuleHandler) (FCGIContext *context, char *params);
 
@@ -26,6 +44,7 @@ extern void FCGI_BeginControl(FCGIContext *context, bool force);
 extern void FCGI_EndControl(FCGIContext *context);
 extern bool FCGI_HasControl(FCGIContext *context, const char *key);
 extern char *FCGI_KeyPair(char *in, const char **key, const char **value);
+extern bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count);
 extern void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code);
 extern void FCGI_JSONPair(const char *key, const char *value);
 extern void FCGI_JSONLong(const char *key, long value);
@@ -34,6 +53,7 @@ extern void FCGI_JSONBool(const char *key, bool value);
 extern void FCGI_JSONKey(const char *key);
 extern void FCGI_PrintRaw(const char *format, ...);
 extern void FCGI_EndJSON();
+extern char *FCGI_EscapeJSON(char *buf);
 extern void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description);
 extern void *FCGI_RequestLoop (void *data);
 
index b202418..d1aaa4c 100644 (file)
@@ -41,7 +41,7 @@ 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.", signal, strsignal(signal));
-       Thread_QuitProgram(false);
+
        //exit(signal);
 }
 
@@ -75,15 +75,14 @@ int main(int argc, char ** argv)
                signal(signals[i], SignalHandler);
        }
        */
-       Sensor_Spawn();
+       Sensor_Init();
+       Sensor_StartAll("test");
 
        // run request thread in the main thread
        FCGI_RequestLoop(NULL);
 
-       // Join the dark side, Luke
-       // *cough*
-       // Join the sensor threads
-       Sensor_Join();
+       Sensor_StopAll();
+
        Cleanup();
        return 0;
 }
index aafc5d1..a172c45 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash
 # Use this to quickly test run the server in valgrind
-#spawn-fcgi -p9005 -n ./valgrind.sh
+spawn-fcgi -p9005 -n ./valgrind.sh
 # Use this to run the server normally
 #./stream &
-spawn-fcgi -p9005 -n ./server
+#spawn-fcgi -p9005 -n ./server
diff --git a/server/sensor.c b/server/sensor.c
deleted file mode 100644 (file)
index 2a0e762..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-/**
- * @file sensor.c
- * @brief Implementation of sensor thread
- * TODO: Finalise implementation
- */
-
-#include "common.h"
-#include "sensor.h"
-#include "options.h"
-#include <math.h>
-
-/** Array of sensors, initialised by Sensor_Init **/
-static Sensor g_sensors[NUMSENSORS]; //global to this file
-const char * g_sensor_names[NUMSENSORS] = {    
-       "analog_test0", "analog_test1", 
-       "analog_fail0", "digital_test0", 
-       "digital_test1", "digital_fail0"
-};
-
-/**
- * Checks the sensor data for unsafe or unexpected results 
- * @param sensor_id - The ID of the sensor
- * @param value - The value of the sensor to check
- */
-void CheckSensor( SensorId sensor_id, double value)
-{
-       switch (sensor_id)
-       {
-               case ANALOG_FAIL0:
-               {
-                       if( value > ANALOG_FAIL0_SAFETY || value < ANALOG_FAIL0_MIN_SAFETY)
-                       {
-                               Log(LOGERR, "Sensor analog_fail0 is above or below its safety value of %d or %d\n", ANALOG_FAIL0_SAFETY, ANALOG_FAIL0_MIN_SAFETY);
-                       //new function that stops actuators?
-                       }
-                       else if( value > ANALOG_FAIL0_WARN || value < ANALOG_FAIL0_MIN_WARN)
-                       {
-                               Log(LOGWARN, "Sensor analog_test0 is above or below its warning value of %d or %d\n", ANALOG_FAIL0_WARN, ANALOG_FAIL0_MIN_WARN);        
-                       }
-                       break;
-               }
-               case DIGITAL_FAIL0:
-               {       
-                       if( value != 0 && value != 1)
-                       {
-                               Log(LOGERR, "Sensor digital_fail0 is not 0 or 1\n");
-                       }
-                       break;
-               }
-               default:
-               {
-               //So it doesn't complain about the missing cases - in practice we will need all sensors to be checked as above, no need to include a default as we should only pass valid sensor_id's; unless for some reason we have a sensor we don't need to check (but then why would you pass to this function in the first place :P)
-               }
-       }
-}
-
-/**
- * Read a data value from a sensor; block until value is read
- * @param sensor_id - The ID of the sensor
- * @param d - DataPoint to set
- * @returns NULL for digital sensors when data is unchanged, otherwise d
- */
-DataPoint * GetData(SensorId sensor_id, DataPoint * d)
-{
-       // 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
-
-       
-       //TODO: We should ensure the time is *never* allowed to change on the server if we use gettimeofday
-       //              Another way people might think of getting the time is to count CPU cycles with clock()
-       //              But this will not work because a) CPU clock speed may change on some devices (RPi?) and b) It counts cycles used by all threads
-       
-       struct timeval t;
-       gettimeofday(&t, NULL);
-       d->time_stamp = (t.tv_sec - g_options.start_time.tv_sec) + 1e-6*(t.tv_usec - g_options.start_time.tv_usec);
-
-       // Make time relative
-       //d->time_stamp.tv_sec -= g_options.start_time.tv_sec;
-       //d->time_stamp.tv_usec -= g_options.start_time.tv_usec;
-       
-       switch (sensor_id)
-       {
-               case ANALOG_TEST0:
-               {
-                       static int count = 0;
-                       d->value = count++;
-                       break;
-               }
-               case ANALOG_TEST1:
-                       d->value = (double)(rand() % 100) / 100;
-                       break;
-               case ANALOG_FAIL0:
-                       d->value = (double)(rand() % 6) * -( rand() % 2) / ( rand() % 100 + 1);
-                       //Gives a value between -5 and 5
-                       CheckSensor(sensor_id, d->value);
-                       break;
-               //TODO: For digital sensors, consider only updating when sensor is actually changed
-               case DIGITAL_TEST0:
-                       d->value = t.tv_sec % 2;
-                       break;
-               case DIGITAL_TEST1:
-                       d->value = (t.tv_sec+1)%2;
-                       break;
-               case DIGITAL_FAIL0:
-                       if( rand() % 100 > 98)
-                               d->value = 2;
-                       d->value = rand() % 2; 
-                       //Gives 0 or 1 or a 2 every 1/100 times
-                       CheckSensor(sensor_id, d->value);
-                       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)
-{
-       // Maybe move the binary file into long term file storage?
-       fclose(s->file);
-}
-
-
-
-/**
- * Initialise a sensor
- * @param s - Sensor to initialise
- */
-void Init(Sensor * s, int id)
-{
-       s->write_index = 0;
-       s->id = id;
-       s->points_written = 0;
-       s->points_read = 0;
-
-       #define FILENAMESIZE 3
-       char filename[FILENAMESIZE];
-       if (s->id >= pow(10, FILENAMESIZE))
-       {
-               Fatal("Too many sensors! FILENAMESIZE is %d; increase it and recompile.", FILENAMESIZE);
-       }
-
-       pthread_mutex_init(&(s->mutex), NULL);
-               
-       sprintf(filename, "%d", s->id);
-       unlink(filename); //TODO: Move old files somewhere
-
-       s->file = fopen(filename, "a+b"); // open binary file
-       Log(LOGDEBUG, "Initialised sensor %d; binary file is \"%s\"", id, filename);
-}
-
-
-/**
- * Run the main sensor polling loop
- * @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 * arg)
-{
-       Sensor * s = (Sensor*)(arg);
-
-       while (Thread_Runstate() == RUNNING) //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)
-               {
-                       DataPoint * d = &(s->buffer[s->write_index]);
-                       if (GetData(s->id, d) == NULL)
-                       {
-                               Fatal("Error collecting data");
-                       }
-                       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));
-                       //TODO: Valgrind complains about this fseek: "Syscall param write(buf) points to uninitialised byte(s)"
-                       //              Not sure why, but we should find out and fix it.
-                       fseek(s->file, 0, SEEK_END);
-                       int amount_written = fwrite(s->buffer, sizeof(DataPoint), SENSOR_DATABUFSIZ, s->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));
-                       }
-                       s->points_written += amount_written;
-                       //Log(LOGDEBUG, "Wrote %d data points for sensor %d", amount_written, s->id);
-               pthread_mutex_unlock(&(s->mutex));
-               // End of critical section
-
-               s->write_index = 0; // reset position in buffer
-               
-       }
-       Log(LOGDEBUG, "Thread for sensor %d exits", s->id);
-       return NULL; 
-}
-
-/**
- * Get position in a binary sensor file with a timestamp using a binary search
- * @param s - Sensor to use
- * @param time_stamp - Timestamp
- * @param count - If not NULL, used to provide number of searches required
- * @param found - If not NULL, set to the closest DataPoint
- * @returns Integer giving the *closest* index in the file
- * TODO: Refactor or replace?
- */
-int FindTime(Sensor * s, double time_stamp, int * count, DataPoint * found)
-{
-       DataPoint d;
-
-       int lower = 0;
-       int upper = s->points_written - 1;
-       int index = 0;
-       if (count != NULL)
-               *count = 0;     
-
-       while (upper - lower > 1)
-       {
-               index = lower + ((upper - lower)/2);
-
-               // Basically anything with fseek is critical; if we don't make it critical the sensor thread may alter data at a random point in the file!
-               // CRITICAL SECTION (May need to rethink how this is done, but I can't see how to do it without fseek :S)
-               // Regarding the suggestion that we have 2 file pointers; one for reading and one for writing:
-               // That seems like it will work... but we will have to be very careful and test it first
-               pthread_mutex_lock(&s->mutex);
-                       fseek(s->file, index*sizeof(DataPoint), SEEK_SET);
-                       int amount_read = fread(&d, sizeof(DataPoint), 1, s->file);
-               pthread_mutex_unlock(&s->mutex);
-               
-               if (amount_read != 1)
-               {
-                       Fatal("Couldn't read single data point from sensor %d", s->id);
-               }
-
-               if (d.time_stamp > time_stamp)
-               {
-                       upper = index;
-               }
-               else if (d.time_stamp < time_stamp)
-               {
-                       lower = index;
-               }
-               if (count != NULL)
-                       *count += 1;
-       }
-
-       if (found != NULL)
-               *found = d;
-
-       return index;
-       
-}
-
-/**
- * Print sensor data between two indexes in the file, using a given format
- * @param s - Sensor to use
- * @param start - Start index
- * @param end - End index
- * @param output_type - JSON, CSV or TSV output format
- */
-void PrintData(Sensor * s, int start, int end, OutputType output_type)
-{
-       DataPoint buffer[SENSOR_QUERYBUFSIZ];
-       int index = start;
-
-       if (output_type == JSON)
-       {
-               FCGI_JSONValue("[");
-       }
-
-
-       while (index < end)
-       {
-               int to_read = end - index;
-               if (to_read > SENSOR_QUERYBUFSIZ)
-               {
-                       to_read = SENSOR_QUERYBUFSIZ;
-               }
-
-               int amount_read = 0;
-               // CRITICAL SECTION
-               pthread_mutex_lock(&(s->mutex));
-
-                       fseek(s->file, index*sizeof(DataPoint), SEEK_SET);
-                       amount_read = fread(buffer, sizeof(DataPoint), to_read, s->file);
-
-               pthread_mutex_unlock(&(s->mutex));
-               // End critical section
-
-               if (amount_read != to_read)
-               {
-                       Fatal("Failed to read %d DataPoints from sensor %d; read %d instead", to_read, s->id, amount_read);
-               }
-
-               // Print the data
-               for (int i = 0; i < amount_read; ++i)
-               {
-                       //TODO: Reformat?
-                       switch (output_type)
-                       {
-                               case JSON:
-                                       FCGI_JSONValue("[%f, %f]", buffer[i].time_stamp, buffer[i].value);
-                                       if (i+1 < amount_read)
-                                               FCGI_JSONValue(",");
-                                       break;
-                               case CSV:
-                                       FCGI_PrintRaw("%f,%f\n", buffer[i].time_stamp, buffer[i].value);
-                                       break;
-                               case TSV:
-                               default:
-                                       FCGI_PrintRaw("%f\t%f\n", buffer[i].time_stamp, buffer[i].value);
-                                       break;
-                       }
-               }
-               index += amount_read;
-       }
-
-       if (output_type == JSON)
-       {
-               FCGI_JSONValue("]");
-       }
-}
-
-/**
- * Fill buffer with most recent sensor data
- * TODO: This may be obselete; remove?
- * @param s - Sensor to use
- * @param buffer - Buffer to fill
- * @param bufsiz - Size of buffer to fill
- * @returns The number of DataPoints actually read
- */
-int Sensor_Query(Sensor * s, DataPoint * buffer, int bufsiz)
-{
-       int amount_read = 0;
-       //CRITICAL SECTION (Don't access file while sensor thread is writing to it!)
-       pthread_mutex_lock(&(s->mutex));
-               
-               fseek(s->file, -bufsiz*sizeof(DataPoint), SEEK_END);
-               amount_read = fread(buffer, sizeof(DataPoint), bufsiz, s->file);
-               //Log(LOGDEBUG, "Read %d data points", amount_read);            
-       pthread_mutex_unlock(&(s->mutex));
-       return amount_read;
-}
-
-/**
- * Get a Sensor given an ID string
- * @param id_str ID string
- * @returns Sensor* identified by the string; NULL on error
- */
-Sensor * Sensor_Identify(const char * id_str)
-{
-       char * end;
-       // Parse string as integer
-       int id = strtol(id_str, &end, 10);
-       if (*end != '\0')
-       {
-               return NULL;
-       }
-       // Bounds check
-       if (id < 0 || id >= NUMSENSORS)
-               return NULL;
-
-
-       Log(LOGDEBUG, "Sensor \"%s\" identified", g_sensor_names[id]);
-       return g_sensors+id;
-}
-
-/**
- * Handle a request to the sensor module
- * @param context - The context to work in
- * @param params - Parameters passed
- * TODO: Seriously need to write more helper functions and decrease the size of this function!
- */
-void Sensor_Handler(FCGIContext *context, char * params)
-{
-       StatusCodes status = STATUS_OK;
-
-       OutputType output_type = JSON;
-       
-
-
-       const char * key; const char * value;
-
-       Sensor * sensor = NULL;
-
-       struct timeval now;
-       gettimeofday(&now, NULL);
-
-       double start_time = -1;
-       double end_time = -1;
-       double current_time = (now.tv_sec - g_options.start_time.tv_sec) + 1e-6*(now.tv_usec - g_options.start_time.tv_usec);
-       bool seek_time = false;
-       bool points_specified = false;
-       int query_size = SENSOR_QUERYBUFSIZ;
-       int start_index = -1;
-       int end_index = -1;
-
-
-       while ((params = FCGI_KeyPair(params, &key, &value)) != NULL)
-       {
-               Log(LOGDEBUG, "Got key=%s and value=%s", key, value);
-               if (strcmp(key, "id") == 0)
-               {
-                       if (sensor != NULL)
-                       {
-                               Log(LOGERR, "Only one sensor id should be specified");
-                               status = STATUS_ERROR;
-                               break;
-                       }
-                       if (*value == '\0')
-                       {
-                               Log(LOGERR, "No id specified.");
-                               status = STATUS_ERROR;
-                               break;
-                       }
-
-                       sensor = Sensor_Identify(value);
-                       if (sensor == NULL)
-                       {
-                               Log(LOGERR, "Invalid sensor id: %s", value);
-                               status = STATUS_ERROR;
-                               break;
-                       }
-               }
-               else if (strcmp(key, "format") == 0)
-               {
-                       if (strcmp(value, "json") == 0)
-                               output_type = JSON;
-                       else if (strcmp(value, "csv") == 0)
-                               output_type = CSV;
-                       else if (strcmp(value, "tsv") == 0)
-                               output_type = TSV;                      
-               }
-               else if (strcmp(key, "points") == 0)
-               {
-                       points_specified = true;
-                       if (strcmp(value, "all") == 0)
-                       {
-                               query_size = sensor->points_written;
-                       }
-                       else
-                       {
-                               char * end;
-                               query_size = strtol(value, &end, 10);
-                               if (*end != '\0')
-                               {
-                                       Log(LOGERR, "Require \"all\" or an integer value: %s = %s", key, value);
-                                       status = STATUS_ERROR;
-                                       break;
-                               }
-                       }
-                       
-               }
-               else if (strcmp(key, "start_time") == 0)
-               {
-                       seek_time = true;
-                       char * end;
-                       start_time = strtod(value, &end);
-                       if (*end != '\0')
-                       {
-                               Log(LOGERR, "Require a double: %s = %s", key, value);
-                               status = STATUS_ERROR;
-                               break;
-                       }                       
-
-                       // Treat negative values as being relative to the current time
-                       if (start_time < 0)
-                       {
-                               start_time = current_time + start_time;
-                       }
-                       start_time = floor(start_time);
-               }
-               else if (strcmp(key, "end_time") == 0)
-               {
-                       seek_time = true;
-                       char * end;
-                       end_time = strtod(value, &end);
-                       if (*end != '\0')
-                       {
-                               Log(LOGERR, "Require a double: %s = %s", key, value);
-                               status = STATUS_ERROR;
-                               break;
-                       }       
-
-                       // Treat negative values as being relative to the current time
-                       if (end_time < 0)
-                       {
-                               end_time = current_time + end_time;
-                       }               
-                       end_time = ceil(end_time);
-               }
-               // For backward compatability:
-               else if (strcmp(key, "dump") == 0)
-               {
-                       output_type = TSV;
-                       query_size = sensor->points_written+1;
-               }
-               else
-               {
-                       Log(LOGERR, "Unknown key \"%s\" (value = %s)", key, value);
-                       status = STATUS_ERROR;
-                       break;
-               }               
-       }
-
-       if (status != STATUS_ERROR && sensor == NULL)
-       {
-               Log(LOGERR, "No valid sensor id given");
-               status = STATUS_ERROR;
-       }
-
-       if (status == STATUS_ERROR)
-       {
-               FCGI_RejectJSON(context, "Invalid input parameters");
-               return;
-       }
-
-
-       if (seek_time)
-       {
-               if (end_time < 0 && !points_specified)
-                       end_index = sensor->points_written;
-               else
-               {
-                       int count = 0; DataPoint d;
-                       end_index = FindTime(sensor, end_time, &count, &d);
-                       Log(LOGDEBUG, "FindTime - Looked for %f; found [%f,%f] after %d iterations; sensor %d, position %d", end_time, d.time_stamp, d.value, count, sensor->id, end_index);
-               }
-               if (start_time < 0)
-                       start_time = 0;
-               else
-               {
-                       int count = 0; DataPoint d;
-                       start_index = FindTime(sensor, start_time, &count, &d);
-                       Log(LOGDEBUG, "FindTime - Looked for %f; found [%f,%f] after %d iterations; sensor %d, position %d", start_time, d.time_stamp, d.value, count, sensor->id, start_index);
-               }
-
-               if (points_specified)
-                       end_index = start_index + query_size;
-       }
-       else
-       {
-               start_index = sensor->points_written - query_size;
-               
-               end_index = sensor->points_written;
-       }
-       
-       if (start_index < 0)
-       {
-               Log(LOGNOTE, "start_index = %d => Clamped to 0", start_index);
-               start_index = 0;
-       }
-       if (end_index > sensor->points_written)
-       {
-               Log(LOGNOTE, "end_index = %d => Clamped to %d", end_index, sensor->points_written);
-               end_index = sensor->points_written;
-       }
-       
-       switch (output_type)
-       {
-               case JSON:
-                       FCGI_BeginJSON(context, status);
-                       FCGI_JSONLong("id", sensor->id);
-                       FCGI_JSONKey("data");
-                       PrintData(sensor, start_index, end_index, output_type);
-                       FCGI_EndJSON();
-                       break;
-               default:
-                       FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
-                       PrintData(sensor, start_index, end_index, output_type);
-                       //Force download with content-disposition
-                       // Sam: This is cool, but I don't think we should do it
-                       //  - letting the user view it in the browser and then save with their own filename is more flexible
-                       //"Content-disposition: attachment;filename=%d.csv\r\n\r\n", sensor->id);
-                       break;
-       }
-       
-}
-
-/**
- * Setup Sensors, start Sensor polling thread(s)
- */
-void Sensor_Spawn()
-{
-       // start sensor threads
-       for (int i = 0; i < NUMSENSORS; ++i)
-       {
-               Init(g_sensors+i, i);
-               pthread_create(&(g_sensors[i].thread), NULL, Sensor_Main, (void*)(g_sensors+i));
-       }
-}
-
-/**
- * Quit Sensor loops
- */
-void Sensor_Join()
-{
-       if (!Thread_Runstate())
-       {
-               Fatal("This function should not be called before Thread_QuitProgram");
-       }
-       for (int i = 0; i < NUMSENSORS; ++i)
-       {
-               pthread_join(g_sensors[i].thread, NULL);
-               Destroy(g_sensors+i);
-       }
-}
diff --git a/server/sensor.h b/server/sensor.h
deleted file mode 100644 (file)
index f834f7a..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * @file sensor.h
- * @brief Declarations for sensor thread related stuff
- */
-
-#ifndef _SENSOR_H
-#define _SENSOR_H
-
-/** Number of data points to keep in sensor buffers **/
-#define SENSOR_DATABUFSIZ 10
-/** Size of the query buffer. @see Sensor_Handler **/
-#define SENSOR_QUERYBUFSIZ 10
-
-/** Number of sensors **/
-#define NUMSENSORS 6
-
-/** Safety Values for sensors **/
-#define ANALOG_FAIL0_WARN 4
-#define ANALOG_FAIL0_SAFETY 5
-#define ANALOG_FAIL0_MIN_WARN -4
-#define ANALOG_FAIL0_MIN_SAFETY -5
-
-typedef enum SensorId {
-       ANALOG_TEST0,
-       ANALOG_TEST1,
-       ANALOG_FAIL0,
-       DIGITAL_TEST0,
-       DIGITAL_TEST1,
-       DIGITAL_FAIL0
-} SensorId;
-
-typedef enum
-{
-       JSON, // JSON data
-       CSV, // Comma seperated vector
-       TSV // Tab seperated vector
-} OutputType;
-
-/** Human readable names for the sensors **/
-extern const char * g_sensor_names[NUMSENSORS];
-
-/** Structure to represent data recorded by a sensor at an instant in time **/
-typedef struct
-{
-       /** Time at which data was taken **/
-       double time_stamp; 
-       /** Value of data **/
-       double value;
-} DataPoint;
-
-/** Structure to represent a sensor **/
-typedef struct
-{
-       /** ID number of the sensor **/
-       SensorId id;
-       /** Buffer to store data from the sensor **/
-       DataPoint buffer[SENSOR_DATABUFSIZ];
-       /** Index of last point written in the data buffer **/
-       int write_index;
-       /** Number of points read **/
-       long points_read;
-       /** Number of points written to file **/
-       long points_written;
-       /** Binary file to write data into when buffer is full **/
-       FILE * file;
-       /** Thread running the sensor **/
-       pthread_t thread;
-       /** Mutex to protect access to stuff **/
-       pthread_mutex_t mutex;
-
-       
-} Sensor;
-
-
-
-
-extern void Sensor_Spawn(); // Initialise sensor
-extern void Sensor_Join(); //Join sensor threads
-extern void * Sensor_Main(void * args); // main loop for sensor thread; pass a Sensor* cast to void*
-
-extern int Sensor_Query(Sensor * s, DataPoint * buffer, int bufsiz); // fill buffer with sensor data
-
-extern void Sensor_Handler(FCGIContext *context, char * params);
-
-#endif //_SENSOR_H
-
diff --git a/server/thread.c b/server/thread.c
deleted file mode 100644 (file)
index f1d0bb3..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @file thread.c
- * @brief Implementation of thread control
- */
-
-#include "thread.h"
-#include "options.h"
-
-pthread_mutex_t mutex_runstate = PTHREAD_MUTEX_INITIALIZER;
-Runstate runstate = RUNNING;
-
-/**
- * Set the runstate, causing all threads to exit when they next check Thread_Runstate
- * Repeated calls to this function have no effect on the runstate.
- * @param error - Set to true to indicate an error occured
- */
-void Thread_QuitProgram(bool error)
-{
-       if (runstate != RUNNING)
-       {
-               Log(LOGNOTE, "Called when program is not running; runstate = %d", runstate);
-               return;
-       }
-
-
-       Log(LOGNOTE, "Program will quit; error = %d", (int)error);
-
-       //CRITICAL SECTION - We do NOT want multiple threads editing the runstate at the same time!
-       pthread_mutex_lock(&mutex_runstate);
-       if (error)
-               runstate = QUIT_ERROR;
-       else
-               runstate = QUIT;
-       
-       gettimeofday(&g_options.end_time, NULL);
-       pthread_mutex_unlock(&mutex_runstate);
-       // End critical section
-}
-
-/**
- * Check the runstate; to be called periodically by all threads.
- * This function will call Thread_QuitProgram and change the Runstate there is an exit condition detected.
- */
-Runstate Thread_Runstate()
-{
-       //TODO: Add real exit conditions; for testing purposes, set a timeout
-       /*
-       struct timeval time;
-       gettimeofday(&time, NULL);
-       Log(LOGDEBUG, "It has been %d seconds since program started.", time.tv_sec - g_options.start_time.tv_sec);
-       if (time.tv_sec - g_options.start_time.tv_sec > 3)
-       {
-               Thread_QuitProgram(false);
-       }
-       */
-       
-       // Just reading the runstate doesn't really require a mutex
-       // The worst case: Another thread alters the runstate before this thread gets the result; this thread thinks the program is still running
-       // In that case, the thread will run an extra cycle of its loop and *then* exit. Since the runstate can only be changed once.
-       // We could put a mutex here anyway, but it will have an impact on how fast the loops can run.
-       return runstate;
-}
diff --git a/server/thread.h b/server/thread.h
deleted file mode 100644 (file)
index 1c14c9a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file thread.h
- * @brief Declarations for thread control related functions and variables
- */
-
-#ifndef _THREAD_H
-#define _THREAD_H
-
-#include "common.h"
-#include <pthread.h>
-
-typedef enum {QUIT, QUIT_ERROR, RUNNING} Runstate;
-
-/** Determine if the thread should exit; to be called periodically **/
-extern Runstate Thread_Runstate();
-/** Tell all other threads (when they call Thread_ExitCondition) to exit. Repeated calls have no effect. **/
-extern void Thread_QuitProgram(bool error);
-
-#endif //_THREAD_H
-
-//EOF
index 2a50865..3262b72 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/bash
-valgrind --leak-check=full ./server
+valgrind --leak-check=full --track-origins=yes ./server
index faf4b51..765bf04 100644 (file)
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
        <script src="unit-tests.js"></script>
-
-  <div style="margin:1em auto; width: 20em;">
-    <h3>Access control</h3>
-    <form id="control" action="#">
-      <table>
-        <tr>
-          <td>Username:</td>
-          <td><input type="text" id="username"></td>
-        </tr>
-        <tr>
-          <td>Password:</td>
-          <td><input type="password" id="password"></td>
-        </tr>
-        <tr>
-          <td><input type="submit" value="Submit"></td>
-        </tr>
-      </table>
-    </form>
-  </div>
+  
+    <hr>
+    <h3 style="margin: 1em auto;">
+        The control tests won't complete until the credentials are 
+        supplied below.
+    </h3>
+    <div style="margin:1em auto; width: 20em;">
+      
+      <h4>Access control</h4>
+      <form id="control" action="#">
+        <table>
+          <tr>
+            <td>Username:</td>
+            <td><input type="text" id="username"></td>
+          </tr>
+          <tr>
+            <td>Password:</td>
+            <td><input type="password" id="password"></td>
+          </tr>
+          <tr>
+            <td><input type="submit" value="Submit"></td>
+          </tr>
+        </table>
+      </form>
+    </div>
 </body>
 </html>
\ No newline at end of file
index 12bb76a..476e145 100644 (file)
 
 ut = {};
 ut.api = location.protocol + "//" +  location.host + "/api/";
-ut.ckey = undefined;
-ut.controlcb = $.Callbacks();
+ut.controlcb = $.Deferred();
 
 /**
- * Sends an AJAX query to the API
+ * Sends a synchronous AJAX query to the API
+ * A synchronous request makes it easier for unit testing.
  * @param {string} module The name of the module to be queried
  * @param {Object} opts Object holding the parameters, username, password and
  *                 callback. The parameters should be an object of key/value
@@ -39,7 +39,7 @@ function query(module, opts) {
     dataType: 'json',
     data: opts.params,
     beforeSend: authfunc,
-    async: opts.async
+    async: false
   }).done(opts.callback)
     .fail(function(jqXHR) {
       ok(false, "Request failed: " + jqXHR.status.toString() + " " + jqXHR.statusText);
@@ -48,86 +48,87 @@ function query(module, opts) {
 }
 
 QUnit.module("API basics");
-QUnit.asyncTest("API Existence (identify)", function () {
-  query("identify", {callback : function(data) {
-   start();
-   ok(data.status > 0, "Return status");
-   ok(data.description, data.description);
-   ok(data.build_date, data.build_date);
-  }});
+QUnit.test("API Existence (identify)", function () {
+  query("identify", {params : {actuators : true, sensors : true}, 
+   callback : function(data) {
+    ok(data.status > 0, "Return status");
+    ok(data.description !== undefined, data.description);
+    ok(data.build_date !== undefined, data.build_date);
+    ok(data.api_version !== undefined, "API version: " + data.api_version);
+    ok(data.sensors !== undefined, "Sensors list");
+    ok(data.actuators !== undefined, "Actuators list");
+    
+    var sl = "Sensors: ", al = "Actuators: ";
+    for (var id in data.sensors) {
+      sl += id + ":" + data.sensors[id] + " ";
+    }
+    for (var id in data.actuators) {
+      al += id + ":" + data.actuators[id] + " ";
+    }
+    ok(sl, sl);
+    ok(al, al);
+   }});
 });
 
-QUnit.asyncTest("Invalid module", function () {
+QUnit.test("Invalid module", function () {
   query("dontexist", {callback : function(data) {
-   start();
    ok(data.status < 0);
   }});
 });
 
 QUnit.module("Sensors");
-QUnit.asyncTest("Existence", function() {
-  query("sensors", {params : {id : 0}, callback : function(data) {
-   start();
-   ok(data.status > 0, "Return status");
-   ok(data.data !== undefined, "Data field existence");
-   var result = "Data: ";
-   for (var i = 0; i < data.data.length; i++) {
-     result += data.data[i][0]  + ":" + data.data[i][1] + ", ";
-   }
-   ok(true, result);
-  }});  
-});
-
-QUnit.asyncTest("Invalid sensor id 1", function() {
-  query("sensors", {params : {id : 999}, callback : function(data) {
-   start();
-   ok(data.status < 0, "Return status");
-  }});  
+QUnit.test("Existence", function() {
+  query("identify", {params : {sensors : 1}, callback : function(data) {
+      ok(data.status > 0, "Identification");
+      var hasSensor = false;
+      for (var id in data.sensors) {
+        hasSensor = true;
+        query("sensors", {params : {id : id}, callback : function(data) {
+          ok(data.status > 0, "Sensor " + id);
+          ok(data.data !== undefined, "Data field existence");
+          var result = "Data: ";
+          for (var i = 0; i < data.data.length; i++) {
+            result += data.data[i][0]  + ":" + data.data[i][1] + ", ";
+          }
+          ok(true, result);
+       }});        
+      }
+      ok(hasSensor, "Has at least one sensor");
+  }});
 });
 
-QUnit.asyncTest("Invalid sensor id 2", function() {
+QUnit.test("Invalid sensor ids", function() {
   query("sensors", {params : {id : ""}, callback : function(data) {
-   start();
-   ok(data.status < 0, "Return status");
+   ok(data.status < 0, "No id");
   }});  
-});
-
-QUnit.asyncTest("Out of bounds sensor id 1", function() {
+  query("sensors", {params : {id : 999}, callback : function(data) {
+   ok(data.status < 0, "Id too large");
+  }});
   query("sensors", {params : {id : "-1"}, callback : function(data) {
-   start();
-   ok(data.status < 0, "Return status");
-  }});  
-});
-
-QUnit.asyncTest("Out of bounds sensor id 2", function() {
-  query("sensors", {params : {id : "999"}, callback : function(data) {
-   start();
-   ok(data.status < 0, "Return status");
+   ok(data.status < 0, "Negative id");
   }});  
 });
 
 QUnit.module("Controls and access");
-QUnit.asyncTest("Gaining access", function() {
-  ut.controlcb.add(function () {
+QUnit.asyncTest("Setting actuator value", function () {
+  $.when(ut.controlcb).done(function () {
+    start();
+    var key;
+    
     query("control", {params : {action : "start", force : true}, 
                     username : $("#username").val(), password : $("#password").val(),
                     async : false, 
                     callback : function(data) {
-     start();
-     ok(data.status > 0, "Return status");
-     ut.ckey = data.key;
-    }});
-  });
-});
-
-QUnit.asyncTest("Setting actuator value", function () {
-  ut.controlcb.add(function () {
+     ok(data.status > 0, "Gaining access key");
+     ok(data.key, "Access key - " + data.key);
+     key = data.key;
+    }});    
     query("control", {params : {action : "set", id : 0,
           username : $("#username").val(), password : $("#password").val(),
-          value : 200, key : ut.ckey},
+          value : 200, key : key},
         callback : function(data) {
-          start();
-          ok(data.status > 0, "Return status");
+          ok(data.status > 0, "Setting actuator");
           ok(true, data.description);
     }});
   });
@@ -135,7 +136,7 @@ QUnit.asyncTest("Setting actuator value", function () {
 
 $(document).ready(function(){
   $("#control").submit(function () {
-    ut.controlcb.fire();
+    ut.controlcb.resolve();
     return false;
   });
 });
\ No newline at end of file

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