Update comments for Doxygen. Move old, unused files to the testing folder.
[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 "bbb_pin.h"
11 #include <math.h>
12
13 /** Array of sensors, initialised by Sensor_Init **/
14 static Sensor g_sensors[SENSORS_MAX];
15 /** The number of sensors **/
16 int g_num_sensors = 0;
17
18
19
20 /** 
21  * Add and initialise a Sensor
22  * @param name - Human readable name of the sensor
23  * @param user_id - User identifier
24  * @param read - Function to call whenever the sensor should be read
25  * @param init - Function to call to initialise the sensor (may be NULL)
26  * @param cleanup - Function to call whenever to deinitialise the sensor (may be NULL)
27  * @param sanity - Function to call to check that the sensor value is sane (may be NULL)
28  * @returns Number of actuators added so far
29  */
30 int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn cleanup, SanityFn sanity)
31 {
32         if (++g_num_sensors > SENSORS_MAX)
33         {
34                 Fatal("Too many sensors; Increase SENSORS_MAX from %d in sensor.h and recompile", SENSORS_MAX);
35                 // We could design the program to use realloc(3)
36                 // But since someone who adds a new sensor has to recompile the program anyway...
37         }
38         Sensor * s = &(g_sensors[g_num_sensors-1]);
39
40         s->id = g_num_sensors-1;
41         s->user_id = user_id;
42         Data_Init(&(s->data_file));
43         s->name = name;
44         s->read = read; // Set read function
45         s->init = init; // Set init function
46
47         // Start by averaging values taken over a second
48         DOUBLE_TO_TIMEVAL(1, &(s->sample_time));
49         s->averages = 1;
50         s->num_read = 0;
51
52         // Set sanity function
53         s->sanity = sanity;
54
55         if (init != NULL)
56         {
57                 if (!init(name, user_id))
58                         Fatal("Couldn't init sensor %s", name);
59         }
60
61         s->current_data.time_stamp = 0;
62         s->current_data.value = 0;
63         s->averaged_data.time_stamp = 0;
64         s->averaged_data.value = 0;
65         return g_num_sensors;
66 }
67
68 /**
69  * Initialise all sensors used by the program
70  * TODO: Edit this to add any extra sensors you need
71  * TODO: Edit the includes as well
72  */
73 #include "sensors/resource.h"
74 #include "sensors/strain.h"
75 #include "sensors/pressure.h"
76 #include "sensors/dilatometer.h"
77 #include "sensors/microphone.h"
78 void Sensor_Init()
79 {
80         //Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, NULL);   
81         //Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, NULL);  
82         Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
83         Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
84         Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
85         //Sensor_Add("../testing/count.py", 0, Piped_Read, Piped_Init, Piped_Cleanup, 1e50,-1e50,1e50,-1e50);
86         //Sensor_Add("strain0_endhoop", STRAIN0, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
87         //Sensor_Add("strain1_endlong", STRAIN1, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
88         //Sensor_Add("strain2_midhoop", STRAIN2, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
89         //Sensor_Add("strain3_midlong", STRAIN3, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
90
91         //Sensor_Add("microphone", 0, Microphone_Read, Microphone_Init, Microphone_Cleanup, Microphone_Sanity);
92         //Sensor_Add("pressure0", PRESSURE0, Pressure_Read, Pressure_Init, 5000,0,5000,0);
93         //Sensor_Add("pressure1", PRESSURE1, Pressure_Read, Pressure_Init, 5000,0,5000,0);
94         //Sensor_Add("pressure_feedback", PRESSURE_FEEDBACK, Pressure_Read, Pressure_Init, 5000,0,5000,0);
95         //Sensor_Add("enclosure", ENCLOSURE, Enclosure_Read, Enclosure_Init, 1,1,1,1); // Does not exist...
96
97         //NOTE: DO NOT ENABLE DILATOMETER WITHOUT FURTHER TESTING; CAUSES SEGFAULTS
98         //Sensor_Add("dilatometer0", 0, Dilatometer_Read, Dilatometer_Init, Dilatometer_Cleanup, NULL);
99         //Sensor_Add("dilatometer1",1, Dilatometer_Read, Dilatometer_Init, Dilatometer_Cleanup, NULL);
100 }
101
102 /**
103  * Cleanup all sensors
104  */
105 void Sensor_Cleanup()
106 {
107         for (int i = 0; i < g_num_sensors; ++i)
108         {
109                 Sensor * s = g_sensors+i;
110                 if (s->cleanup != NULL)
111                         s->cleanup(s->user_id);
112         }
113         g_num_sensors = 0;
114 }
115
116 /**
117  * Sets the sensor to the desired control mode. No checks are
118  * done to see if setting to the desired mode will conflict with
119  * the current mode - the caller must guarantee this itself.
120  * @param s The sensor whose mode is to be changed
121  * @param mode The mode to be changed to
122  * @param arg An argument specific to the mode to be set. 
123  *            e.g for CONTROL_START it represents the experiment name.
124  */
125 void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg)
126 {
127         switch(mode)
128         {
129                 case CONTROL_START:
130                         {
131                                 // Set filename
132                                 char filename[BUFSIZ];
133                                 const char *experiment_path = (const char*) arg;
134                                 int ret;
135
136                                 ret = snprintf(filename, BUFSIZ, "%s/sensor_%d", experiment_path, s->id);
137
138                                 if (ret >= BUFSIZ) 
139                                 {
140                                         Fatal("Experiment path \"%s\" too long (%d, limit %d)",
141                                                         experiment_path, ret, BUFSIZ);
142                                 }
143
144                                 Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename);
145                                 // Open DataFile
146                                 Data_Open(&(s->data_file), filename);
147                         }
148                 case CONTROL_RESUME: //Case fallthrough, no break before
149                         {
150                                 int ret;
151                                 s->activated = true; // Don't forget this!
152
153                                 // Create the thread
154                                 ret = pthread_create(&(s->thread), NULL, Sensor_Loop, (void*)(s));
155                                 if (ret != 0)
156                                 {
157                                         Fatal("Failed to create Sensor_Loop for Sensor %d", s->id);
158                                 }
159
160                                 Log(LOGDEBUG, "Resuming sensor %d", s->id);
161                         }
162                 break;
163
164                 case CONTROL_EMERGENCY:
165                 case CONTROL_PAUSE:
166                         s->activated = false;
167                         pthread_join(s->thread, NULL);
168                         Log(LOGDEBUG, "Paused sensor %d", s->id);
169                 break;
170                 
171                 case CONTROL_STOP:
172                         if (s->activated) //May have been paused before
173                         {
174                                 s->activated = false;
175                                 pthread_join(s->thread, NULL);
176                         }
177
178                         Data_Close(&(s->data_file)); // Close DataFile
179                         Log(LOGDEBUG, "Stopped sensor %d", s->id);
180                 break;
181                 default:
182                         Fatal("Unknown control mode: %d", mode);
183         }
184 }
185
186 /**
187  * Sets all sensors to the desired mode. 
188  * @see Sensor_SetMode for more information.
189  * @param mode The mode to be changed to
190  * @param arg An argument specific to the mode to be set.
191  */
192 void Sensor_SetModeAll(ControlModes mode, void * arg)
193 {
194         if (mode == CONTROL_START)
195                 Sensor_Init();
196         for (int i = 0; i < g_num_sensors; i++)
197                 Sensor_SetMode(&g_sensors[i], mode, arg);
198         if (mode == CONTROL_STOP)
199                 Sensor_Cleanup();
200 }
201
202
203 /**
204  * Record data from a single Sensor; to be run in a seperate thread
205  * @param arg - Cast to Sensor* - Sensor that the thread will handle
206  * @returns NULL (void* required to use the function with pthreads)
207  */
208 void * Sensor_Loop(void * arg)
209 {
210         Sensor * s = (Sensor*)(arg);
211         Log(LOGDEBUG, "Sensor %d starts", s->id);
212
213         // Until the sensor is stopped, record data points
214         while (s->activated)
215         {
216                 
217                 bool success = s->read(s->user_id, &(s->current_data.value));
218
219                 struct timespec t;
220                 clock_gettime(CLOCK_MONOTONIC, &t);
221                 s->current_data.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());  
222                 
223                 if (success)
224                 {
225                         if (s->sanity != NULL)
226                         {
227                                 if (!s->sanity(s->user_id, s->current_data.value))
228                                 {
229                                         Fatal("Sensor %s (%d,%d) reads unsafe value", s->name, s->id, s->user_id);
230                                 }
231                         }
232                         s->averaged_data.time_stamp += s->current_data.time_stamp;
233                         s->averaged_data.value = s->current_data.value;
234                         
235                         if (++(s->num_read) >= s->averages)
236                         {
237                                 s->averaged_data.time_stamp /= s->averages;
238                                 s->averaged_data.value /= s->averages;
239                                 Data_Save(&(s->data_file), &(s->averaged_data), 1); // Record it
240                                 s->num_read = 0;
241                                 s->averaged_data.time_stamp = 0;
242                                 s->averaged_data.value = 0;
243                         }
244                 }
245                 else
246                 {
247                         // Silence because strain sensors fail ~50% of the time :S
248                         //Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id);
249                 }
250
251
252                 clock_nanosleep(CLOCK_MONOTONIC, 0, &(s->sample_time), NULL);
253                 
254         }
255         
256         // Needed to keep pthreads happy
257         Log(LOGDEBUG, "Sensor %s (%d,%d) finished", s->name,s->id,s->user_id);
258         return NULL;
259 }
260
261 /**
262  * Get a Sensor given its name
263  * @returns Sensor with the given name, NULL if there isn't one
264  */
265 Sensor * Sensor_Identify(const char * name)
266 {       
267         for (int i = 0; i < g_num_sensors; ++i)
268         {
269                 if (strcmp(g_sensors[i].name, name) == 0)
270                         return &(g_sensors[i]);
271         }
272         return NULL;
273 }
274
275 /**
276  * Helper: Begin sensor response in a given format
277  * @param context - the FCGIContext
278  * @param s - Sensor to begin the response for
279  * @param format - Format
280  */
281 void Sensor_BeginResponse(FCGIContext * context, Sensor * s, DataFormat format)
282 {
283         // Begin response
284         switch (format)
285         {
286                 case JSON:
287                         FCGI_BeginJSON(context, STATUS_OK);
288                         FCGI_JSONLong("id", s->id);
289                         FCGI_JSONLong("user_id", s->user_id); //NOTE: Might not want to expose this?
290                         FCGI_JSONPair("name", s->name);
291                         break;
292                 default:
293                         FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
294                         break;
295         }
296 }
297
298 /**
299  * Helper: End sensor response in a given format
300  * @param context - the FCGIContext
301  * @param s - Sensor to end the response for
302  * @param format - Format
303  */
304 void Sensor_EndResponse(FCGIContext * context, Sensor * s, DataFormat format)
305 {
306         // End response
307         switch (format)
308         {
309                 case JSON:
310                         FCGI_EndJSON();
311                         break;
312                 default:
313                         break;
314         }
315 }
316
317 /**
318  * Handle a request to the sensor module
319  * @param context - The context to work in
320  * @param params - Parameters passed
321  */
322 void Sensor_Handler(FCGIContext *context, char * params)
323 {
324         struct timespec now;
325         clock_gettime(CLOCK_MONOTONIC, &now);
326         double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
327         int id = 0;
328         const char * name = "";
329         double start_time = 0;
330         double end_time = current_time;
331         const char * fmt_str;
332         double sample_s = 0;
333
334         // key/value pairs
335         FCGIValue values[] = {
336                 {"id", &id, FCGI_INT_T}, 
337                 {"name", &name, FCGI_STRING_T},
338                 {"format", &fmt_str, FCGI_STRING_T}, 
339                 {"start_time", &start_time, FCGI_DOUBLE_T}, 
340                 {"end_time", &end_time, FCGI_DOUBLE_T},
341                 {"sample_s", &sample_s, FCGI_DOUBLE_T}
342         };
343
344         // enum to avoid the use of magic numbers
345         typedef enum {
346                 ID,
347                 NAME,
348                 FORMAT,
349                 START_TIME,
350                 END_TIME,
351                 SAMPLE_S
352         } SensorParams;
353         
354         // Fill values appropriately
355         if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
356         {
357                 // Error occured; FCGI_RejectJSON already called
358                 return;
359         }
360
361         Sensor * s = NULL;
362         if (FCGI_RECEIVED(values[NAME].flags))
363         {
364                 if (FCGI_RECEIVED(values[ID].flags))
365                 {
366                         FCGI_RejectJSON(context, "Can't supply both sensor id and name");
367                         return;
368                 }
369                 s = Sensor_Identify(name);
370                 if (s == NULL)
371                 {
372                         FCGI_RejectJSON(context, "Unknown sensor name");
373                         return;
374                 }
375         }
376         else if (!FCGI_RECEIVED(values[ID].flags))
377         {
378                 FCGI_RejectJSON(context, "No sensor id or name supplied");
379                 return;
380         }
381         else if (id < 0 || id >= g_num_sensors)
382         {
383                 FCGI_RejectJSON(context, "Invalid sensor id");
384                 return;
385         }
386         else
387         {
388                 s = &(g_sensors[id]);
389         }
390
391         // Adjust sample rate if necessary
392         if (FCGI_RECEIVED(values[SAMPLE_S].flags))
393         {
394                 if (sample_s < 0)
395                 {
396                         FCGI_RejectJSON(context, "Negative sampling speed!");
397                         return;
398                 }               
399                 DOUBLE_TO_TIMEVAL(sample_s, &(s->sample_time));
400         }
401         
402         
403         DataFormat format = Data_GetFormat(&(values[FORMAT]));
404
405         // Begin response
406         Sensor_BeginResponse(context, s, format);
407
408         // Print Data
409         Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time);
410         
411         // Finish response
412         Sensor_EndResponse(context, s, format);
413
414 }
415
416 /**
417  * Get the Name of a Sensor
418  * @param id - ID number
419  */
420 const char * Sensor_GetName(int id)
421 {
422         return g_sensors[id].name;
423 }
424
425 /**
426  * Returns the last DataPoint that is currently available.
427  * @param id - The sensor ID for which to retrieve data from
428  * @return The last DataPoint
429  */
430 DataPoint Sensor_LastData(int id)
431 {
432         Sensor * s = &(g_sensors[id]);
433         return s->current_data;
434 }
435
436

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