cd8caec4830cb9e35e36683be11b7a64ca2bef7e
[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      0
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         CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED);
55         
56         printf("connecting to coke machine...\n");
57         
58         giCoke_SerialFD = InitSerial(gsCoke_SerialPort, 9600);
59         if( giCoke_SerialFD == -1 ) {
60                 fprintf(stderr, "ERROR: Unable to open coke serial port ('%s')\n", gsCoke_SerialPort);
61         }
62         else {
63                 int i;
64                 for( i = 0; i < 7; i ++ )
65                         gaCoke_CachedStatus[i] = -1;
66                 // Reset the slot names.
67                 // - Dunno why this is needed, but the machine plays silly
68                 //   sometimes.
69                 Writef("n0 Slot0\n");
70                 if( !WaitForColon() )
71                 {
72                         Writef("n1 Slot1\n");
73                         WaitForColon();
74                         Writef("n2 Slot2\n");
75                         WaitForColon();
76                         Writef("n3 Slot3\n");
77                         WaitForColon();
78                         Writef("n4 Slot4\n");
79                         WaitForColon();
80                         Writef("n5 Slot5\n");
81                         WaitForColon();
82                         Writef("n6 Coke\n");
83                         
84                         Coke_int_UpdateSlotStatuses();
85                 }
86         }
87         
88         AddPeriodicFunction(Coke_int_UpdateSlotStatuses);
89         
90         return 0;
91 }
92
93 int Coke_int_GetSlotStatus(char *Buffer, int Slot)
94 {
95         regmatch_t      matches[4];
96          int    ret;
97         char    *status;        
98         
99         // Parse status response
100         ret = RunRegex(&gCoke_StatusRegex, Buffer, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
101         if( ret ) {
102                 return -1;
103         }
104
105         // Get slot status
106         Buffer[ matches[3].rm_eo ] = '\0';
107         status = &Buffer[ matches[3].rm_so ];
108         
109         #if TRACE_COKE
110         printf("Coke_CanDispense: Machine responded slot status '%s'\n", status);
111         #endif
112
113         if( strcmp(status, "full") == 0 ) {
114                 gaCoke_CachedStatus[Slot] = 0;  // 0: Avaliiable
115                 return 0;
116         }
117         else {
118                 gaCoke_CachedStatus[Slot] = 1;  // 1: Empty
119                 return 1;
120         }
121 }
122
123 /**
124  * \brief Update the status of all coke slots
125  * \note Uses goto to reduce the chance of the lock being kept
126  */
127 void Coke_int_UpdateSlotStatuses(void)
128 {
129          int    i, len;
130         char    tmp[40];
131         
132         if( giCoke_SerialFD == -1 )     return ;
133         
134         pthread_mutex_lock(&gCoke_Mutex);
135         
136         WaitForColon();
137         #if TRACE_COKE
138         printf("Coke_int_UpdateSlotStatuses: send d7\n");
139         #endif
140         Writef("d7\r\n");       // Update slot statuses
141         if( WaitForColon() )    goto ret;
142         #if TRACE_COKE
143         printf("Coke_int_UpdateSlotStatuses: send s\n");
144         #endif
145         Writef("s\r\n");
146         do {
147                 i = ReadLine(sizeof tmp, tmp);  // Read back what we just said
148                 if( i == -1 )   goto ret;
149         } while(tmp[0] == ':' || tmp[0] == 's');
150         
151         for( i = 0; i <= 6; i ++ )
152         {
153                 len = ReadLine(sizeof tmp, tmp);
154                 if( len == -1 ) {
155                         #if TRACE_COKE
156                         printf("Coke_int_UpdateSlotStatuses: Read failed on slot %i\n", i);
157                         #endif
158                         goto ret;       // I give up :(
159                 }
160                 #if TRACE_COKE
161                 printf("Coke_int_UpdateSlotStatuses: tmp = '%s'\n", tmp);
162                 #endif
163                 Coke_int_GetSlotStatus(tmp, i);
164         }
165
166 ret:
167         pthread_mutex_unlock(&gCoke_Mutex);
168 }
169
170 int Coke_CanDispense(int UNUSED(User), int Item)
171 {
172         // Sanity please
173         if( Item < 0 || Item > 6 )      return -1;      // -EYOURBAD
174         
175         // Can't dispense if the machine is not connected
176         if( giCoke_SerialFD == -1 )
177                 return -2;
178         
179         return gaCoke_CachedStatus[Item];
180 }
181
182 /**
183  * \brief Actually do a dispense from the coke machine
184  */
185 int Coke_DoDispense(int UNUSED(User), int Item)
186 {
187         char    tmp[32];
188          int    ret, len;
189
190         // Sanity please
191         if( Item < 0 || Item > 6 )      return -1;
192
193         // Can't dispense if the machine is not connected
194         if( giCoke_SerialFD == -1 )
195                 return -2;
196         
197         // LOCK
198         pthread_mutex_lock(&gCoke_Mutex);
199         
200         #if TRACE_COKE
201         printf("Coke_DoDispense: flushing input\n");
202         #endif
203         
204         // Wait for prompt
205         ret = 0;
206         while( WaitForColon() && ret < 3 )
207         {
208                 // Flush the input buffer
209                 char    tmpbuf[512];
210                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
211                 #if TRACE_COKE
212                 printf("Coke_DoDispense: sending 'd7'\n");
213                 #endif
214                 Writef("d7\r\n");
215                 ret ++;
216         }
217         if( ret == 3 )
218         {
219                 #if TRACE_COKE
220                 printf("Coke_DoDispense: timed out\n");
221                 #endif
222                 pthread_mutex_unlock(&gCoke_Mutex);
223                 return -1;
224         }
225
226         #if TRACE_COKE
227         printf("Coke_DoDispense: sending 'd%i'\n", Item);
228         #endif
229         // Dispense
230         Writef("d%i\r\n", Item);
231         
232         // Read empty lines and echo-backs
233         do {
234                 ret = ReadLine(sizeof(tmp)-1, tmp);
235                 if( ret == -1 ) {
236                         pthread_mutex_unlock(&gCoke_Mutex);
237                         return -1;
238                 }
239                 #if TRACE_COKE
240                 printf("Coke_DoDispense: read %i '%s'\n", ret, tmp);
241                 #endif
242         } while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' );
243
244         WaitForColon(); // Eat up rest of response
245         
246         #if TRACE_COKE
247         printf("Coke_DoDispense: done\n");
248         #endif
249
250         // TODO: Regex instead?
251         if( strcmp(tmp, "ok") == 0 ) {
252                 // We think dispense worked
253                 // - The machine returns 'ok' if an empty slot is dispensed, even if
254                 //   it doesn't actually try to dispense (no sound)
255                 ret = 0;
256         }
257         else {
258                 printf("Coke_DoDispense: Machine returned unknown value '%s'\n", tmp);
259                 ret = -1;
260         }
261         
262         #if TRACE_COKE
263         printf("Coke_DoDispense: Updating slot status\n");
264         #endif
265         // Update status
266         Writef("s%i\r\n", Item);
267         len = ReadLine(sizeof tmp, tmp);
268         if(len == -1)   gaCoke_CachedStatus[Item] = -1;
269         Coke_int_GetSlotStatus(tmp, Item);
270         
271         // Release and return
272         pthread_mutex_unlock(&gCoke_Mutex);
273         
274         return ret;
275 }
276
277 char ReadChar()
278 {
279         fd_set  readfs;
280         char    ch = 0;
281          int    ret;
282         struct timeval  timeout;
283         
284         timeout.tv_sec = READ_TIMEOUT;
285         timeout.tv_usec = 0;
286         
287         FD_ZERO(&readfs);
288         FD_SET(giCoke_SerialFD, &readfs);
289         
290         ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout);
291         if( ret == 0 )  return 0;       // Timeout
292         if( ret != 1 ) {
293                 printf("readchar return %i\n", ret);
294                 return 0;
295         }
296         
297         ret = read(giCoke_SerialFD, &ch, 1);
298         if( ret != 1 ) {
299                 printf("ret = %i\n", ret);
300                 return 0;
301         }
302         
303         return ch;
304 }
305
306 int Writef(const char *Format, ...)
307 {
308         va_list args;
309          int    len;
310         
311         va_start(args, Format);
312         len = vsnprintf(NULL, 0, Format, args);
313         va_end(args);
314         
315         {
316                 char    buf[len+1];
317                 va_start(args, Format);
318                 vsnprintf(buf, len+1, Format, args);
319                 va_end(args);
320                 
321                 #if DEBUG
322                 printf("Writef: %s", buf);
323                 #endif
324                 
325                 return write(giCoke_SerialFD, buf, len);
326         }
327         
328 }
329
330 int WaitForColon()
331 {
332         fd_set  readfs;
333         char    ch = 0;
334         
335         FD_SET(giCoke_SerialFD, &readfs);
336         
337         while( (ch = ReadChar()) != ':' && ch != 0);
338         
339         if( ch == 0 )   return -1;      // Timeout
340         
341         return 0;
342 }
343
344 int ReadLine(int len, char *output)
345 {
346         char    ch;
347          int    i = 0;
348         
349         for(;;)
350         {
351                 ch = ReadChar();
352                         
353                 if( i < len )
354                         output[i++] = ch;
355                 
356                 if( ch == '\0' ) {
357                         break;
358                 }
359                 if( ch == '\n' || ch == '\r' ) {
360                         if( i < len )
361                                 output[--i] = '\0';
362                         break;
363                 }
364         }
365
366         //printf("ReadLine: output=%s\n", output);
367
368         if( !ch )       return -1;
369         return i;
370 }
371
372

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