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

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