Added periodic thread (reloads item config, checks coke slots)
[tpg/opendispense2.git] / src / server / handler_coke.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * handler_coke.c - Coke controller code
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file
8  * COPYING for full details.
9  *
10  * NOTES:
11  * - Remember, the coke machine echoes your text back to you!
12  */
13 #include "common.h"
14 #include <stdio.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <regex.h>
20 #include <stdarg.h>
21 #include <pthread.h>
22
23 #define READ_TIMEOUT    2       // 2 seconds for ReadChar
24 #define TRACE_COKE      1
25
26 // === IMPORTS ===
27
28 // === PROTOTYPES ===
29  int    Coke_InitHandler();
30  int    Coke_int_GetSlotStatus(char *Buffer, int Slot);
31 void    Coke_int_UpdateSlotStatuses(void);
32  int    Coke_CanDispense(int User, int Item);
33  int    Coke_DoDispense(int User, int Item);
34  int    Writef(const char *Format, ...);
35  int    WaitForColon();
36  int    ReadLine(int len, char *output);
37
38 // === GLOBALS ===
39 tHandler        gCoke_Handler = {
40         "coke",
41         Coke_InitHandler,
42         Coke_CanDispense,
43         Coke_DoDispense
44 };
45 char    *gsCoke_SerialPort = "/dev/ttyS0";
46  int    giCoke_SerialFD;
47 regex_t gCoke_StatusRegex;
48  int    gaCoke_CachedStatus[7];
49 pthread_mutex_t gCoke_Mutex = PTHREAD_MUTEX_INITIALIZER;
50
51 // == CODE ===
52 int Coke_InitHandler()
53 {
54         printf("connecting to coke machine...\n");
55         
56         giCoke_SerialFD = InitSerial(gsCoke_SerialPort, 9600);
57         if( giCoke_SerialFD == -1 ) {
58                 fprintf(stderr, "ERROR: Unable to open coke serial port ('%s')\n", gsCoke_SerialPort);
59         }
60         else {
61                 // Reset the slot names.
62                 // - Dunno why this is needed, but the machine plays silly
63                 //   sometimes.
64                 Writef("n0 Slot0\n");
65                 if( !WaitForColon() )
66                 {
67                         Writef("n1 Slot1\n");
68                         WaitForColon();
69                         Writef("n2 Slot2\n");
70                         WaitForColon();
71                         Writef("n3 Slot3\n");
72                         WaitForColon();
73                         Writef("n4 Slot4\n");
74                         WaitForColon();
75                         Writef("n5 Slot5\n");
76                         WaitForColon();
77                         Writef("n6 Coke\n");
78                         
79                         Coke_int_UpdateSlotStatuses();
80                 }
81         }
82         
83         AddPeriodicFunction(Coke_int_UpdateSlotStatuses);
84         
85         CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED);
86         return 0;
87 }
88
89 int Coke_int_GetSlotStatus(char *Buffer, int Slot)
90 {
91         regmatch_t      matches[4];
92          int    ret;
93         char    *status;        
94         
95         // Parse status response
96         ret = RunRegex(&gCoke_StatusRegex, Buffer, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
97         if( ret ) {
98                 return -1;
99         }
100
101         // Get slot status
102         Buffer[ matches[3].rm_eo ] = '\0';
103         status = &Buffer[ matches[3].rm_so ];
104         
105         #if TRACE_COKE
106         printf("Coke_CanDispense: Machine responded slot status '%s'\n", status);
107         #endif
108
109         if( strcmp(status, "full") == 0 ) {
110                 gaCoke_CachedStatus[Slot] = 0;  // 0: Avaliiable
111                 return 0;
112         }
113         else {
114                 gaCoke_CachedStatus[Slot] = 1;  // 1: Empty
115                 return 1;
116         }
117 }
118
119 void Coke_int_UpdateSlotStatuses(void)
120 {
121          int    i;
122          int    len;
123         char    tmp[40];
124         
125         pthread_mutex_lock(&gCoke_Mutex);
126         
127         if( WaitForColon() )    return ;
128         Writef("d7\r\n");       // Update slot statuses
129         WaitForColon();
130         Writef("s\n");
131         ReadLine(sizeof tmp, tmp);      // Read back what we just said
132         
133         for( i = 0; i <= 6; i ++ )
134         {
135                 len = ReadLine(sizeof tmp, tmp);
136                 if( len == -1 ) return ;        // I give up :(
137                 Coke_int_GetSlotStatus(tmp, i);
138         }
139         pthread_mutex_unlock(&gCoke_Mutex);
140 }
141
142 int Coke_CanDispense(int UNUSED(User), int Item)
143 {
144         // Disabled in favor of caching
145         #if 0
146         char    tmp[40], *status;
147         regmatch_t      matches[4];
148          int    ret;
149
150         // Sanity please
151         if( Item < 0 || Item > 6 )      return -1;      // -EYOURBAD
152         
153         // Can't dispense if the machine is not connected
154         if( giCoke_SerialFD == -1 )
155                 return -2;
156         
157         #if TRACE_COKE
158         printf("Coke_CanDispense: Flushing\n");
159         #endif
160         
161         
162         // Wait for a prompt
163         ret = 0;
164         while( WaitForColon() && ret < 3 )
165         {
166                 // Flush the input buffer
167                 char    tmpbuf[512];
168                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
169                 #if TRACE_COKE
170                 printf("Coke_CanDispense: sending 'd7'\n");
171                 #endif
172                 Writef("d7\r\n");
173                 ret ++;
174         }
175         // Check for a timeout error
176         if( !(ret < 3) ) {
177                 fprintf(stderr, "Coke machine timed out\n");
178                 return -2;      // -EMYBAD
179         }
180         
181         // You need to do a 'd7' before reading the status
182         // - Otherwise it sometimes reports a full slot as empty
183         //   [TPG] (2011-02-19)
184         if( ret == 0 )
185         {
186                 Writef("d7\r\n");
187                 WaitForColon();
188         }
189
190         // TODO: Handle "not ok" response to D7
191         
192         #if TRACE_COKE
193         printf("Coke_CanDispense: sending 's%i'\n", Item);
194         #endif
195         
196         // Ask the coke machine
197         Writef("s%i\r\n", Item);
198
199         #if TRACE_COKE
200         printf("Coke_CanDispense: reading response\n");
201         #endif
202         // Read from the machine (ignoring empty lines)
203         while( (ret = ReadLine(sizeof(tmp)-1, tmp)) == 0 );
204         #if TRACE_COKE
205         printf("ret = %i, tmp = '%s'\n", ret, tmp);
206         #endif
207         // Read back-echoed lines
208         while( tmp[0] == ':' || tmp[1] != 'l' )
209         {
210                 ret = ReadLine(sizeof(tmp)-1, tmp);
211                 if( ret == -1 ) return -1;
212                 printf("ret = %i, tmp = '%s'\n", ret, tmp);
213         }
214
215         // Catch an error       
216         if( ret <= 0 ) {
217                 fprintf(stderr, "Coke machine is not being chatty (read = %i)\n", ret);
218                 if( ret == -1 ) {
219                         perror("Coke Machine");
220                 }
221                 return -1;
222         }
223         
224         #if TRACE_COKE
225         printf("Coke_CanDispense: wait for the prompt again\n");
226         #endif
227
228         // Eat rest of response
229         WaitForColon();
230
231         return Coke_GetSlotStatus(tmp, Item);
232         #else
233         // Sanity please
234         if( Item < 0 || Item > 6 )      return -1;      // -EYOURBAD
235         
236         // Can't dispense if the machine is not connected
237         if( giCoke_SerialFD == -1 )
238                 return -2;
239         
240         return gaCoke_CachedStatus[Item];
241         #endif
242 }
243
244 /**
245  * \brief Actually do a dispense from the coke machine
246  */
247 int Coke_DoDispense(int UNUSED(User), int Item)
248 {
249         char    tmp[32];
250          int    ret, len;
251
252         // Sanity please
253         if( Item < 0 || Item > 6 )      return -1;
254
255         // Can't dispense if the machine is not connected
256         if( giCoke_SerialFD == -1 )
257                 return -2;
258         
259         // LOCK
260         pthread_mutex_lock(&gCoke_Mutex);
261         
262         #if TRACE_COKE
263         printf("Coke_DoDispense: flushing input\n");
264         #endif
265         
266         // Wait for prompt
267         ret = 0;
268         while( WaitForColon() && ret < 3 )
269         {
270                 // Flush the input buffer
271                 char    tmpbuf[512];
272                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
273                 #if TRACE_COKE
274                 printf("Coke_DoDispense: sending 'd7'\n");
275                 #endif
276                 Writef("d7\r\n");
277         }
278
279         #if TRACE_COKE
280         printf("Coke_DoDispense: sending 'd%i'\n", Item);
281         #endif
282         // Dispense
283         Writef("d%i\r\n", Item);
284         
285         // Read empty lines and echo-backs
286         do {
287                 ret = ReadLine(sizeof(tmp)-1, tmp);
288                 if( ret == -1 ) {
289                         pthread_mutex_unlock(&gCoke_Mutex);
290                         return -1;
291                 }
292                 #if TRACE_COKE
293                 printf("Coke_DoDispense: read %i '%s'\n", ret, tmp);
294                 #endif
295         } while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' );
296
297         WaitForColon(); // Eat up rest of response
298         
299         #if TRACE_COKE
300         printf("Coke_DoDispense: done\n");
301         #endif
302
303         // TODO: Regex
304         if( strcmp(tmp, "ok") == 0 ) {
305                 // We think dispense worked
306                 // - The machine returns 'ok' if an empty slot is dispensed, even if
307                 //   it doesn't actually try to dispense (no sound)
308                 ret = 0;
309         }
310         else {
311                 printf("Machine returned unknown value '%s'\n", tmp);
312                 ret = -1;
313         }
314         
315         // Update status
316         Writef("s%i\r\n", Item);
317         len = ReadLine(sizeof tmp, tmp);
318         if(len == -1)   gaCoke_CachedStatus[Item] = -1;
319         Coke_int_GetSlotStatus(tmp, Item);
320         
321         
322         pthread_mutex_unlock(&gCoke_Mutex);
323         
324         return ret;
325 }
326
327 char ReadChar()
328 {
329         fd_set  readfs;
330         char    ch = 0;
331          int    ret;
332         struct timeval  timeout;
333         
334         timeout.tv_sec = READ_TIMEOUT;
335         timeout.tv_usec = 0;
336         
337         FD_ZERO(&readfs);
338         FD_SET(giCoke_SerialFD, &readfs);
339         
340         ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout);
341         if( ret == 0 )  return 0;       // Timeout
342         if( ret != 1 ) {
343                 printf("readchar return %i\n", ret);
344                 return 0;
345         }
346         
347         ret = read(giCoke_SerialFD, &ch, 1);
348         if( ret != 1 ) {
349                 printf("ret = %i\n", ret);
350                 return 0;
351         }
352         
353         return ch;
354 }
355
356 int Writef(const char *Format, ...)
357 {
358         va_list args;
359          int    len;
360         
361         va_start(args, Format);
362         len = vsnprintf(NULL, 0, Format, args);
363         va_end(args);
364         
365         {
366                 char    buf[len+1];
367                 va_start(args, Format);
368                 vsnprintf(buf, len+1, Format, args);
369                 va_end(args);
370                 
371                 #if DEBUG
372                 printf("Writef: %s", buf);
373                 #endif
374                 
375                 return write(giCoke_SerialFD, buf, len);
376         }
377         
378 }
379
380 int WaitForColon()
381 {
382         fd_set  readfs;
383         char    ch = 0;
384         
385         FD_SET(giCoke_SerialFD, &readfs);
386         
387         while( (ch = ReadChar()) != ':' && ch != 0);
388         
389         if( ch == 0 )   return -1;      // Timeout
390         
391         return 0;
392 }
393
394 int ReadLine(int len, char *output)
395 {
396         char    ch;
397          int    i = 0;
398         
399         for(;;)
400         {
401                 ch = ReadChar();
402                         
403                 if( i < len )
404                         output[i++] = ch;
405                 
406                 if( ch == '\0' ) {
407                         break;
408                 }
409                 if( ch == '\n' || ch == '\r' ) {
410                         if( i < len )
411                                 output[--i] = '\0';
412                         break;
413                 }
414         }
415
416         //printf("ReadLine: output=%s\n", output);
417
418         if( !ch )       return -1;
419         return i;
420 }
421
422

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