User timeouts, compile fixes
[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
22 #define READ_TIMEOUT    2       // 2 seconds for ReadChar
23 #define TRACE_COKE      1
24
25 // === IMPORTS ===
26
27 // === PROTOTYPES ===
28  int    Coke_InitHandler();
29  int    Coke_CanDispense(int User, int Item);
30  int    Coke_DoDispense(int User, int Item);
31  int    Writef(const char *Format, ...);
32  int    WaitForColon();
33  int    ReadLine(int len, char *output);
34
35 // === GLOBALS ===
36 tHandler        gCoke_Handler = {
37         "coke",
38         Coke_InitHandler,
39         Coke_CanDispense,
40         Coke_DoDispense
41 };
42 char    *gsCoke_SerialPort = "/dev/ttyS0";
43  int    giCoke_SerialFD;
44 regex_t gCoke_StatusRegex;
45
46 // == CODE ===
47 int Coke_InitHandler()
48 {
49         printf("connecting to coke machine...\n");
50         
51         giCoke_SerialFD = InitSerial(gsCoke_SerialPort, 9600);
52         if( giCoke_SerialFD == -1 ) {
53                 fprintf(stderr, "ERROR: Unable to open coke serial port ('%s')\n", gsCoke_SerialPort);
54         }
55         else {
56                 // Reset the slot names.
57                 // - Dunno why this is needed, but the machine plays silly
58                 //   sometimes.
59                 Writef("n0 Slot0\n");
60                 WaitForColon();
61                 Writef("n1 Slot1\n");
62                 WaitForColon();
63                 Writef("n2 Slot2\n");
64                 WaitForColon();
65                 Writef("n3 Slot3\n");
66                 WaitForColon();
67                 Writef("n4 Slot4\n");
68                 WaitForColon();
69                 Writef("n5 Slot5\n");
70                 WaitForColon();
71                 Writef("n6 Coke\n");
72         }
73         
74         CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED);
75         return 0;
76 }
77
78 int Coke_CanDispense(int UNUSED(User), int Item)
79 {
80         char    tmp[40], *status;
81         regmatch_t      matches[4];
82          int    ret;
83
84         // Sanity please
85         if( Item < 0 || Item > 6 )      return -1;      // -EYOURBAD
86         
87         // Can't dispense if the machine is not connected
88         if( giCoke_SerialFD == -1 )
89                 return -2;
90         
91         #if TRACE_COKE
92         printf("Coke_CanDispense: Flushing\n");
93         #endif
94         
95         
96         // Wait for a prompt
97         ret = 0;
98         while( WaitForColon() && ret < 3 )
99         {
100                 // Flush the input buffer
101                 char    tmpbuf[512];
102                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
103                 #if TRACE_COKE
104                 printf("Coke_CanDispense: sending 'd7'\n");
105                 #endif
106                 Writef("d7\r\n");
107                 ret ++;
108         }
109         // Check for a timeout error
110         if( !(ret < 3) ) {
111                 fprintf(stderr, "Coke machine timed out\n");
112                 return -2;      // -EMYBAD
113         }
114         
115         // You need to do a 'd7' before reading the status
116         // - Otherwise it sometimes reports a full slot as empty
117         //   [TPG] (2011-02-19)
118         if( ret == 0 )
119         {
120                 Writef("d7\r\n");
121                 WaitForColon();
122         }
123
124         // TODO: Handle "not ok" response to D7
125         
126         #if TRACE_COKE
127         printf("Coke_CanDispense: sending 's%i'\n", Item);
128         #endif
129         
130         // Ask the coke machine
131         Writef("s%i\r\n", Item);
132
133         #if TRACE_COKE
134         printf("Coke_CanDispense: reading response\n");
135         #endif
136         // Read from the machine (ignoring empty lines)
137         while( (ret = ReadLine(sizeof(tmp)-1, tmp)) == 0 );
138         #if TRACE_COKE
139         printf("ret = %i, tmp = '%s'\n", ret, tmp);
140         #endif
141         // Read back-echoed lines
142         while( tmp[0] == ':' || tmp[1] != 'l' )
143         {
144                 ret = ReadLine(sizeof(tmp)-1, tmp);
145                 printf("ret = %i, tmp = '%s'\n", ret, tmp);
146         }
147
148         // Catch an error       
149         if( ret <= 0 ) {
150                 fprintf(stderr, "Coke machine is not being chatty (read = %i)\n", ret);
151                 if( ret == -1 ) {
152                         perror("Coke Machine");
153                 }
154                 return -1;
155         }
156         
157         #if TRACE_COKE
158         printf("Coke_CanDispense: wait for the prompt again\n");
159         #endif
160
161         // Eat rest of response
162         WaitForColon();
163
164         // Parse status response
165         ret = RunRegex(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
166         if( ret ) {
167                 return -1;
168         }
169
170         // Get slot status
171         tmp[ matches[3].rm_eo ] = '\0';
172         status = &tmp[ matches[3].rm_so ];
173         
174         #if TRACE_COKE
175         printf("Coke_CanDispense: Machine responded slot status '%s'\n", status);
176         #endif
177
178         if( strcmp(status, "full") == 0 )
179                 return 0;
180
181         return 1;
182 }
183
184 /**
185  * \brief Actually do a dispense from the coke machine
186  */
187 int Coke_DoDispense(int UNUSED(User), int Item)
188 {
189         char    tmp[32];
190          int    ret;
191
192         // Sanity please
193         if( Item < 0 || Item > 6 )      return -1;
194
195         // Can't dispense if the machine is not connected
196         if( giCoke_SerialFD == -1 )
197                 return -2;
198         
199         #if TRACE_COKE
200         printf("Coke_DoDispense: flushing input\n");
201         #endif
202         
203         // Wait for prompt
204         ret = 0;
205         while( WaitForColon() && ret < 3 )
206         {
207                 // Flush the input buffer
208                 char    tmpbuf[512];
209                 read(giCoke_SerialFD, tmpbuf, sizeof(tmpbuf));
210                 #if TRACE_COKE
211                 printf("Coke_DoDispense: sending 'd7'\n");
212                 #endif
213                 Writef("d7\r\n");
214         }
215
216         #if TRACE_COKE
217         printf("Coke_DoDispense: sending 'd%i'\n", Item);
218         #endif
219         // Dispense
220         Writef("d%i\r\n", Item);
221         
222         // Read empty lines and echo-backs
223         do {
224                 ret = ReadLine(sizeof(tmp)-1, tmp);
225                 if( ret == -1 ) return -1;
226                 #if TRACE_COKE
227                 printf("Coke_DoDispense: read %i '%s'\n", ret, tmp);
228                 #endif
229         } while( ret == 0 || tmp[0] == ':' || tmp[0] == 'd' );
230
231         WaitForColon(); // Eat up rest of response
232         
233         #if TRACE_COKE
234         printf("Coke_DoDispense: done\n");
235         #endif
236
237         // TODO: Regex
238         if( strcmp(tmp, "ok") == 0 ) {
239                 // We think dispense worked
240                 // - The machine returns 'ok' if an empty slot is dispensed, even if
241                 //   it doesn't actually try to dispense (no sound)
242                 return 0;
243         }
244
245         printf("Machine returned unknown value '%s'\n", tmp);   
246
247         return -1;
248 }
249
250 char ReadChar()
251 {
252         fd_set  readfs;
253         char    ch = 0;
254          int    ret;
255         struct timeval  timeout;
256         
257         timeout.tv_sec = READ_TIMEOUT;
258         timeout.tv_usec = 0;
259         
260         FD_ZERO(&readfs);
261         FD_SET(giCoke_SerialFD, &readfs);
262         
263         ret = select(giCoke_SerialFD+1, &readfs, NULL, NULL, &timeout);
264         if( ret == 0 )  return 0;       // Timeout
265         if( ret != 1 ) {
266                 printf("readchar return %i\n", ret);
267                 return 0;
268         }
269         
270         ret = read(giCoke_SerialFD, &ch, 1);
271         if( ret != 1 ) {
272                 printf("ret = %i\n", ret);
273                 return 0;
274         }
275         
276         return ch;
277 }
278
279 int Writef(const char *Format, ...)
280 {
281         va_list args;
282          int    len;
283         
284         va_start(args, Format);
285         len = vsnprintf(NULL, 0, Format, args);
286         va_end(args);
287         
288         {
289                 char    buf[len+1];
290                 va_start(args, Format);
291                 vsnprintf(buf, len+1, Format, args);
292                 va_end(args);
293                 
294                 #if DEBUG
295                 printf("Writef: %s", buf);
296                 #endif
297                 
298                 return write(giCoke_SerialFD, buf, len);
299         }
300         
301 }
302
303 int WaitForColon()
304 {
305         fd_set  readfs;
306         char    ch = 0;
307         
308         FD_SET(giCoke_SerialFD, &readfs);
309         
310         while( (ch = ReadChar()) != ':' && ch != 0);
311         
312         if( ch == 0 )   return -1;      // Timeout
313         
314         return 0;
315 }
316
317 int ReadLine(int len, char *output)
318 {
319         char    ch;
320          int    i = 0;
321         
322         for(;;)
323         {
324                 ch = ReadChar();
325                         
326                 if( i < len )
327                         output[i++] = ch;
328                 
329                 if( ch == '\0' ) {
330                         break;
331                 }
332                 if( ch == '\n' || ch == '\r' ) {
333                         if( i < len )
334                                 output[--i] = '\0';
335                         break;
336                 }
337         }
338
339         //printf("ReadLine: output=%s\n", output);
340
341         if( !ch )       return -1;
342         return i;
343 }
344
345

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