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

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