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

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