da2958032d395e0d6e27a043e77388e1347e5722
[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 /**
120  * \brief Update the status of all coke slots
121  * \note Uses goto to reduce the chance of the lock being kept
122  */
123 void Coke_int_UpdateSlotStatuses(void)
124 {
125          int    i, len;
126         char    tmp[40];
127         
128         if( giCoke_SerialFD == -1 )     return ;
129         
130         pthread_mutex_lock(&gCoke_Mutex);
131         
132         WaitForColon();
133         #if TRACE_COKE
134         printf("Coke_int_UpdateSlotStatuses: send d7\n");
135         #endif
136         Writef("d7\r\n");       // Update slot statuses
137         if( WaitForColon() )    goto ret;
138         #if TRACE_COKE
139         printf("Coke_int_UpdateSlotStatuses: send s\n");
140         #endif
141         Writef("s\r\n");
142         do {
143                 i = ReadLine(sizeof tmp, tmp);  // Read back what we just said
144                 if( i == -1 )   goto ret;
145         } while(tmp[0] == ':' || tmp[0] == 's');
146         
147         for( i = 0; i <= 6; i ++ )
148         {
149                 len = ReadLine(sizeof tmp, tmp);
150                 if( len == -1 ) {
151                         #if TRACE_COKE
152                         printf("Coke_int_UpdateSlotStatuses: Read failed on slot %i\n", i);
153                         #endif
154                         goto ret;       // I give up :(
155                 }
156                 #if TRACE_COKE
157                 printf("Coke_int_UpdateSlotStatuses: tmp = '%s'\n", tmp);
158                 #endif
159                 Coke_int_GetSlotStatus(tmp, i);
160         }
161
162 ret:
163         pthread_mutex_unlock(&gCoke_Mutex);
164 }
165
166 int Coke_CanDispense(int UNUSED(User), int Item)
167 {
168         // Sanity please
169         if( Item < 0 || Item > 6 )      return -1;      // -EYOURBAD
170         
171         // Can't dispense if the machine is not connected
172         if( giCoke_SerialFD == -1 )
173                 return -2;
174         
175         return gaCoke_CachedStatus[Item];
176 }
177
178 /**
179  * \brief Actually do a dispense from the coke machine
180  */
181 int Coke_DoDispense(int UNUSED(User), int Item)
182 {
183         char    tmp[32];
184          int    ret, len;
185
186         // Sanity please
187         if( Item < 0 || Item > 6 )      return -1;
188
189         // Can't dispense if the machine is not connected
190         if( giCoke_SerialFD == -1 )
191                 return -2;
192         
193         // LOCK
194         pthread_mutex_lock(&gCoke_Mutex);
195         
196         #if TRACE_COKE
197         printf("Coke_DoDispense: flushing input\n");
198         #endif
199         
200         // Wait for prompt
201         ret = 0;
202         while( WaitForColon() && ret < 3 )
203         {
204                 // Flush the input buffer
205                 char    tmpbuf[512];
206                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
207                 #if TRACE_COKE
208                 printf("Coke_DoDispense: sending 'd7'\n");
209                 #endif
210                 Writef("d7\r\n");
211                 ret ++;
212         }
213         if( ret == 3 )
214         {
215                 #if TRACE_COKE
216                 printf("Coke_DoDispense: timed out\n");
217                 #endif
218                 pthread_mutex_unlock(&gCoke_Mutex);
219                 return -1;
220         }
221
222         #if TRACE_COKE
223         printf("Coke_DoDispense: sending 'd%i'\n", Item);
224         #endif
225         // Dispense
226         Writef("d%i\r\n", Item);
227         
228         // Read empty lines and echo-backs
229         do {
230                 ret = ReadLine(sizeof(tmp)-1, tmp);
231                 if( ret == -1 ) {
232                         pthread_mutex_unlock(&gCoke_Mutex);
233                         return -1;
234                 }
235                 #if TRACE_COKE
236                 printf("Coke_DoDispense: read %i '%s'\n", ret, tmp);
237                 #endif
238         } while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' );
239
240         WaitForColon(); // Eat up rest of response
241         
242         #if TRACE_COKE
243         printf("Coke_DoDispense: done\n");
244         #endif
245
246         // TODO: Regex instead?
247         if( strcmp(tmp, "ok") == 0 ) {
248                 // We think dispense worked
249                 // - The machine returns 'ok' if an empty slot is dispensed, even if
250                 //   it doesn't actually try to dispense (no sound)
251                 ret = 0;
252         }
253         else {
254                 printf("Coke_DoDispense: Machine returned unknown value '%s'\n", tmp);
255                 ret = -1;
256         }
257         
258         #if TRACE_COKE
259         printf("Coke_DoDispense: Updating slot status\n");
260         #endif
261         // Update status
262         Writef("s%i\r\n", Item);
263         len = ReadLine(sizeof tmp, tmp);
264         if(len == -1)   gaCoke_CachedStatus[Item] = -1;
265         Coke_int_GetSlotStatus(tmp, Item);
266         
267         // Release and return
268         pthread_mutex_unlock(&gCoke_Mutex);
269         
270         return ret;
271 }
272
273 char ReadChar()
274 {
275         fd_set  readfs;
276         char    ch = 0;
277          int    ret;
278         struct timeval  timeout;
279         
280         timeout.tv_sec = READ_TIMEOUT;
281         timeout.tv_usec = 0;
282         
283         FD_ZERO(&readfs);
284         FD_SET(giCoke_SerialFD, &readfs);
285         
286         ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout);
287         if( ret == 0 )  return 0;       // Timeout
288         if( ret != 1 ) {
289                 printf("readchar return %i\n", ret);
290                 return 0;
291         }
292         
293         ret = read(giCoke_SerialFD, &ch, 1);
294         if( ret != 1 ) {
295                 printf("ret = %i\n", ret);
296                 return 0;
297         }
298         
299         return ch;
300 }
301
302 int Writef(const char *Format, ...)
303 {
304         va_list args;
305          int    len;
306         
307         va_start(args, Format);
308         len = vsnprintf(NULL, 0, Format, args);
309         va_end(args);
310         
311         {
312                 char    buf[len+1];
313                 va_start(args, Format);
314                 vsnprintf(buf, len+1, Format, args);
315                 va_end(args);
316                 
317                 #if DEBUG
318                 printf("Writef: %s", buf);
319                 #endif
320                 
321                 return write(giCoke_SerialFD, buf, len);
322         }
323         
324 }
325
326 int WaitForColon()
327 {
328         fd_set  readfs;
329         char    ch = 0;
330         
331         FD_SET(giCoke_SerialFD, &readfs);
332         
333         while( (ch = ReadChar()) != ':' && ch != 0);
334         
335         if( ch == 0 )   return -1;      // Timeout
336         
337         return 0;
338 }
339
340 int ReadLine(int len, char *output)
341 {
342         char    ch;
343          int    i = 0;
344         
345         for(;;)
346         {
347                 ch = ReadChar();
348                         
349                 if( i < len )
350                         output[i++] = ch;
351                 
352                 if( ch == '\0' ) {
353                         break;
354                 }
355                 if( ch == '\n' || ch == '\r' ) {
356                         if( i < len )
357                                 output[--i] = '\0';
358                         break;
359                 }
360         }
361
362         //printf("ReadLine: output=%s\n", output);
363
364         if( !ch )       return -1;
365         return i;
366 }
367
368

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