edited dilatometer
[matches/MCTX3420.git] / server / actuator.c
1 /**
2  * @file actuator.c
3  * @brief Implementation of Actuator related functionality
4  */
5
6 #include "actuator.h"
7 #include "options.h"
8 // Files containing GPIO and PWM definitions
9 #include "bbb_pin.h"
10
11 /** Number of actuators **/
12 int g_num_actuators = 0;
13
14 /** Array of Actuators (global to this file) initialised by Actuator_Init **/
15 static Actuator g_actuators[ACTUATORS_MAX];
16 /** 
17  * Add and initialise an Actuator
18  * @param name - Human readable name of the actuator
19  * @param read - Function to call whenever the actuator should be read
20  * @param init - Function to call to initialise the actuator (may be NULL)
21  * @returns Number of actuators added so far
22  */
23 int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity, double initial_value)
24 {
25         if (++g_num_actuators > ACTUATORS_MAX)
26         {
27                 Fatal("Too many sensors; Increase ACTUATORS_MAX from %d in actuator.h and recompile", ACTUATORS_MAX);
28         }
29         Actuator * a = &(g_actuators[g_num_actuators-1]);
30         a->id = g_num_actuators-1;
31         a->user_id = user_id;
32         Data_Init(&(a->data_file));
33         a->name = name;
34         a->set = set; // Set read function
35         a->init = init; // Set init function
36
37         a->sanity = sanity;
38         a->cleanup = cleanup;
39         pthread_mutex_init(&(a->mutex), NULL);
40
41         if (init != NULL)
42         {
43                 if (!init(name, user_id))
44                         Fatal("Couldn't initialise actuator %s", name);
45         }
46
47         Actuator_SetValue(a, initial_value, false);
48
49         return g_num_actuators;
50 }
51
52
53 /**
54  * Initialisation of *all* Actuators
55  */
56 #include "actuators/pregulator.h"
57 #include "actuators/relays.h"
58 void Actuator_Init()
59 {
60         //Actuator_Add("ledtest",0,  Ledtest_Set, NULL,NULL,NULL);
61         //Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity, 0);
62         Actuator_Add("pregulator", 0, Pregulator_Set, Pregulator_Init, Pregulator_Cleanup, Pregulator_Sanity, 0);
63         Actuator_Add("can_select", RELAY_CANSELECT, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0);
64         Actuator_Add("can_enable", RELAY_CANENABLE, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0);
65         Actuator_Add("main_pressure", RELAY_MAIN, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 1);
66 }
67
68 /**
69  * Deinitialise actuators
70  */
71 void Actuator_Cleanup()
72 {
73         for (int i = 0; i < g_num_actuators; ++i)
74         {
75                 Actuator * a = g_actuators+i;
76                 if (a->cleanup != NULL)
77                         a->cleanup(a->user_id);
78         }
79         g_num_actuators = 0;
80 }
81
82
83 /**
84  * Sets the actuator to the desired mode. No checks are
85  * done to see if setting to the desired mode will conflict with
86  * the current mode - the caller must guarantee this itself.
87  * @param a The actuator whose mode is to be changed
88  * @param mode The mode to be changed to
89  * @param arg An argument specific to the mode to be set. 
90  *            e.g for CONTROL_START it represents the experiment name.
91  */
92 void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
93 {
94         switch (mode)
95         {
96                 case CONTROL_START:
97                         {
98                                 // Set filename
99                                 char filename[BUFSIZ];
100                                 const char *experiment_path = (const char*) arg;
101                                 int ret;
102
103                                 ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id);
104
105                                 if (ret >= BUFSIZ) 
106                                 {
107                                         Fatal("Experiment path \"%s\" too long (%d, limit %d)",
108                                                         experiment_path, ret, BUFSIZ);
109                                 }
110
111                                 Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename);
112                                 // Open DataFile
113                                 Data_Open(&(a->data_file), filename);
114                         } 
115                 case CONTROL_RESUME:  //Case fallthrough; no break before
116                         {
117                                 int ret;
118                                 a->activated = true; // Don't forget this
119                                 a->control_changed = false;
120
121                                 ret = pthread_create(&(a->thread), NULL, Actuator_Loop, (void*)(a));
122                                 if (ret != 0)
123                                 {
124                                         Fatal("Failed to create Actuator_Loop for Actuator %d", a->id);
125                                 }
126
127                                 Log(LOGDEBUG, "Resuming actuator %d", a->id);
128                         }
129                 break;
130
131                 case CONTROL_EMERGENCY: //TODO add proper case for emergency
132                 case CONTROL_PAUSE:
133                         a->activated = false;
134                         Actuator_SetControl(a, NULL);
135                         pthread_join(a->thread, NULL); // Wait for thread to exit
136
137                         Log(LOGDEBUG, "Paused actuator %d", a->id);
138                 break;
139
140                 break;
141                 case CONTROL_STOP:
142                         if (a->activated) //May have been paused before
143                         {
144                                 a->activated = false;
145                                 Actuator_SetControl(a, NULL);
146                                 pthread_join(a->thread, NULL); // Wait for thread to exit       
147                         }
148                         Data_Close(&(a->data_file)); // Close DataFile
149                         
150                         Log(LOGDEBUG, "Stopped actuator %d", a->id);
151                 break;
152                 default:
153                         Fatal("Unknown control mode: %d", mode);
154         }
155 }
156
157 /**
158  * Sets all actuators to the desired mode. 
159  * @see Actuator_SetMode for more information.
160  * @param mode The mode to be changed to
161  * @param arg An argument specific to the mode to be set.
162  */
163 void Actuator_SetModeAll(ControlModes mode, void * arg)
164 {
165         if (mode == CONTROL_START)
166                 Actuator_Init();
167         for (int i = 0; i < g_num_actuators; i++)
168                 Actuator_SetMode(&g_actuators[i], mode, arg);
169         if (mode == CONTROL_STOP)
170                 Actuator_Cleanup();
171 }
172
173 /**
174  * Actuator control thread
175  * @param arg - Cast to an Actuator*
176  * @returns NULL to keep pthreads happy
177  */
178 void * Actuator_Loop(void * arg)
179 {
180         Actuator * a = (Actuator*)(arg);
181         
182         // Loop until stopped
183         while (a->activated)
184         {
185                 pthread_mutex_lock(&(a->mutex));
186                 while (!a->control_changed)
187                 {
188                         pthread_cond_wait(&(a->cond), &(a->mutex));
189                 }
190                 a->control_changed = false;
191                 pthread_mutex_unlock(&(a->mutex));
192                 if (!a->activated)
193                         break;
194
195                 Actuator_SetValue(a, a->control.start, true);
196                 // Currently does discrete steps after specified time intervals
197
198                 struct timespec wait;
199                 DOUBLE_TO_TIMEVAL(a->control.stepwait, &wait);
200                 while (!a->control_changed && a->control.steps > 0 && a->activated)
201                 {
202                         clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
203                         a->control.start += a->control.stepsize;
204                         Actuator_SetValue(a, a->control.start, true);
205                         
206                         a->control.steps--;
207                 }
208                 if (a->control_changed)
209                         continue;
210                 clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
211
212                 //TODO:
213                 // Note that although this loop has a sleep in it which would seem to make it hard to enforce urgent shutdowns,
214                 //      You can call the Actuator's cleanup function immediately (and this loop should later just exit)
215                 //      tl;dr This function isn't/shouldn't be responsible for the emergency Actuator stuff
216                 // (That should be handled by the Fatal function... at some point)
217         }
218
219         //TODO: Cleanup?
220         
221         // Keep pthreads happy
222         return NULL;
223 }
224
225 /**
226  * Set an Actuators control variable
227  * @param a - Actuator to control 
228  * @param c - Control to set to
229  */
230 void Actuator_SetControl(Actuator * a, ActuatorControl * c)
231 {
232         pthread_mutex_lock(&(a->mutex));
233         if (c != NULL)
234                 a->control = *c;
235         a->control_changed = true;
236         pthread_cond_broadcast(&(a->cond));
237         pthread_mutex_unlock(&(a->mutex));
238         
239 }
240
241 /**
242  * Set an Actuator value
243  * @param a - The Actuator
244  * @param value - The value to set
245  */
246 void Actuator_SetValue(Actuator * a, double value, bool record)
247 {
248         if (a->sanity != NULL && !a->sanity(a->user_id, value))
249         {
250                 //ARE YOU INSANE?
251                 Log(LOGERR,"Insane value %lf for actuator %s", value, a->name);
252                 return;
253         }
254         if (!(a->set(a->user_id, value)))
255         {
256                 Fatal("Failed to set actuator %s to %lf", a->name, value);
257         }
258
259         // Set time stamp
260         struct timespec t;
261         clock_gettime(CLOCK_MONOTONIC, &t);
262         DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), a->last_setting.value};
263         // Record value change
264         if (record)
265         {       
266                 d.time_stamp -= 1e-6;
267                 Data_Save(&(a->data_file), &d, 1);
268                 d.value = value;
269                 d.time_stamp += 1e-6;
270                 Data_Save(&(a->data_file), &d, 1);
271         }
272         a->last_setting = d;
273 }
274
275 /**
276  * Helper: Begin Actuator response in a given format
277  * @param context - the FCGIContext
278  * @param format - Format
279  * @param id - ID of Actuator
280  */
281 void Actuator_BeginResponse(FCGIContext * context, Actuator * a, DataFormat format)
282 {
283         // Begin response
284         switch (format)
285         {
286                 case JSON:
287                         FCGI_BeginJSON(context, STATUS_OK);
288                         FCGI_JSONLong("id", a->id);
289                         FCGI_JSONLong("user_id", a->user_id); //TODO: Don't need to show this?
290                         FCGI_JSONPair("name", a->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 Actuator response in a given format
300  * @param context - the FCGIContext
301  * @param id - ID of the Actuator
302  * @param format - Format
303  */
304 void Actuator_EndResponse(FCGIContext * context, Actuator * a, 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 /**
319  * Handle a request for an Actuator
320  * @param context - FCGI context
321  * @param params - Parameters passed
322  */
323 void Actuator_Handler(FCGIContext * context, char * params)
324 {
325         struct timespec now;
326         clock_gettime(CLOCK_MONOTONIC, &now);
327         double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
328         int id = 0;
329         char * name = "";
330         char * set = "";
331         double start_time = 0;
332         double end_time = current_time;
333         char * fmt_str;
334
335         // key/value pairs
336         FCGIValue values[] = {
337                 {"id", &id, FCGI_INT_T},
338                 {"name", &name, FCGI_STRING_T}, 
339                 {"set", &set, FCGI_STRING_T},
340                 {"start_time", &start_time, FCGI_DOUBLE_T},
341                 {"end_time", &end_time, FCGI_DOUBLE_T},
342                 {"format", &fmt_str, FCGI_STRING_T}
343         };
344
345         // enum to avoid the use of magic numbers
346         typedef enum {
347                 ID,
348                 NAME,
349                 SET,
350                 START_TIME,
351                 END_TIME,
352                 FORMAT
353         } ActuatorParams;
354         
355         // Fill values appropriately
356         if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
357         {
358                 // Error occured; FCGI_RejectJSON already called
359                 return;
360         }       
361
362         // Get the Actuator identified
363         Actuator * a = NULL;
364
365         if (FCGI_RECEIVED(values[NAME].flags))
366         {
367                 if (FCGI_RECEIVED(values[ID].flags))
368                 {
369                         FCGI_RejectJSON(context, "Can't supply both id and name");
370                         return;
371                 }
372                 a = Actuator_Identify(name);
373                 if (a == NULL)
374                 {
375                         FCGI_RejectJSON(context, "Unknown actuator name");
376                         return;
377                 }
378                 
379         }
380         else if (!FCGI_RECEIVED(values[ID].flags))
381         {
382                 FCGI_RejectJSON(context, "No id or name supplied");
383                 return;
384         }
385         else if (id < 0 || id >= g_num_actuators)
386         {
387                 FCGI_RejectJSON(context, "Invalid Actuator id");
388                 return;
389         }
390         else
391         {
392                 a = &(g_actuators[id]);
393         }
394         
395
396         DataFormat format = Data_GetFormat(&(values[FORMAT]));
397
398
399
400
401         if (FCGI_RECEIVED(values[SET].flags))
402         {
403                 
404         
405                 ActuatorControl c = {0.0, 0.0, 0.0, 0}; // Need to set default values (since we don't require them all)
406                 // sscanf returns the number of fields successfully read...
407                 int n = sscanf(set, "%lf_%lf_%lf_%d", &(c.start), &(c.stepwait), &(c.stepsize), &(c.steps)); // Set provided values in order
408                 if (n != 4)
409                 {
410                         //      If the user doesn't provide all 4 values, the Actuator will get set *once* using the first of the provided values
411                         //      (see Actuator_Loop)
412                         //  Not really a problem if n = 1, but maybe generate a warning for 2 <= n < 4 ?
413                         Log(LOGDEBUG, "Only provided %d values (expect %d) for Actuator setting", n, 4);
414                 }
415                 // SANITY CHECKS
416                 if (c.stepwait < 0 || c.steps < 0 || (a->sanity != NULL && !a->sanity(a->user_id, c.start)))
417                 {
418                         FCGI_RejectJSON(context, "Bad Actuator setting");
419                         return;
420                 }
421                 Actuator_SetControl(a, &c);
422         }
423         
424         // Begin response
425         Actuator_BeginResponse(context, a, format);
426         if (format == JSON)
427                 FCGI_JSONPair("set", set);
428
429         // Print Data
430         Data_Handler(&(a->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time);
431         
432         // Finish response
433         Actuator_EndResponse(context, a, format);
434 }
435
436 /**
437  * Get the name of an Actuator given its id
438  * @param id - ID of the actuator
439  * @returns The Actuator's name
440  */
441 const char * Actuator_GetName(int id)
442 {
443         return g_actuators[id].name;
444 }
445
446 /**
447  * Identify an Actuator from its name string
448  * @param name - The name of the Actuator
449  * @returns Actuator
450  */
451 Actuator * Actuator_Identify(const char * name)
452 {
453         for (int i = 0; i < g_num_actuators; ++i)
454         {
455                 if (strcmp(g_actuators[i].name, name) == 0)
456                         return &(g_actuators[i]);
457         }
458         return NULL;
459 }
460
461 DataPoint Actuator_LastData(int id)
462 {
463         Actuator * a = &(g_actuators[id]);
464         return a->last_setting;
465 }

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