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

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