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

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