Fixes to new coke code, appears to work now
[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 <stdarg.h>
17 #include <pthread.h>
18 #include <unistd.h>
19 #include <modbus/modbus.h>
20 #include <errno.h>
21
22 #define MIN_DISPENSE_PERIOD     5
23
24 // === CONSTANTS ===
25 const int       ciCoke_MinPeriod = 5;
26 const int       ciCoke_DropBitBase = 1024;
27 const int       ciCoke_StatusBitBase = 16;
28
29 // === IMPORTS ===
30
31 // === PROTOTYPES ===
32  int    Coke_InitHandler();
33  int    Coke_CanDispense(int User, int Item);
34  int    Coke_DoDispense(int User, int Item);
35  int    Coke_int_ConnectToPLC(void);
36  int    Coke_int_GetSlotFromItem(int Item, int bDispensing);
37  int    Coke_int_IsSlotEmpty(int Slot);
38  int    Coke_int_DropSlot(int Slot);
39
40 // === GLOBALS ===
41 tHandler        gCoke_Handler = {
42         "coke",
43         Coke_InitHandler,
44         Coke_CanDispense,
45         Coke_DoDispense
46 };
47 const char      *gsCoke_ModbusAddress = "130.95.13.73";
48 modbus_t        *gCoke_Modbus;
49 time_t  gtCoke_LastDispenseTime;
50  int    gbCoke_DummyMode = 1;
51  int    giCoke_NextCokeSlot = 0;
52
53 // == CODE ===
54 int Coke_InitHandler()
55 {
56         // Configuable dummy/blank mode (all dispenses succeed)
57         // TODO: Find a better way of handling missing/invalid options
58         if( Config_GetValueCount("coke_dummy_mode") > 0 )
59         {
60                 gbCoke_DummyMode = Config_GetValue_Bool("coke_dummy_mode", 0);
61                 if(gbCoke_DummyMode == -1)      gbCoke_DummyMode = 0;
62         }
63
64         // Open modbus
65         if( !gbCoke_DummyMode )
66         {
67                 Coke_int_ConnectToPLC();
68         }
69
70         return 0;
71 }
72
73 int Coke_CanDispense(int UNUSED(User), int Item)
74 {
75          int    slot;
76         
77         // Check for 'dummy' mode
78         if( gbCoke_DummyMode )
79                 return 0;
80
81         // Get slot
82         slot = Coke_int_GetSlotFromItem(Item, 0);
83         if(slot < 0)    return -1;
84
85         return Coke_int_IsSlotEmpty(slot);
86 }
87
88 /**
89  * \brief Actually do a dispense from the coke machine
90  */
91 int Coke_DoDispense(int UNUSED(User), int Item)
92 {
93          int    slot;
94         // Check for 'dummy' mode
95         if( gbCoke_DummyMode )
96                 return 0;
97
98         // Get slot
99         slot = Coke_int_GetSlotFromItem(Item, 1);
100         if(slot < 0)    return -1;
101         
102         // Make sure there are not two dispenses within n seconds
103         if( time(NULL) - gtCoke_LastDispenseTime < ciCoke_MinPeriod )
104         {
105                  int    delay = ciCoke_MinPeriod - (time(NULL) - gtCoke_LastDispenseTime);
106                 printf("Wait %i seconds?\n", delay);
107                 sleep( delay );
108                 printf("wait done\n");
109         }
110
111         return Coke_int_DropSlot(slot);
112 }
113
114 // --- INTERNAL FUNCTIONS ---
115 int Coke_int_ConnectToPLC(void)
116 {
117         if( !gCoke_Modbus )
118         {
119                 gCoke_Modbus = modbus_new_tcp(gsCoke_ModbusAddress, 502);
120                 if( !gCoke_Modbus )
121                 {
122                         perror("coke - modbus_new_tcp");
123                         return 1;
124                 }
125         }
126         printf("Connecting to coke PLC machine on '%s'\n", gsCoke_ModbusAddress);
127         
128         if( modbus_connect(gCoke_Modbus) )
129         {
130                 perror("coke - modbus_connect");
131                 modbus_free(gCoke_Modbus);
132                 gCoke_Modbus = NULL;
133                 return 1;
134         }
135
136         return 0;
137 }
138
139 int Coke_int_GetSlotFromItem(int Item, int bDispensing)
140 {
141         if( Item < 0 || Item > 6 )      return -1;
142
143         // Non-coke slots
144         if( Item < 6 )
145                 return Item;
146         
147         // Iterate though coke slots and find the first one with a drink avaliable
148         // `giCoke_NextCokeSlot` ensures that the slots rotate
149         for( int i = 0; i < 4; i ++ )
150         {
151                 int slot = 6 + (i + giCoke_NextCokeSlot) % 4;
152                 if( !Coke_int_IsSlotEmpty(slot) )
153                 {
154                         if(bDispensing) {
155                                 giCoke_NextCokeSlot ++;
156                                 if(giCoke_NextCokeSlot == 4)    giCoke_NextCokeSlot = 0;
157                         }
158                         return slot;    // Drink avaliable
159                 }
160         }
161
162         // Coke is empty!
163         // - Return 6, even if it's empty, the checks elsewhere will avoid problems
164         return 6;
165 }
166
167 int Coke_int_IsSlotEmpty(int Slot)
168 {
169         uint8_t status;
170
171         if( Slot < 0 || Slot > 9 )      return -1;
172
173         if(!gCoke_Modbus)
174                 return -2;
175
176         errno = 0;
177         if( modbus_read_bits(gCoke_Modbus, ciCoke_StatusBitBase + Slot, 1, &status) <= 0 )
178         {
179                 // TODO: Retry connection, or fail
180                 perror("Coke_int_IsSlotEmpty - modbus_read_bits");
181                 return -2;
182         }
183
184         return status == 0;
185 }
186
187 int Coke_int_DropSlot(int Slot)
188 {
189         uint8_t res;
190
191         if(Slot < 0 || Slot > 9)        return -1;
192
193         // Can't dispense if the machine is not connected
194         if( !gCoke_Modbus )
195                 return -2;
196
197         // Check if a dispense is in progress
198         if( modbus_read_bits(gCoke_Modbus, ciCoke_DropBitBase + Slot, 1, &res) <= 0 )
199         {
200                 perror("Coke_int_DropSlot - modbus_read_bits#1");
201                 return -2;
202         }
203         if( res != 0 )
204         {
205                 // Manual dispense in progress
206                 return -1;
207         }
208
209         // Dispense
210         if( modbus_write_bit(gCoke_Modbus, ciCoke_DropBitBase + Slot, 1) < 0 )
211         {
212                 perror("Coke_int_DropSlot - modbus_write_bit");
213                 return -2;
214         }
215
216         // Check that it started
217         usleep(1000);   // 1ms
218         if( modbus_read_bits(gCoke_Modbus, ciCoke_DropBitBase + Slot, 1, &res) <= 0 )
219         {
220                 perror("Coke_int_DropSlot - modbus_read_bits#2");
221                 return -2;
222         }
223         if( res == 0 )
224         {
225                 // Oops!, no drink
226                 return 1;
227         }
228         
229         return 0;
230 }
231

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