Merge branch 'master' of https://github.com/szmoore/MCTX3420.git
[matches/MCTX3420.git] / server / sensor.c
1 /**
2  * @file sensor.c
3  * @brief Implementation of sensor thread
4  * TODO: Finalise implementation
5  */
6
7 #include "common.h"
8 #include "sensor.h"
9 #include "options.h"
10 #include <math.h>
11
12 /** Array of sensors, initialised by Sensor_Init **/
13 static Sensor g_sensors[NUMSENSORS]; //global to this file
14
15 /** Human readable names for the sensors **/
16 const char * g_sensor_names[NUMSENSORS] = {     
17         "analog_test0", "analog_test1", 
18         "analog_fail0", "digital_test0", 
19         "digital_test1", "digital_fail0"
20 };
21
22 /**
23  * One off initialisation of *all* sensors
24  */
25 void Sensor_Init()
26 {
27         for (int i = 0; i < NUMSENSORS; ++i)
28         {
29                 g_sensors[i].id = i;
30                 Data_Init(&(g_sensors[i].data_file));
31                 g_sensors[i].record_data = false;       
32         }
33 }
34
35 /**
36  * Start a Sensor recording DataPoints
37  * @param s - The Sensor to start
38  * @param experiment_name - Prepended to DataFile filename
39  */
40 void Sensor_Start(Sensor * s, const char * experiment_name)
41 {
42         // Set filename
43         char filename[BUFSIZ];
44         if (sprintf(filename, "%s_%d", experiment_name, s->id) >= BUFSIZ)
45         {
46                 Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
47         }
48
49         Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename);
50         // Open DataFile
51         Data_Open(&(s->data_file), filename);
52
53         s->record_data = true; // Don't forget this!
54
55         // Create the thread
56         pthread_create(&(s->thread), NULL, Sensor_Loop, (void*)(s));
57 }
58
59 /**
60  * Stop a Sensor from recording DataPoints. Blocks until it has stopped.
61  * @param s - The Sensor to stop
62  */
63 void Sensor_Stop(Sensor * s)
64 {
65         // Stop
66         if (s->record_data)
67         {
68                 s->record_data = false;
69                 pthread_join(s->thread, NULL); // Wait for thread to exit
70                 Data_Close(&(s->data_file)); // Close DataFile
71                 s->newest_data.time_stamp = 0;
72                 s->newest_data.value = 0;
73         }
74 }
75
76 /**
77  * Stop all Sensors
78  */
79 void Sensor_StopAll()
80 {
81         for (int i = 0; i < NUMSENSORS; ++i)
82                 Sensor_Stop(g_sensors+i);
83 }
84
85 /**
86  * Start all Sensors
87  */
88 void Sensor_StartAll(const char * experiment_name)
89 {
90         for (int i = 0; i < NUMSENSORS; ++i)
91                 Sensor_Start(g_sensors+i, experiment_name);
92 }
93
94
95 /**
96  * Checks the sensor data for unsafe or unexpected results 
97  * @param sensor_id - The ID of the sensor
98  * @param value - The value from the sensor to test
99  */
100 void Sensor_CheckData(SensorId id, double value)
101 {
102         switch (sensor_id)
103         {
104                 case ANALOG_FAIL0:
105                 {
106                         if( value > ANALOG_FAIL0_SAFETY || value < ANALOG_FAIL0_MIN_SAFETY)
107                         {
108                                 Log(LOGERR, "Sensor analog_fail0 is above or below its safety value of %d or %d\n", ANALOG_FAIL0_SAFETY, ANALOG_FAIL0_MIN_SAFETY);
109                         //new function that stops actuators?
110                         }
111                         else if( value > ANALOG_FAIL0_WARN || value < ANALOG_FAIL0_MIN_WARN)
112                         {
113                                 Log(LOGWARN, "Sensor analog_test0 is above or below its warning value of %d or %d\n", ANALOG_FAIL0_WARN, ANALOG_FAIL0_MIN_WARN);        
114                         }
115                         break;
116                 }
117                 case DIGITAL_FAIL0:
118                 {       
119                         if( value != 0 && value != 1)
120                         {
121                                 Log(LOGERR, "Sensor digital_fail0 is not 0 or 1\n");
122                         }
123                         break;
124                 }
125                 default:
126                 {
127                 //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)
128                 }
129         }
130 }
131
132
133 /**
134  * Read a DataPoint from a Sensor; block until value is read
135  * @param id - The ID of the sensor
136  * @param d - DataPoint to set
137  * @returns True if the DataPoint was different from the most recently recorded.
138  */
139 bool Sensor_Read(Sensor * s, DataPoint * d)
140 {
141         
142         // Set time stamp
143         struct timeval t;
144         gettimeofday(&t, NULL);
145         d->time_stamp = TIMEVAL_DIFF(t, g_options.start_time);
146
147         // Read value based on Sensor Id
148         switch (s->id)
149         {
150                 case ANALOG_TEST0:
151                         d->value = (double)(rand() % 100) / 100;
152                         break;
153
154                 case ANALOG_TEST1:
155                 {
156                         static int count = 0;
157                         d->value = count++;
158                         break;
159                 }
160                 case ANALOG_FAIL0:
161                         d->value = (double)(rand() % 6) * -( rand() % 2) / ( rand() % 100 + 1);
162                         //Gives a value between -5 and 5
163                         CheckSensor(sensor_id, d->value);
164                         break;
165                 case DIGITAL_TEST0:
166                         d->value = t.tv_sec % 2;
167                         break;
168                 case DIGITAL_TEST1:
169                         d->value = (t.tv_sec+1)%2;
170                         break;
171                 case DIGITAL_FAIL0:
172                         if( rand() % 100 > 98)
173                                 d->value = 2;
174                         d->value = rand() % 2; 
175                         //Gives 0 or 1 or a 2 every 1/100 times
176                         CheckSensor(sensor_id, d->value);
177                         break;
178                 default:
179                         Fatal("Unknown sensor id: %d", s->id);
180                         break;
181         }       
182         usleep(100000); // simulate delay in sensor polling
183
184         // Perform sanity check based on Sensor's ID and the DataPoint
185         Sensor_CheckData(s->id, d);
186
187         // Update latest DataPoint if necessary
188         bool result = (d->value != s->newest_data.value);
189         if (result)
190         {
191                 s->newest_data.time_stamp = d->time_stamp;
192                 s->newest_data.value = d->value;
193         }
194         return result;
195 }
196
197 /**
198  * Record data from a single Sensor; to be run in a seperate thread
199  * @param arg - Cast to Sensor* - Sensor that the thread will handle
200  * @returns NULL (void* required to use the function with pthreads)
201  */
202 void * Sensor_Loop(void * arg)
203 {
204         Sensor * s = (Sensor*)(arg);
205         Log(LOGDEBUG, "Sensor %d starts", s->id);
206
207         // Until the sensor is stopped, record data points
208         while (s->record_data)
209         {
210                 DataPoint d;
211                 //Log(LOGDEBUG, "Sensor %d reads data [%f,%f]", s->id, d.time_stamp, d.value);
212                 if (Sensor_Read(s, &d)) // If new DataPoint is read:
213                 {
214                         //Log(LOGDEBUG, "Sensor %d saves data [%f,%f]", s->id, d.time_stamp, d.value);
215                         Data_Save(&(s->data_file), &d, 1); // Record it
216                 }
217         }
218         
219         // Needed to keep pthreads happy
220
221         Log(LOGDEBUG, "Sensor %d finished", s->id);
222         return NULL;
223 }
224
225 /**
226  * Get a Sensor given an ID string
227  * @param id_str ID string
228  * @returns Sensor* identified by the string; NULL on error
229  */
230 Sensor * Sensor_Identify(const char * id_str)
231 {
232         char * end;
233         // Parse string as integer
234         int id = strtol(id_str, &end, 10);
235         if (*end != '\0')
236         {
237                 return NULL;
238         }
239         // Bounds check
240         if (id < 0 || id >= NUMSENSORS)
241                 return NULL;
242
243
244         Log(LOGDEBUG, "Sensor \"%s\" identified", g_sensor_names[id]);
245         return g_sensors+id;
246 }
247
248 /**
249  * Helper: Begin sensor response in a given format
250  * @param context - the FCGIContext
251  * @param id - ID of sensor
252  * @param format - Format
253  */
254 void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format)
255 {
256         // Begin response
257         switch (format)
258         {
259                 case JSON:
260                         FCGI_BeginJSON(context, STATUS_OK);
261                         FCGI_JSONLong("id", id);
262                         FCGI_JSONKey("data");
263                         break;
264                 default:
265                         FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
266                         break;
267         }
268 }
269
270 /**
271  * Helper: End sensor response in a given format
272  * @param context - the FCGIContext
273  * @param id - ID of the sensor
274  * @param format - Format
275  */
276 void Sensor_EndResponse(FCGIContext * context, SensorId id, DataFormat format)
277 {
278         // End response
279         switch (format)
280         {
281                 case JSON:
282                         FCGI_EndJSON();
283                         break;
284                 default:
285                         break;
286         }
287 }
288
289 /**
290  * Handle a request to the sensor module
291  * @param context - The context to work in
292  * @param params - Parameters passed
293  */
294 void Sensor_Handler(FCGIContext *context, char * params)
295 {
296         struct timeval now;
297         gettimeofday(&now, NULL);
298         double current_time = TIMEVAL_DIFF(now, g_options.start_time);
299
300         int id = 0;
301         double start_time = 0;
302         double end_time = current_time;
303         const char * fmt_str;
304
305         // key/value pairs
306         FCGIValue values[] = {
307                 {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, 
308                 {"format", &fmt_str, FCGI_STRING_T}, 
309                 {"start_time", &start_time, FCGI_DOUBLE_T}, 
310                 {"end_time", &end_time, FCGI_DOUBLE_T},
311         };
312
313         // enum to avoid the use of magic numbers
314         typedef enum {
315                 ID,
316                 FORMAT,
317                 START_TIME,
318                 END_TIME,
319         } SensorParams;
320         
321         // Fill values appropriately
322         if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
323         {
324                 // Error occured; FCGI_RejectJSON already called
325                 return;
326         }
327         else if (id < 0 || id >= NUMSENSORS)
328         {
329                 FCGI_RejectJSON(context, "Invalid sensor id specified");
330                 return;
331         }
332
333         // Get Sensor and format
334         Sensor * s = g_sensors+id;
335         DataFormat format = JSON;
336
337         // Check if format type was specified
338         if (FCGI_RECEIVED(values[FORMAT].flags))
339         {
340                 if (strcmp(fmt_str, "json") == 0)
341                         format = JSON;
342                 else if (strcmp(fmt_str, "tsv") == 0)
343                         format = TSV;
344                 else 
345                 {
346                         FCGI_RejectJSON(context, "Unknown format type specified.");
347                         return;
348                 }
349         }
350
351         // Begin response
352         Sensor_BeginResponse(context, id, format);
353         
354         // If a time was specified
355         if (FCGI_RECEIVED(values[START_TIME].flags) || FCGI_RECEIVED(values[END_TIME].flags))
356         {
357                 // Wrap times relative to the current time
358                 if (start_time < 0)
359                         start_time += current_time;
360                 if (end_time < 0)
361                         end_time += current_time;
362
363                 // Print points by time range
364                 Data_PrintByTimes(&(s->data_file), start_time, end_time, format);
365         }
366         else // No time was specified; just return a recent set of points
367         {
368                 pthread_mutex_lock(&(s->data_file.mutex));
369                         int start_index = s->data_file.num_points-DATA_BUFSIZ;
370                         int end_index = s->data_file.num_points;
371                 pthread_mutex_unlock(&(s->data_file.mutex));
372
373                 // Bounds check
374                 if (start_index < 0)
375                         start_index = 0;
376                 if (end_index < 0)
377                         end_index = 0;
378
379                 // Print points by indexes
380                 Log(LOGDEBUG, "Sensor %d file \"%s\" indexes %d->%d", s->id, s->data_file.filename, start_index, end_index);
381                 Data_PrintByIndexes(&(s->data_file), start_index, end_index, format);
382         }
383         
384         // Finish response
385         Sensor_EndResponse(context, id, format);
386         
387 }
388
389
390

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