├── CoffeeCardSystem_GPRS_GIT.ino ├── MDB.c ├── MDB.h ├── MDB_MasterThroughPC.ino ├── MDB_SlaveAutonomous.ino ├── README.md ├── USART.c ├── USART.h ├── USART_ISR.c ├── bit_reg_defs.h ├── libs └── foo.txt ├── mdb-interface.kicad_sch ├── mdb_interface_specification.pdf ├── ringBuf.c ├── ringBuf.h └── source └── foo.txt /CoffeeCardSystem_GPRS_GIT.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // SoftwareSerial for debugging 7 | //#define SOFTWARESERIAL_DEBUG_ENABLED 1 8 | 9 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 10 | #define RX_DEBUG_PIN 2 11 | #define TX_DEBUG_PIN 3 12 | #endif 13 | 14 | // SIMCOM GPRS Module Settings 15 | #define SIMCOM_RX_PIN 7 16 | #define SIMCOM_TX_PIN 8 17 | #define SIMCOM_PW_PIN 9 18 | // SPI Slaves Settings 19 | #define RC522_SLS_PIN 4 // MFRC522 SlaveSelect Pin 20 | #define RC522_RST_PIN 5 // SPI RESET Pin 21 | 22 | 23 | typedef enum {GET, POST, HEAD} HTTP_Method; // 'keys' for HTTP functions 24 | 25 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 26 | SoftwareSerial Debug(RX_DEBUG_PIN, TX_DEBUG_PIN); 27 | #endif 28 | 29 | // SIMCOM module instance 30 | SoftwareSerial SIM900(SIMCOM_RX_PIN, SIMCOM_TX_PIN); 31 | // Create MFRC522 instance 32 | MFRC522 mfrc522(RC522_SLS_PIN, RC522_RST_PIN); 33 | // String object for containing HEX represented UID 34 | String uid_str_obj = ""; 35 | // Store the time of CSH_S_ENABLED start 36 | uint32_t global_time = 0; 37 | // Static Server port and address 38 | String server_url_str = "http://xxx.xxx.xxx.xxx:xxx/api/Vending/"; 39 | //uint8_t in_progress = 0; 40 | 41 | void setup() { 42 | // For debug LEDs 43 | DDRC |= 0b00111111; // PORTC LEDs for 6 commands in MDB_CommandHandler() switch-case 44 | 45 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 46 | Debug.begin(9600); 47 | Debug.println(F("Debug mode enabled")); 48 | #endif 49 | // == Init card reader START == 50 | SPI.begin(); // Init SPI bus, MANDATORY 51 | mfrc522.PCD_Init(); 52 | // == Init card reader END == 53 | SIM900_Init(); 54 | MDB_Init(); 55 | sei(); 56 | } 57 | 58 | void loop() { 59 | MDB_CommandHandler(); 60 | sessionHandler(); 61 | // without this delay READER_ENABLE command won't be in RX Buffer 62 | // Establish the reason of this later (maybe something is wrong with RX buffer reading) 63 | if (CSH_GetDeviceState() == CSH_S_DISABLED) 64 | delay(10); 65 | } 66 | 67 | /* 68 | * Handler for two Cashless Device active states: 69 | * ENABLED -- device is waiting for a new card 70 | * VEND -- device is busy making transactions with the server 71 | */ 72 | void sessionHandler(void) 73 | { 74 | switch(CSH_GetDeviceState()) 75 | { 76 | case CSH_S_ENABLED : RFID_readerHandler(); break; 77 | // check timeout 78 | case CSH_S_SESSION_IDLE : timeout(global_time, FUNDS_TIMEOUT * 1000); break; 79 | case CSH_S_VEND : transactionHandler(); break; 80 | default : break; 81 | } 82 | } 83 | /* 84 | * Waiting for RFID tag routine 85 | * I a new card is detected and it is present in the server's database, 86 | * server replies with available funds 87 | * then set them via functions from MDB.h, 88 | * and turn Cashless Device Poll Reply as BEGIN_SESSION 89 | */ 90 | void RFID_readerHandler(void) 91 | { 92 | String old_uid_str_obj; 93 | String new_uid_str_obj; 94 | 95 | String http_request; 96 | uint16_t status_code = 0; 97 | uint16_t user_funds = 0; 98 | 99 | // Look for new cards 100 | if ( ! mfrc522.PICC_IsNewCardPresent()) 101 | return; 102 | 103 | // Select one of the cards 104 | if ( ! mfrc522.PICC_ReadCardSerial()) 105 | return; 106 | // in_progress = 0; 107 | // Convert UID into string represented HEX number 108 | // in reversed order (for human recognition) 109 | getUIDStrHex(&mfrc522, &new_uid_str_obj); 110 | 111 | // Check if there is no UID -- then just return 112 | if (new_uid_str_obj.length() == 0) 113 | return; 114 | // Check if previous UID == UID that was just read 115 | // to prevent an infinite HTTP GET requests 116 | if (uid_str_obj == new_uid_str_obj) 117 | return; 118 | // Now update the global UID 119 | uid_str_obj = new_uid_str_obj; 120 | 121 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 122 | Debug.println(uid_str_obj); 123 | #endif 124 | 125 | // HTTP handling 126 | http_request = server_url_str + uid_str_obj; 127 | submitHTTPRequest(GET, http_request, &status_code, &user_funds); 128 | 129 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 130 | Debug.print(F("HTTP Status Code: ")); 131 | Debug.println(status_code); 132 | #endif 133 | 134 | if (status_code == 200) 135 | { 136 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 137 | Debug.print(F("Funds: ")); 138 | Debug.println(user_funds); 139 | Debug.println(F("Poll State: Begin Session")); 140 | #endif 141 | CSH_SetUserFunds(user_funds); // Set current user funds 142 | CSH_SetPollState(CSH_BEGIN_SESSION); // Set Poll Reply to BEGIN SESSION 143 | CSH_SetDeviceState(CSH_S_PROCESSING); 144 | global_time = millis(); 145 | return; 146 | } 147 | // If we got here that means we didn't receive permission for transaction 148 | // so clear global UID string and global_time 149 | uid_str_obj = ""; 150 | global_time = 0; 151 | /* 152 | * SET FUNDS AND CONNECTION TIMEOUT SOMEWHERE, TO CANCEL SESSION AFTER 10 SECONDS !!! 153 | */ 154 | } 155 | /* 156 | * Transaction routine 157 | * At this point item_cost and item_number (or vend_amount) 158 | * are should be set by VendRequest() (in MDB.c) 159 | */ 160 | void transactionHandler(void) 161 | { 162 | String http_request; 163 | uint16_t status_code = 0; 164 | uint16_t user_funds = 0; 165 | 166 | uint16_t item_cost = CSH_GetItemCost(); 167 | uint16_t item_numb = CSH_GetVendAmount(); 168 | 169 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 170 | Debug.println(F("Vend Request")); 171 | #endif 172 | 173 | http_request = server_url_str + uid_str_obj + "/" + String(item_cost); 174 | uid_str_obj = ""; 175 | global_time = 0; 176 | submitHTTPRequest(POST, http_request, &status_code, &user_funds); 177 | 178 | if (status_code != 202) 179 | { 180 | CSH_SetPollState(CSH_VEND_DENIED); 181 | CSH_SetDeviceState(CSH_S_PROCESSING); 182 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 183 | Debug.println(F("Vend Denied")); 184 | #endif 185 | return; 186 | } 187 | // If code is 202 -- tell VMC that vend is approved 188 | CSH_SetPollState(CSH_VEND_APPROVED); 189 | CSH_SetDeviceState(CSH_S_PROCESSING); 190 | // in_progress = 1; 191 | 192 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 193 | Debug.println(F("Vend Approved")); 194 | #endif 195 | } 196 | 197 | /* 198 | * 20-02-2016 199 | * Convert byte array with size of (4..10) 8-bit hex numbers into a string object 200 | * Example: array of 4 hex numbers, which represents 32-bit unsigned long: 201 | * {0x36, 0x6B, 0x1B, 0xDB} represents a 32-bit (4-word) number 0xDB1B6B36, which is converted into a string "63B6B1BD" 202 | * This string represented hex number appears reversed for human recognition, 203 | * although technically the least 4-bit hex digit accords to the first letter of a string, uuid_str[0] 204 | * and the most significant 4-bit hex digit placed as a last char element (except for '\0') 205 | * 206 | * changes the String &uid_str object by pointer, considered as output 207 | */ 208 | void getUIDStrHex(MFRC522 *card, String *uid_str) 209 | { 210 | char uuid_str[2 * card->uid.size]; 211 | 212 | // This cycle makes a conversion of the following manner for each 8-bit number: {0x1B} --> {0x0B, 0x01} 213 | for (byte i = 0; i < card->uid.size; ++i) { 214 | uuid_str[2 * i] = card->uid.uidByte[i] & 0b1111; 215 | uuid_str[2 * i + 1] = card->uid.uidByte[i] >> 4; 216 | } 217 | //uuid_str[2 * card->uid.size + 1] = '\0'; // Add a null-terminator to make it a C-style string 218 | 219 | /* 220 | * This cycle adds 0x30 or (0x41 - 0x0A) to actual numbers according to ASCII table 221 | * 0x00 to 0x09 digits become '0' to '9' chars (add 0x30) 222 | * 0x0A to 0x0F digits become 'A' to 'F' chars in UPPERCASE (add 0x41 and subtract 0x0A) 223 | * Last thing is to copy that into a String object, which is easier to handle 224 | */ 225 | for (byte i = 0; i < 2 * card->uid.size; ++i) 226 | { 227 | if (uuid_str[i] < 0x0A) 228 | uuid_str[i] = (uuid_str[i] + 0x30); 229 | else 230 | uuid_str[i] = (uuid_str[i] - 0x0A + 0x41); 231 | *uid_str += uuid_str[i]; 232 | } 233 | } 234 | 235 | void setUID(String uid_str) 236 | { 237 | 238 | } 239 | String getUID(void) 240 | { 241 | 242 | } 243 | 244 | /* 245 | * Send HTTP request 246 | * input arguments: enum HTTPMethod -- GET, POST or HEAD 247 | * String request -- request string of structure http://server_address:server_port/path 248 | * uint8_t *http_status_code -- returned http code by pointer 249 | * uint16_t *user_funds -- returned user funds by pointer 250 | * returned value: none 251 | */ 252 | void submitHTTPRequest(HTTP_Method method, String http_link, uint16_t *status_code, uint16_t *user_funds) 253 | { 254 | String response; 255 | uint8_t start_index; 256 | uint8_t end_index; 257 | // uint8_t data_len; 258 | // Commands for maitainance 259 | // SIM900_SendCommand(F("AT")); 260 | // SIM900_SendCommand(F("CPIN?")); 261 | // SIM900_SendCommand(F("CSQ")); 262 | // SIM900_SendCommand(F("CREG?")); 263 | // SIM900_SendCommand(F("CGATT?")); 264 | 265 | // TCP config 266 | // SIM900_SendCommand(F("CIPSHUT")); 267 | // SIM900_SendCommand(F("CSTT=\"internet.beeline.ru\",\"beeline\",\"beeline\"")); 268 | // SIM900_SendCommand(F("CIICR")); 269 | // SIM900_SendCommand(F("CIFSR")); 270 | // SIM900_SendCommand(F("CIPSTATUS")); 271 | 272 | // Make connection and HTTP request 273 | SIM900_SendCommand(F("SAPBR=1,1")); // Enable bearer 274 | SIM900_SendCommand(F("HTTPINIT")); 275 | SIM900_SendCommand("HTTPPARA=\"URL\",\"" + http_link + "\""); // cant put these two strings into flash memory 276 | SIM900_SendCommand(F("HTTPPARA=\"CID\",1")); 277 | 278 | switch (method) 279 | { //submit the request 280 | case GET : response = SIM900_SendCommand(F("HTTPACTION=0")); break; // GET 281 | case POST : response = SIM900_SendCommand(F("HTTPACTION=1")); break; // POST 282 | // case HEAD : response = SIM900_SendCommand(F("HTTPACTION=2")); break; // HEAD // not needed for now, disabled to free Flash memory 283 | default : return; 284 | } // optimize for flash/RAM space, HTTPACTION repeats 3 times 285 | /* 286 | * Read the HTTP code 287 | * Successful response is of the following format (without angular brackets): 288 | * OK\r\n+HTTPACTION:,,\r\n 289 | * so search for the first comma 290 | */ 291 | start_index = response.indexOf(',') + 1; 292 | end_index = response.indexOf(',', start_index); 293 | *status_code = (uint16_t)response.substring(start_index, end_index).toInt(); 294 | 295 | if (*status_code == 200) // read the response 296 | { 297 | /* 298 | * Successful response is of the following format: 299 | * AT+HTTPREAD\r\n+HTTPREAD:,\r\nOK\r\n 300 | * so search for the first ':', read data_len until '\r' 301 | * then read the data itself 302 | */ 303 | response = SIM900_SendCommand(F("HTTPREAD")); 304 | start_index = response.indexOf(':') + 1; 305 | end_index = response.indexOf('\r', start_index); 306 | // data_len = (uint8_t)response.substring(start_index, end_index).toInt(); 307 | // now parse string from a new start_index to start_index + data_len 308 | start_index = response.indexOf('\n', end_index) + 1; 309 | // *user_funds = (uint16_t)response.substring(start_index, start_index + data_len).toInt(); 310 | // // or until '\r', which takes less Flash memory 311 | end_index = response.indexOf('\r', start_index); 312 | *user_funds = (uint16_t)response.substring(start_index, end_index).toInt(); 313 | } 314 | // These two are must be present in the end 315 | SIM900_SendCommand(F("HTTPTERM")); // Terminate session, mandatory 316 | SIM900_SendCommand(F("SAPBR=0,1")); // Disable bearer, mandatory 317 | } 318 | /* 319 | * Initiate SIMCOM SIM900 Module 320 | */ 321 | void SIM900_Init(void) 322 | { 323 | SIM900_PowerON(); 324 | SIM900.begin(19200); 325 | // HTTP Config 326 | SIM900_SendCommand(F("SAPBR=3,1,\"CONTYPE\",\"GPRS\"")); //setting the SAPBR, the connection type is using gprs 327 | SIM900_SendCommand(F("SAPBR=3,1,\"APN\",\"internet.beeline.ru\"")); //setting the APN, the second need you fill in your local apn server 328 | } 329 | /* 330 | * Turn module ON (not global power, only functionality) 331 | * Arguments: 332 | * uint8_t fun : 0 -- Minimum functionality 333 | * 1 -- Full functionality 334 | * 4 -- Disable phone transmit and receive RF circuits 335 | * uint8_t rst : 0 -- Do not reset the ME before setting it to power level 336 | * 1 -- Reset the ME before setting it to power level 337 | * RetVal: none 338 | */ 339 | void SIM900_TurnOn(uint8_t fun, uint8_t rst) 340 | { 341 | String command = F("CFUN="); 342 | String send_comm; 343 | switch (fun) 344 | { 345 | case 0 : send_comm = command + "0"; break; 346 | case 1 : send_comm = command + "1"; break; 347 | case 4 : send_comm = command + "4"; break; 348 | default : return; 349 | } 350 | send_comm += ","; 351 | switch (rst) 352 | { 353 | case 0 : send_comm += "0"; break; 354 | case 1 : send_comm += "1"; break; 355 | default : return; 356 | } 357 | SIM900_SendCommand(send_comm); // Turn module ON: 1 - full functionality, do not reset 358 | } 359 | /* 360 | * Send command to GSM/GPRS modem 361 | * accepts AT command (SIMCOM modules) 362 | * returns response String object 363 | */ 364 | String SIM900_SendCommand(String command) 365 | { 366 | String at = F("AT+"); 367 | SIM900.println(at + command); 368 | return verifyResponse(command); 369 | } 370 | /* 371 | * Wait for reply for the command from SIMCOM module and return it 372 | * returns the reply (String object) 373 | */ 374 | String verifyResponse(String command) 375 | { 376 | String buff_str; 377 | String word_chk; 378 | while ( !(SIM900.available()) ) 379 | ; // wait for data on the bus 380 | // delay(300); // this delay may be needed, but for now everything works without it 381 | 382 | if (command.indexOf(F("CIFSR")) != -1) 383 | word_chk = F("."); // because there is no OK response to this command, only IP address xxx.xxx.xxx.xxx, which always contains dots 384 | else if (command.indexOf(F("HTTPACTION")) != -1) 385 | word_chk = F("+HTTPACTION:"); // because after OK there is still a message to wait 386 | else 387 | word_chk = F("OK"); 388 | 389 | while (1) 390 | { 391 | if (SIM900.available()) 392 | { 393 | buff_str += char(SIM900.read()); 394 | if ( (buff_str.indexOf(word_chk) != -1) && (buff_str.endsWith(F("\r\n"))) ) 395 | break; // wait for "+HTTPACTION:num,num,num\r\n" reply 396 | else if ( buff_str.indexOf(F("ERROR")) > 0 ) 397 | break; 398 | } 399 | } 400 | // debug 401 | // Serial.println(buff_str); 402 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 403 | Debug.println(buff_str); 404 | #endif 405 | return buff_str; 406 | } 407 | 408 | /* 409 | * Turn SIM900 Power ON 410 | */ 411 | void SIM900_PowerON(void) 412 | { 413 | pinMode(SIMCOM_PW_PIN, OUTPUT); 414 | digitalWrite(SIMCOM_PW_PIN, LOW); 415 | delay(1000); 416 | digitalWrite(SIMCOM_PW_PIN, HIGH); 417 | delay(2000); 418 | digitalWrite(SIMCOM_PW_PIN, LOW); 419 | delay(3000); 420 | } 421 | 422 | /* 423 | * Turn SIM900 Power OFF 424 | * Argument: 425 | * uint8_t mode: 0 -- turn off Urgently (won't send anything back) 426 | * 1 -- turn off Normally (will send Normal Power Down) 427 | * RetVal: none 428 | */ 429 | void SIM900_PowerOff(uint8_t mode) 430 | { 431 | String command = F("AT+CPOWD="); 432 | switch (mode) 433 | { 434 | case 0 : SIM900.println (command + "0"); break; // println because we don't wait for any response 435 | case 1 : SIM900_SendCommand(command + "1"); break; 436 | default : return; 437 | } 438 | } 439 | /* 440 | * Checks if timeout has ran off 441 | * Simply compares start_time with timeout_value 442 | * Calls terminateSession() if time ran out 443 | * Can only be called in CSH_S_SESSION_IDLE state 444 | */ 445 | void timeout(uint32_t start_time, uint32_t timeout_value) 446 | { 447 | if (CSH_GetDeviceState() != CSH_S_SESSION_IDLE) 448 | return; 449 | // if (in_progress == 1) 450 | // return; 451 | if (millis() - start_time >= timeout_value) 452 | terminateSession(); 453 | } 454 | /* 455 | * Terminates current session and sets device back to ENABLED state 456 | * This one called when timeout ran out or return button was pressed 457 | */ 458 | void terminateSession(void) 459 | { 460 | CSH_SetPollState(CSH_SESSION_CANCEL_REQUEST); 461 | CSH_SetDeviceState(CSH_S_ENABLED); 462 | uid_str_obj = ""; 463 | global_time = 0; 464 | #ifdef SOFTWARESERIAL_DEBUG_ENABLED 465 | Debug.println(F("Session terminated")); 466 | #endif 467 | } 468 | 469 | -------------------------------------------------------------------------------- /MDB.c: -------------------------------------------------------------------------------- 1 | #include "MDB.h" 2 | #include "USART.h" 3 | #include // For LED debug purposes 4 | 5 | VMC_Config_t vmc_config = {0, 0, 0, 0}; 6 | VMC_Prices_t vmc_prices = {0, 0}; 7 | 8 | // Available user funds, changed via server connection in the Enabled state 9 | uint16_t user_funds = 0x0000; 10 | // Selected item price, changed via VMC_VEND_REQUEST 11 | uint16_t item_cost = 0x0000; 12 | // Selected item amount, changed via VMC_VEND_REQUEST 13 | // This one is not necessarily the ampunt of selected items to dispense, 14 | // it also can be a single item position value in the VMC memory 15 | uint16_t vend_amount = 0x0000; 16 | uint8_t csh_error_code = 0; 17 | // Cashless Device State and Poll State at a power-up 18 | uint16_t csh_poll_state = CSH_JUST_RESET; 19 | uint8_t csh_state = CSH_S_INACTIVE; 20 | CSH_Config_t csh_config = { 21 | 0x01, // featureLevel 22 | /* 23 | * Russia's country code is 810 or 643, 24 | * which translates into 0x18, 0x10 25 | * or 0x16, 0x43 26 | */ 27 | // 0x18, // country code H 28 | // 0x10, // country code L 29 | 0x00, 30 | 0x00, 31 | /* 32 | * The VMC I work with accepts only 2 decimal places, 33 | * which is reasonable, considering 1 RUB = 100 Kopecks. 34 | * But actually there are no kopecks 35 | * in any item prices, rubles only. 36 | * As a coin acceptor handles coins with minimum value of 1 RUB, 37 | * I chose scaling factor 0d100 (0x64), 38 | * which makes calculations easier to understand. 39 | * Example: Funds available -- 1600.00 RUB 40 | * Scale factor -- 100 41 | * Decimal places -- 2 42 | * This makes internal funds value 1600, or 0x0640 in HEX 43 | * which divides into 0x06 and 0x40 44 | * for two uint8_t internal variables 45 | */ 46 | 0x64, // Scale Factor 47 | 0x02, // Decimal Places 48 | FUNDS_TIMEOUT, // Max Response Time 49 | 0b00001001 // Misc Options 50 | }; 51 | 52 | 53 | void MDB_Init(void) 54 | { 55 | USART_Init(9600); 56 | } 57 | /* 58 | * 59 | */ 60 | void MDB_CommandHandler(void) 61 | { 62 | uint16_t command = 0; 63 | // MDB_Read(&command); 64 | // wait for commands only for our cashless device 65 | do { 66 | MDB_Read(&command); 67 | } while ((command < VMC_RESET) || (command > VMC_EXPANSION)); 68 | 69 | // switch (command) 70 | // { 71 | // case VMC_RESET : MDB_ResetHandler(); break; 72 | // case VMC_SETUP : MDB_SetupHandler(); break; 73 | // case VMC_POLL : MDB_PollHandler(); break; 74 | // case VMC_VEND : MDB_VendHandler(); break; 75 | // case VMC_READER : MDB_ReaderHandler(); break; 76 | // case VMC_EXPANSION : MDB_ExpansionHandler(); break; 77 | // default : break; 78 | // } 79 | 80 | switch (command) 81 | { 82 | case VMC_RESET : /*PORTC ^= (1 << 0);*/ MDB_ResetHandler(); break; 83 | case VMC_SETUP : /*PORTC ^= (1 << 1);*/ MDB_SetupHandler(); break; 84 | case VMC_POLL : /*PORTC ^= (1 << 2);*/ MDB_PollHandler(); break; 85 | case VMC_VEND : /*PORTC ^= (1 << 3);*/ MDB_VendHandler(); break; 86 | case VMC_READER : /*PORTC ^= (1 << 4);*/ MDB_ReaderHandler(); break; 87 | case VMC_EXPANSION : /*PORTC ^= (1 << 5);*/ MDB_ExpansionHandler(); break; 88 | default : break; 89 | } 90 | switch (csh_state) 91 | { 92 | case CSH_S_INACTIVE : /*PORTC = 0; PORTC |= (1 << 0);*/ break; 93 | case CSH_S_DISABLED : /*PORTC = 0; PORTC |= (1 << 1);*/ break; 94 | case CSH_S_ENABLED : PORTC = 0; PORTC |= (1 << 2); break; 95 | case CSH_S_SESSION_IDLE : PORTC = 0; PORTC |= (1 << 3); break; 96 | case CSH_S_VEND : PORTC = 0; PORTC |= (1 << 4); break; 97 | default : break; 98 | } 99 | // // State Machine Logic 100 | // switch (csh_state) 101 | // { 102 | // case CSH_S_INACTIVE : //PORTC = 0; PORTC |= (1 << 0); 103 | // { 104 | // switch (command) 105 | // { 106 | // case VMC_RESET : MDB_ResetHandler(); break; //PORTC ^= (1 << 0); 107 | // case VMC_POLL : MDB_PollHandler(); break; 108 | // case VMC_SETUP : MDB_SetupHandler(); break; 109 | // case VMC_EXPANSION : MDB_ExpansionHandler(); break; 110 | // default : break; 111 | // } 112 | // }; break; 113 | // case CSH_S_DISABLED : //PORTC = 0; PORTC |= (1 << 1); 114 | // { 115 | // switch (command) 116 | // { 117 | // case VMC_RESET : MDB_ResetHandler(); break; //PORTC ^= (1 << 0); 118 | // case VMC_SETUP : MDB_SetupHandler(); break; 119 | // // case VMC_POLL : csh_poll_state = CSH_ACK; MDB_PollHandler(); break; 120 | // case VMC_READER : MDB_ReaderHandler(); break; 121 | // case VMC_EXPANSION : MDB_ExpansionHandler(); break; 122 | // default : break; 123 | // } 124 | // }; break; 125 | // case CSH_S_ENABLED : // PORTC = 0; PORTC |= (1 << 5); 126 | // { 127 | // switch (command) 128 | // { 129 | // case VMC_RESET : MDB_ResetHandler(); break; // PORTC ^= (1 << 0); 130 | // case VMC_POLL : MDB_PollHandler(); break; 131 | // case VMC_VEND : MDB_VendHandler(); break; 132 | // case VMC_READER : MDB_ReaderHandler(); break; 133 | // case VMC_EXPANSION : MDB_ExpansionHandler(); break; 134 | 135 | // default : break; 136 | // } 137 | // }; break; 138 | // case CSH_S_SESSION_IDLE : 139 | // { 140 | // switch (command) 141 | // { 142 | // case VMC_RESET : PORTC ^= (1 << 0); MDB_ResetHandler(); break; 143 | // case VMC_SETUP : PORTC ^= (1 << 1); MDB_SetupHandler(); break; 144 | // case VMC_POLL : PORTC ^= (1 << 2); MDB_PollHandler(); break; 145 | // case VMC_VEND : PORTC ^= (1 << 3); MDB_VendHandler(); break; 146 | // case VMC_READER : PORTC ^= (1 << 4); MDB_ReaderHandler(); break; 147 | // case VMC_EXPANSION : PORTC ^= (1 << 5); MDB_ExpansionHandler(); break; 148 | // default : break; 149 | // } 150 | // }; break; 151 | // case CSH_S_VEND : //PORTC = 0; PORTC |= (1 << 4); 152 | // { 153 | // switch (command) 154 | // { 155 | 156 | // } 157 | // }; break; 158 | 159 | // default : break; 160 | // } 161 | } 162 | /* 163 | * Handles Just Reset sequence 164 | */ 165 | void MDB_ResetHandler(void) 166 | { 167 | Reset(); 168 | } 169 | /* 170 | * Handles Setup sequence 171 | */ 172 | void MDB_SetupHandler(void) 173 | { 174 | uint8_t i; // counter 175 | uint8_t checksum = VMC_SETUP; 176 | uint16_t vmc_temp; 177 | uint8_t vmc_data[6]; 178 | /* 179 | * wait for the whole frame 180 | * frame structure: 181 | * 1 subcommand + 4 vmc_config fields + 1 Checksum byte 182 | * 6 elements total 183 | */ 184 | while (1) 185 | if (MDB_DataCount() > 5) 186 | break; 187 | // Store frame in array 188 | for (i = 0; i < 6; ++i) 189 | { 190 | MDB_Read(&vmc_temp); 191 | vmc_data[i] = (uint8_t)(vmc_temp & 0x00FF); // get rid of Mode bit if present 192 | } 193 | // calculate checksum excluding last read element, which is a received checksum 194 | // for (i = 0; i < 5; ++i) 195 | checksum += calc_checksum(vmc_data, 5); 196 | // compare calculated and received checksums 197 | if (checksum != vmc_data[5]) 198 | { 199 | MDB_Send(CSH_NAK); 200 | return; // checksum mismatch, error 201 | } 202 | // vmc_data[0] is a SETUP Config Data or Max/Min Prices identifier 203 | switch(vmc_data[0]) 204 | { 205 | case VMC_CONFIG_DATA : { 206 | // Store VMC data 207 | vmc_config.featureLevel = vmc_data[1]; 208 | vmc_config.displayColumns = vmc_data[2]; 209 | vmc_config.displayRows = vmc_data[3]; 210 | vmc_config.displayInfo = vmc_data[4]; 211 | ConfigInfo(); 212 | }; break; 213 | 214 | case VMC_MAX_MIN_PRICES : { 215 | // Store VMC Prices 216 | vmc_prices.maxPrice = ((uint16_t)vmc_data[1] << 8) | vmc_data[2]; 217 | vmc_prices.minPrice = ((uint16_t)vmc_data[3] << 8) | vmc_data[4]; 218 | // Send ACK 219 | MDB_Send(CSH_ACK); 220 | // Change state to DISABLED 221 | csh_state = CSH_S_DISABLED; 222 | }; break; 223 | 224 | default : break; 225 | } 226 | } 227 | /* 228 | * Handles Poll replies 229 | */ 230 | void MDB_PollHandler(void) 231 | { 232 | switch(csh_poll_state) 233 | { 234 | case CSH_ACK : MDB_Send(CSH_ACK); break; // if no data is to send, answer with ACK 235 | case CSH_SILENCE : break; 236 | case CSH_JUST_RESET : JustReset(); break; 237 | case CSH_READER_CONFIG_INFO : ConfigInfo(); break; 238 | case CSH_DISPLAY_REQUEST : DisplayRequest(); break; // <<<=== Global Display Message 239 | case CSH_BEGIN_SESSION : BeginSession(); break; // <<<=== Global User Funds 240 | case CSH_SESSION_CANCEL_REQUEST : SessionCancelRequest(); break; 241 | case CSH_VEND_APPROVED : VendApproved(); break; 242 | case CSH_VEND_DENIED : VendDenied(); break; 243 | case CSH_END_SESSION : EndSession(); break; 244 | case CSH_CANCELLED : Cancelled(); break; 245 | case CSH_PERIPHERAL_ID : PeripheralID(); break; 246 | case CSH_MALFUNCTION_ERROR : MalfunctionError(); break; 247 | case CSH_CMD_OUT_OF_SEQUENCE : CmdOutOfSequence(); break; 248 | case CSH_DIAGNOSTIC_RESPONSE : DiagnosticResponse(); break; 249 | default : break; 250 | } 251 | } 252 | /* 253 | * 254 | */ 255 | void MDB_VendHandler(void) 256 | { 257 | uint16_t subcomm_temp; 258 | uint8_t subcomm; 259 | 260 | // Wait for Subcommand (1 element total) 261 | while (1) 262 | if (MDB_DataCount() > 0) 263 | break; 264 | // Store Subcommand in array 265 | MDB_Read(&subcomm_temp); 266 | subcomm = (uint8_t)(subcomm_temp & 0x00FF); // get rid of Mode bit if present 267 | // Switch through subcommands 268 | switch(subcomm) 269 | { 270 | case VMC_VEND_REQUEST : VendRequest(); break; 271 | case VMC_VEND_CANCEL : VendDenied(); break; 272 | case VMC_VEND_SUCCESS : VendSuccessHandler(); break; 273 | case VMC_VEND_FAILURE : VendFailureHandler(); break; 274 | case VMC_VEND_SESSION_COMPLETE : VendSessionComplete(); break; 275 | case VMC_VEND_CASH_SALE : VendCashSale(); break; 276 | default : break; 277 | } 278 | } 279 | /* 280 | * 281 | */ 282 | void MDB_ReaderHandler(void) 283 | { 284 | uint8_t i; // counter 285 | uint8_t checksum = VMC_READER; 286 | uint16_t reader_temp; 287 | uint8_t reader_data[2]; 288 | // Wait for Subcommand and checksum (2 elements total) 289 | while (1) 290 | if (MDB_DataCount() > 1) 291 | break; 292 | // Store received data in array 293 | for (i = 0; i < 2; ++i) 294 | { 295 | MDB_Read(&reader_temp); 296 | reader_data[i] = (uint8_t)(reader_temp & 0x00FF); // get rid of Mode bit if present 297 | } 298 | // Calculate checksum 299 | checksum += calc_checksum(reader_data, 1); 300 | // Second element is an incoming checksum, compare it to the calculated one 301 | if (checksum != reader_data[1]) 302 | { 303 | MDB_Send(CSH_NAK); 304 | return; // checksum mismatch, error 305 | } 306 | // Look at Subcommand 307 | switch(reader_data[0]) 308 | { 309 | case VMC_READER_DISABLE : Disable(); break; 310 | case VMC_READER_ENABLE : Enable(); break; 311 | case VMC_READER_CANCEL : Cancelled(); break; 312 | default : break; 313 | } 314 | } 315 | /* 316 | * 317 | */ 318 | void MDB_ExpansionHandler(void) 319 | { 320 | uint16_t readCmd; 321 | 322 | MDB_Read(&readCmd); 323 | switch(readCmd) 324 | { 325 | case VMC_EXPANSION_REQUEST_ID : ExpansionRequestID(); break; 326 | case VMC_EXPANSION_DIAGNOSTICS : ExpansionDiagnostics(); break; 327 | // Actually I never got VMC_EXPANSION_DIAGNOSTICS subcommand, so whatever 328 | default : break; 329 | } 330 | // Not yet implemented 331 | } 332 | /* 333 | * ======================================== 334 | * Start of MDB Bus Communication functions 335 | * ======================================== 336 | */ 337 | void MDB_Send(uint16_t data) 338 | { 339 | USART_TXBuf_Write(data); 340 | } 341 | /* 342 | * Receive MDB Command/Reply/Data from the Bus 343 | */ 344 | void MDB_Read(uint16_t *data) 345 | { 346 | USART_RXBuf_Read(data); 347 | } 348 | /* 349 | * Peek at the oldest incoming data from the Bus 350 | */ 351 | void MDB_Peek(uint16_t *data) 352 | { 353 | USART_RXBuf_Peek(data); 354 | } 355 | 356 | uint8_t MDB_DataCount (void) 357 | { 358 | return USART_RXBuf_Count(); 359 | } 360 | /* 361 | * ====================================== 362 | * End of MDB Bus Communication functions 363 | * ====================================== 364 | */ 365 | 366 | /* 367 | * ============================================= 368 | * Start of Internal functions for main handlers 369 | * ============================================= 370 | */ 371 | void Reset(void) 372 | { 373 | // Reset all data 374 | vmc_config.featureLevel = 0; 375 | vmc_config.displayColumns = 0; 376 | vmc_config.displayRows = 0; 377 | vmc_config.displayInfo = 0; 378 | 379 | vmc_prices.maxPrice = 0; 380 | vmc_prices.minPrice = 0; 381 | 382 | csh_error_code = 0; 383 | // Send ACK, turn INACTIVE 384 | csh_state = CSH_S_INACTIVE; 385 | csh_poll_state = CSH_JUST_RESET; 386 | MDB_Send(CSH_ACK); 387 | 388 | while ( ! (USART_TXBuf_IsEmpty()) ) 389 | ; 390 | } 391 | 392 | void JustReset(void) 393 | { 394 | MDB_Send(CSH_JUST_RESET); 395 | Reset(); 396 | 397 | while ( ! (USART_TXBuf_IsEmpty()) ) 398 | ; 399 | } 400 | 401 | void ConfigInfo(void) 402 | { 403 | uint8_t checksum = 0; 404 | // calculate checksum, no Mode bit yet 405 | checksum = ( CSH_READER_CONFIG_INFO 406 | + csh_config.featureLevel 407 | + csh_config.countryCodeH 408 | + csh_config.countryCodeL 409 | + csh_config.scaleFactor 410 | + csh_config.decimalPlaces 411 | + csh_config.maxResponseTime 412 | + csh_config.miscOptions ); 413 | MDB_Send(CSH_READER_CONFIG_INFO); 414 | MDB_Send(csh_config.featureLevel); 415 | MDB_Send(csh_config.countryCodeH); 416 | MDB_Send(csh_config.countryCodeL); 417 | MDB_Send(csh_config.scaleFactor); 418 | MDB_Send(csh_config.decimalPlaces); 419 | MDB_Send(csh_config.maxResponseTime); 420 | MDB_Send(csh_config.miscOptions); 421 | MDB_Send(checksum | CSH_ACK); 422 | 423 | while ( ! (USART_TXBuf_IsEmpty()) ) 424 | ; 425 | } 426 | 427 | void DisplayRequest(void) 428 | { 429 | // char *message; 430 | // uint8_t i; 431 | // uint8_t checksum = CSH_DISPLAY_REQUEST; 432 | // uint8_t message_length; 433 | // // As in standard, the number of bytes must equal the product of Y3 and Y4 434 | // // up to a maximum of 32 bytes in the setup/configuration command 435 | // message_length = (vmc_config.displayColumns * vmc_config.displayRows); 436 | // message = (char *)malloc(message_length * sizeof(char)); 437 | 438 | 439 | // /* Here you need to copy the message into allocated memory 440 | 441 | 442 | // // Send al this on the Bus 443 | // MDB_Send(CSH_DISPLAY_REQUEST); 444 | // MDB_Send(0xAA); // Display time in arg * 0.1 second units 445 | // MDB_Send(0x34); 446 | // MDB_Send(0x38); 447 | // checksum += (0xAA + 0x34 + 0x38); 448 | // MDB_Send(checksum | CSH_ACK); 449 | 450 | // for(i = 0; i < message_length; ++i) 451 | // MDB_Send(*(message + i)); 452 | // free(message); 453 | } 454 | 455 | void BeginSession(void) // <<<=== Global User Funds 456 | { 457 | /* 458 | * I need this variable because my VMC doesn't want to send a 459 | * VMC_VEND command (0x113) after I answer it with a single BEGIN SESSION Poll Reply 460 | * So I decided to try it several times, that is what this counter for 461 | */ 462 | static uint8_t begin_session_counter = 0; 463 | // uint16_t reply; 464 | uint8_t checksum = 0; 465 | uint16_t funds = CSH_GetUserFunds(); 466 | uint8_t user_funds_H = (uint8_t)(funds >> 8); 467 | uint8_t user_funds_L = (uint8_t)(funds & 0x00FF); 468 | 469 | // Again, that ugly counter, experimentally established, that 10 times is enough 470 | begin_session_counter++; 471 | if (begin_session_counter >= 50) 472 | { 473 | csh_poll_state = CSH_ACK; 474 | csh_state = CSH_S_SESSION_IDLE; 475 | begin_session_counter = 0; 476 | return; 477 | } 478 | 479 | // calculate checksum, no Mode bit yet 480 | checksum = ( CSH_BEGIN_SESSION 481 | + user_funds_H 482 | + user_funds_L ); 483 | 484 | MDB_Send(CSH_BEGIN_SESSION); 485 | MDB_Send(user_funds_H); // upper byte of funds available 486 | MDB_Send(user_funds_L); // lower byte of funds available 487 | MDB_Send(checksum | CSH_ACK); // set Mode bit and send 488 | 489 | while ( ! (USART_TXBuf_IsEmpty()) ) 490 | ; 491 | // while (1) 492 | // if (MDB_DataCount() > 0) 493 | // break; 494 | // do { 495 | // MDB_Read(&reply); 496 | // } while (reply != CSH_ACK); 497 | // Initiate Session Idle State 498 | } 499 | 500 | void SessionCancelRequest(void) 501 | { 502 | MDB_Send(CSH_SESSION_CANCEL_REQUEST); 503 | MDB_Send(CSH_SESSION_CANCEL_REQUEST | CSH_ACK); // Checksum with Mode bit 504 | 505 | while ( ! (USART_TXBuf_IsEmpty()) ) 506 | ; 507 | } 508 | 509 | void VendApproved(void) 510 | { 511 | static uint8_t vend_approved_counter = 0; 512 | uint8_t checksum = 0; 513 | uint8_t vend_amount_H = (uint8_t)(vend_amount >> 8); 514 | uint8_t vend_amount_L = (uint8_t)(vend_amount & 0x00FF); 515 | 516 | vend_approved_counter++; 517 | if (vend_approved_counter >= 50) 518 | { 519 | csh_poll_state = CSH_END_SESSION; 520 | vend_approved_counter = 0; 521 | return; 522 | } 523 | // calculate checksum, no Mode bit yet 524 | checksum = ( CSH_VEND_APPROVED 525 | + vend_amount_H 526 | + vend_amount_L ); 527 | MDB_Send(CSH_VEND_APPROVED); 528 | MDB_Send(vend_amount_H); // Vend Amount H 529 | MDB_Send(vend_amount_L); // Vend Amount L 530 | MDB_Send(checksum | CSH_ACK); // set Mode bit and send 531 | 532 | while ( ! (USART_TXBuf_IsEmpty()) ) 533 | ; 534 | } 535 | 536 | void VendDenied(void) 537 | { 538 | MDB_Send(CSH_VEND_DENIED); 539 | MDB_Send(CSH_VEND_DENIED | CSH_ACK); // Checksum with Mode bit set 540 | 541 | while ( ! (USART_TXBuf_IsEmpty()) ) 542 | ; 543 | csh_poll_state = CSH_END_SESSION; 544 | // // just for safety, this may be redundant 545 | // item_cost = 0; 546 | // vend_amount = 0; 547 | } 548 | 549 | void EndSession(void) 550 | { 551 | MDB_Send(CSH_END_SESSION); 552 | MDB_Send(CSH_END_SESSION | CSH_ACK); // Checksum with Mode bit 553 | 554 | // while ( ! (USART_TXBuf_IsEmpty()) ) 555 | // ; 556 | // Null data of the ended session 557 | CSH_SetUserFunds(0); 558 | CSH_SetItemCost(0); 559 | CSH_SetVendAmount(0); 560 | CSH_SetPollState(CSH_JUST_RESET); 561 | CSH_SetDeviceState(CSH_S_ENABLED); // csh_state = CSH_S_ENABLED; 562 | } 563 | 564 | void Cancelled(void) 565 | { 566 | if (csh_state != CSH_S_ENABLED) 567 | return; 568 | MDB_Send(CSH_CANCELLED); 569 | MDB_Send(CSH_CANCELLED | CSH_ACK); // Checksum with Mode bit 570 | while ( ! (USART_TXBuf_IsEmpty()) ) 571 | ; 572 | } 573 | 574 | void PeripheralID(void) 575 | { 576 | // Not fully implemented yet 577 | // Manufacturer Data, 30 bytes + checksum with mode bit 578 | uint8_t i; // counter 579 | uint8_t checksum = 0; 580 | uint8_t periph_id[31]; 581 | uint8_t a_char = 0x41; 582 | periph_id[0] = CSH_PERIPHERAL_ID; 583 | periph_id[1] = 'U'; periph_id[2] = 'N'; periph_id[3] = 'I'; // Unicum manufacturer, Russia 584 | 585 | // Set Manufacturer ID 000000000001 586 | for (i = 4; i < 15; ++i) 587 | periph_id[i] = 0; 588 | 589 | periph_id[15] = 1; 590 | 591 | // Set Serial Number, ASCII 592 | // Set Model Number, ASCII 593 | for (i = 16; i < 28; ++i) 594 | periph_id[i] = a_char + i; 595 | // Set Software verion, packed BCD 596 | periph_id[28] = 1; 597 | periph_id[29] = 0; 598 | periph_id[30] = calc_checksum(periph_id, 29); 599 | // Send all data on the bus 600 | for (i = 0; i < 30; ++i) 601 | MDB_Send(periph_id[i]); 602 | 603 | MDB_Send(periph_id[30] | CSH_ACK); 604 | 605 | while ( ! (USART_TXBuf_IsEmpty()) ) 606 | ; 607 | } 608 | 609 | void MalfunctionError(void) 610 | { 611 | uint8_t i; 612 | uint8_t malf_err[2]; 613 | malf_err[0] = CSH_MALFUNCTION_ERROR; 614 | malf_err[1] = csh_error_code; 615 | // calculate checksum, set Mode bit and store it 616 | malf_err[2] = calc_checksum(malf_err, 2) | CSH_ACK; 617 | for (i = 0; i < 3; ++i) 618 | MDB_Send(malf_err[i]); 619 | } 620 | 621 | void CmdOutOfSequence(void) 622 | { 623 | MDB_Send(CSH_CMD_OUT_OF_SEQUENCE); 624 | MDB_Send(CSH_CMD_OUT_OF_SEQUENCE | CSH_ACK); 625 | } 626 | 627 | void DiagnosticResponse(void) 628 | { 629 | 630 | } 631 | 632 | /* 633 | * Internal functions for MDB_VendHandler() 634 | */ 635 | void VendRequest(void) 636 | { 637 | uint8_t i; // counter 638 | uint8_t checksum = VMC_VEND + VMC_VEND_REQUEST; 639 | uint8_t vend_data[5]; 640 | uint16_t vend_temp; 641 | // Wait for 5 elements in buffer 642 | // 4 data + 1 checksum 643 | while (1) 644 | if (MDB_DataCount() > 4) 645 | break; 646 | // Read all data and store it in an array, with a subcommand 647 | for (i = 0; i < 5; ++i) 648 | { 649 | MDB_Read(&vend_temp); 650 | vend_data[i] = (uint8_t)(vend_temp & 0x00FF); // get rid of Mode bit if present 651 | } 652 | // calculate checksum excluding last read element, which is a received checksum 653 | checksum += calc_checksum(vend_data, 4); 654 | // compare calculated and received checksums 655 | if (checksum != vend_data[4]) 656 | { 657 | MDB_Send(CSH_NAK); 658 | return; // checksum mismatch, error 659 | } 660 | CSH_SetItemCost((vend_data[0] << 8) | vend_data[1]); 661 | CSH_SetVendAmount((vend_data[2] << 8) | vend_data[3]); 662 | // Send ACK to VMC 663 | MDB_Send(CSH_ACK); 664 | // Set uninterruptable VEND state 665 | csh_state = CSH_S_VEND; 666 | /* 667 | * =================================== 668 | * HERE GOES THE CODE FOR RFID/HTTP Handlers 669 | * if enough payment media, tell server to subtract sum 670 | * wait for server reply, then CSH_VEND_APPROVED 671 | * Otherwise, CSH_VEND_DENIED 672 | * =================================== 673 | */ 674 | } 675 | 676 | void VendSuccessHandler(void) 677 | { 678 | uint8_t i; // counter 679 | uint8_t checksum = VMC_VEND + VMC_VEND_SUCCESS; 680 | uint8_t vend_data[3]; 681 | uint16_t vend_temp; 682 | 683 | // Wait for 3 elements in buffer 684 | // 2 data + 1 checksum 685 | while (1) 686 | if (MDB_DataCount() > 2) 687 | break; 688 | 689 | for (i = 0; i < 3; ++i) 690 | { 691 | MDB_Read(&vend_temp); 692 | vend_data[i] = (uint8_t)(vend_temp & 0x00FF); // get rid of Mode bit if present 693 | } 694 | /* here goes another check-check with a server */ 695 | MDB_Send(CSH_ACK); 696 | 697 | // Return state to SESSION IDLE 698 | csh_state = CSH_S_SESSION_IDLE; 699 | 700 | // checksum += calc_checksum(vend_data, 2); 701 | // if (checksum != vend_data[2]) 702 | // { 703 | // MDB_Send(CSH_NAK); 704 | // return; // checksum mismatch, error 705 | // } 706 | } 707 | 708 | void VendFailureHandler(void) 709 | { 710 | uint8_t checksum = VMC_VEND + VMC_VEND_FAILURE; 711 | uint8_t incoming_checksum; 712 | uint16_t temp; 713 | // Wait for 1 element in buffer 714 | // 1 checksum 715 | while (1) 716 | if (MDB_DataCount() > 0) 717 | break; 718 | MDB_Read(&temp); 719 | incoming_checksum = (uint8_t)(temp & 0x00FF); // get rid of Mode bit if present 720 | if (checksum != incoming_checksum) 721 | { 722 | MDB_Send(CSH_NAK); 723 | return; // checksum mismatch, error 724 | } 725 | /* refund through server connection */ 726 | 727 | MDB_Send(CSH_ACK); // in case of success 728 | // MalfunctionError(); -- in case of failure, like unable to connect to server 729 | // Return state to SESSION IDLE 730 | csh_state = CSH_S_SESSION_IDLE; 731 | } 732 | 733 | void VendSessionComplete(void) 734 | { 735 | MDB_Send(CSH_ACK); 736 | CSH_SetPollState(CSH_END_SESSION); 737 | } 738 | 739 | void VendCashSale(void) 740 | { 741 | uint8_t i; // counter 742 | uint8_t checksum = VMC_VEND + VMC_VEND_CASH_SALE; 743 | uint8_t vend_data[5]; 744 | uint16_t vend_temp; 745 | // Wait for 5 elements in buffer 746 | // 4 data + 1 checksum 747 | while (1) 748 | if (MDB_DataCount() > 4) 749 | break; 750 | for (i = 0; i < 5; ++i) 751 | { 752 | MDB_Read(&vend_temp); 753 | vend_data[i] = (uint8_t)(vend_temp & 0x00FF); // get rid of Mode bit if present 754 | } 755 | checksum += calc_checksum(vend_data, 4); 756 | if (checksum != vend_data[4]) 757 | { 758 | MDB_Send(CSH_NAK); 759 | return; // checksum mismatch, error 760 | } 761 | 762 | /* Cash sale implementation */ 763 | 764 | MDB_Send(CSH_ACK); 765 | } 766 | /* 767 | * Internal functions for MDB_ReaderHandler() 768 | */ 769 | void Disable(void) 770 | { 771 | csh_state = CSH_S_DISABLED; 772 | MDB_Send(CSH_ACK); 773 | } 774 | 775 | void Enable(void) 776 | { 777 | if (csh_state != CSH_S_DISABLED) 778 | return; 779 | csh_state = CSH_S_ENABLED; 780 | csh_poll_state = CSH_ACK; 781 | MDB_Send(CSH_ACK); 782 | } 783 | /* 784 | * Internal functions for MDB_ExpansionHandler() 785 | */ 786 | void ExpansionRequestID(void) 787 | { 788 | uint16_t i; // counter 789 | uint8_t checksum = VMC_EXPANSION + VMC_EXPANSION_REQUEST_ID; 790 | uint16_t temp; 791 | uint8_t data[30]; 792 | /* 793 | * Wait for incoming 29 data elements + 1 checksum (30 total) 794 | * Store the data by the following indexes: 795 | * 0, 1, 2 -- Manufacturer Code (3 elements) 796 | * 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 -- Serial Number (12 elements) 797 | * 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 -- Model Number (12 elements) 798 | * 27, 28 -- Software Version (2 elements) 799 | * 29 -- Checksum (1 element) 800 | */ 801 | while (1) 802 | if (MDB_DataCount() > 29) 803 | break; 804 | // Store data 805 | for (i = 0; i < 30; ++i) 806 | { 807 | MDB_Read(&temp); 808 | data[i] = (uint8_t)(temp & 0x00FF); // get rid of Mode bit if present 809 | } 810 | 811 | // Calculate checksum 812 | checksum += calc_checksum(data, 29); 813 | // Second element is an incoming checksum, compare it to the calculated one 814 | if (checksum != data[29]) 815 | { 816 | MDB_Send(CSH_NAK); 817 | return; // checksum mismatch, error 818 | } 819 | // Respond with our own data 820 | PeripheralID(); 821 | while ( ! (USART_TXBuf_IsEmpty()) ) 822 | ; 823 | } 824 | 825 | void ExpansionDiagnostics(void) 826 | { 827 | uint8_t checksum = VMC_EXPANSION + VMC_EXPANSION_DIAGNOSTICS; 828 | MDB_Send(CSH_ACK); 829 | // while(1) 830 | // if (MDB_DataCount() > ) 831 | } 832 | /* 833 | * ============================================= 834 | * End of Internal functions for main handlers 835 | * ============================================= 836 | */ 837 | /* 838 | * Functions to work with Cashless device states and data 839 | */ 840 | uint8_t CSH_GetPollState(void) 841 | { 842 | return csh_poll_state; 843 | } 844 | uint8_t CSH_GetDeviceState(void) 845 | { 846 | return csh_state; 847 | } 848 | uint16_t CSH_GetUserFunds(void) 849 | { 850 | return user_funds; 851 | } 852 | uint16_t CSH_GetItemCost(void) 853 | { 854 | return item_cost; 855 | } 856 | uint16_t CSH_GetVendAmount(void) 857 | { 858 | return vend_amount; 859 | } 860 | void CSH_SetPollState(uint8_t new_poll_state) 861 | { 862 | csh_poll_state = new_poll_state; 863 | } 864 | void CSH_SetDeviceState(uint8_t new_device_state) 865 | { 866 | csh_state = new_device_state; 867 | } 868 | void CSH_SetUserFunds(uint16_t new_funds) 869 | { 870 | user_funds = new_funds; 871 | } 872 | void CSH_SetItemCost(uint16_t new_item_cost) 873 | { 874 | item_cost = new_item_cost; 875 | } 876 | void CSH_SetVendAmount(uint16_t new_vend_amount) 877 | { 878 | vend_amount = new_vend_amount; 879 | } 880 | 881 | /* 882 | * Misc helper functions 883 | */ 884 | 885 | /* 886 | * calc_checksum() 887 | * Calculates checksum of *array from 0 to arr_size 888 | * Use with caution (because of pointer arithmetics) 889 | */ 890 | uint8_t calc_checksum(uint8_t *array, uint8_t arr_size) 891 | { 892 | uint8_t ret_val = 0x00; 893 | uint8_t i; 894 | for (i = 0; i < arr_size; ++i) 895 | ret_val += *(array + i); 896 | return ret_val; 897 | } 898 | -------------------------------------------------------------------------------- /MDB.h: -------------------------------------------------------------------------------- 1 | #ifndef MDB_H 2 | #define MDB_H 3 | 4 | #include 5 | 6 | typedef struct 7 | { 8 | uint8_t featureLevel; 9 | uint8_t displayColumns; 10 | uint8_t displayRows; 11 | uint8_t displayInfo; 12 | } VMC_Config_t; 13 | 14 | typedef struct 15 | { 16 | uint8_t featureLevel; 17 | uint8_t countryCodeH; 18 | uint8_t countryCodeL; 19 | uint8_t scaleFactor; 20 | uint8_t decimalPlaces; 21 | uint8_t maxResponseTime; // seconds, overrides default NON-RESPONSE time 22 | uint8_t miscOptions; 23 | } CSH_Config_t; 24 | 25 | typedef struct 26 | { 27 | uint16_t maxPrice; 28 | uint16_t minPrice; 29 | } VMC_Prices_t; 30 | 31 | #define FUNDS_TIMEOUT 10 // funds timeout, seconds 32 | 33 | /* 34 | * Level 01 Cashless (CSH) device states 35 | */ 36 | #define CSH_S_INACTIVE 0x00 37 | #define CSH_S_DISABLED 0x01 38 | #define CSH_S_ENABLED 0x02 39 | #define CSH_S_SESSION_IDLE 0x03 40 | #define CSH_S_VEND 0x04 41 | // this one is to avoid multiple calls of functions that were already executed once 42 | #define CSH_S_PROCESSING 0xFF 43 | 44 | // typedef enum { 45 | // INACTIVE, 46 | // DISABLED, 47 | // ENABLED, 48 | // SESSION_IDLE, 49 | // VEND 50 | // } CSH_State_t; 51 | /* 52 | * Level 01 Cashless device address 53 | */ 54 | #define CSH_ADDRESS 0x110 55 | /* 56 | * MDB Vending Machine Controller (VMC) Commands, 7 total 57 | * These are stored in USART RX Buffer 58 | * Read the command with MDB_Read and 59 | * compare to these definitions 60 | * Don't change, written as in standard 61 | */ 62 | #define VMC_RESET 0x110 63 | #define VMC_SETUP 0x111 64 | #define VMC_POLL 0x112 65 | #define VMC_VEND 0x113 66 | #define VMC_READER 0x114 67 | #define VMC_EXPANSION 0x117 68 | /* 69 | * MDB VMC Subcommands 70 | */ 71 | // VMC_SETUP 72 | #define VMC_CONFIG_DATA 0x00 73 | #define VMC_MAX_MIN_PRICES 0x01 74 | // VMC_VEND 75 | #define VMC_VEND_REQUEST 0x00 76 | #define VMC_VEND_CANCEL 0x01 77 | #define VMC_VEND_SUCCESS 0x02 78 | #define VMC_VEND_FAILURE 0x03 79 | #define VMC_VEND_SESSION_COMPLETE 0x04 80 | #define VMC_VEND_CASH_SALE 0x05 81 | // VMC_READER 82 | #define VMC_READER_DISABLE 0x00 83 | #define VMC_READER_ENABLE 0x01 84 | #define VMC_READER_CANCEL 0x02 85 | // VMC_EXPANSION 86 | #define VMC_EXPANSION_REQUEST_ID 0x00 87 | #define VMC_EXPANSION_DIAGNOSTICS 0xFF 88 | /* 89 | * MDB Level 01 Cashless device Replies 90 | * These are to be written in USART TX Buffer 91 | * Store them with MDB_Send 92 | * Don't change, written as in standard 93 | */ 94 | #define CSH_ACK 0x0100 // Acknowledgement, Mode-bit is set 95 | #define CSH_NAK 0x01FF // Negative Acknowledgement 96 | #define CSH_SILENCE 0xFFFF // This one is not from standard, it's an impossible value for the VMC 97 | #define CSH_JUST_RESET 0x00 98 | #define CSH_READER_CONFIG_INFO 0x01 99 | #define CSH_DISPLAY_REQUEST 0x02 100 | #define CSH_BEGIN_SESSION 0x03 101 | #define CSH_SESSION_CANCEL_REQUEST 0x04 102 | #define CSH_VEND_APPROVED 0x05 103 | #define CSH_VEND_DENIED 0x06 104 | #define CSH_END_SESSION 0x07 105 | #define CSH_CANCELLED 0x08 106 | #define CSH_PERIPHERAL_ID 0x09 107 | #define CSH_MALFUNCTION_ERROR 0x0A 108 | #define CSH_CMD_OUT_OF_SEQUENCE 0x0B 109 | #define CSH_DIAGNOSTIC_RESPONSE 0xFF 110 | 111 | #ifdef __cplusplus 112 | extern "C" { 113 | #endif 114 | 115 | /* These one goes to main.c or .ino sketch */ 116 | void MDB_Init (void); 117 | void MDB_CommandHandler (void); 118 | /* Functions to work with MDB Bus */ 119 | void MDB_Send (uint16_t data); 120 | void MDB_Read (uint16_t *data); 121 | void MDB_Peek (uint16_t *data); 122 | uint8_t MDB_DataCount (void); 123 | 124 | /* Functions to work with Cashless device states and data */ 125 | uint8_t CSH_GetPollState (void); 126 | uint8_t CSH_GetDeviceState (void); 127 | uint16_t CSH_GetUserFunds (void); 128 | uint16_t CSH_GetItemCost (void); 129 | uint16_t CSH_GetVendAmount (void); 130 | void CSH_SetPollState (uint8_t new_poll_state); 131 | void CSH_SetDeviceState (uint8_t new_device_state); 132 | void CSH_SetUserFunds (uint16_t new_funds); 133 | void CSH_SetItemCost (uint16_t new_item_cost); 134 | void CSH_SetVendAmount (uint16_t new_vend_amount); 135 | /* Internal functions for MDB_CommandHandler */ 136 | static void MDB_ResetHandler (void); 137 | static void MDB_SetupHandler (void); 138 | static void MDB_PollHandler (void); 139 | static void MDB_VendHandler (void); 140 | static void MDB_ReaderHandler (void); 141 | static void MDB_ExpansionHandler (void); 142 | 143 | /* Internal functions for upper handlers */ 144 | static void Reset (void); 145 | static void ConfigInfo (void); 146 | static void DisplayRequest (void); 147 | static void BeginSession (void); 148 | static void EndSession (void); 149 | static void Cancelled (void); 150 | static void PeripheralID (void); 151 | static void MalfunctionError (void); 152 | static void CmdOutOfSequence (void); 153 | static void DiagnosticResponse (void); 154 | static void VendRequest (void); // <<<=== Important function 155 | static void VendApproved (void); 156 | static void VendDenied (void); 157 | static void VendSuccessHandler (void); 158 | static void VendFailureHandler (void); 159 | static void VendSessionComplete (void); 160 | static void Disable (void); 161 | static void Enable (void); 162 | static void ExpansionRequestID (void); 163 | static void ExpansionDiagnostics (void); 164 | /* Internal helper functions */ 165 | static uint8_t calc_checksum (uint8_t *array, uint8_t arr_size); 166 | 167 | #ifdef __cplusplus 168 | } 169 | #endif 170 | 171 | #endif /* MDB_LIB_H */ -------------------------------------------------------------------------------- /MDB_MasterThroughPC.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MDB Vending Machine Controller emulation environment 3 | * for Level 01 Cashless Devices 4 | * Author: Novoselov I.E. 5 | * Email: jedi.orden@gmail.com 6 | */ 7 | #include 8 | #include 9 | #include 10 | 11 | // SoftwareSerial pins for debugging 12 | #define RX_DEBUG_PIN 3 13 | #define TX_DEBUG_PIN 2 14 | // Debug object 15 | SoftwareSerial Debug(RX_DEBUG_PIN, TX_DEBUG_PIN); 16 | 17 | void setup() { 18 | Debug.begin(9600); 19 | USART_Init(9600); 20 | sei(); 21 | } 22 | 23 | void loop() { 24 | uint8_t recComm = 0; 25 | uint8_t bytesAvailable = 0; 26 | char new_session[] = "=====NEW SESSION====="; 27 | 28 | if (bytesAvailable = Debug.available()) 29 | { 30 | recComm = Debug.read(); 31 | Debug.println(); 32 | Debug.println(new_session); 33 | Debug.print(F("Bytes available : 0d")); 34 | Debug.println(bytesAvailable, DEC); 35 | Debug.print(F("Incoming command : 0x")); 36 | Debug.println(recComm, HEX); 37 | } 38 | switch(recComm) 39 | { 40 | case 0x30 : vmcReset(); break; 41 | case 0x31 : vmcSetup(); break; 42 | case 0x32 : vmcPoll(); break; 43 | case 0x33 : vmcVend(); break; 44 | case 0x34 : vmcReader(); break; 45 | case 0x37 : vmcExpansion(); break; 46 | default : break; 47 | } 48 | } 49 | /* 50 | * Emulates Reset and Initialize command 51 | */ 52 | void vmcReset(void) 53 | { 54 | uint16_t reply_reset = 0; 55 | Debug.println(F("VMC_RESET Call test")); 56 | MDB_Send(VMC_RESET); 57 | while (true) 58 | if (MDB_DataCount() > 0) 59 | { 60 | Debug.print(F("MDB_DataCount : ")); 61 | Debug.println(MDB_DataCount()); 62 | MDB_Read(&reply_reset); 63 | Debug.print(F("Cashless reply : ")); 64 | Debug.print(F("0b")); 65 | Debug.println(reply_reset, HEX); 66 | break; 67 | } 68 | } 69 | /* 70 | * Emulates 2 SETUP commands in a row: 71 | * SETUP - Reader Config Data, then 72 | * SETUP - Max / Min Prices 73 | */ 74 | void vmcSetup(void) 75 | { 76 | uint8_t i; // counter 77 | uint8_t checksum; 78 | uint16_t commd_setup_H[6] = {VMC_SETUP, 0x00, 0x01, 0x0A, 0x04, 0x01}; 79 | uint16_t commd_setup_L[6] = {VMC_SETUP, 0x01, 0xF1, 0xF2, 0xF3, 0xF4}; 80 | uint16_t reply_setup_H[9]; 81 | uint16_t reply_setup_L; 82 | 83 | Debug.println(F("VMC_SETUP Call test")); 84 | // Stage 1 -- Reader Config Data 85 | checksum = calcChecksum(commd_setup_H, 6); 86 | for (i = 0; i < 6; ++i) 87 | MDB_Send(commd_setup_H[i]); 88 | MDB_Send(checksum); 89 | Debug.print(F("Reader Config Checksum : 0x")); 90 | Debug.println(checksum, HEX); 91 | 92 | while (true) 93 | if (MDB_DataCount() > 8) 94 | { 95 | for (i = 0; i < 9; ++i) 96 | MDB_Read(reply_setup_H + i); 97 | break; 98 | } 99 | 100 | Debug.print(F("Incoming Reader Config Data :")); 101 | for (i = 0; i < 9; ++i) 102 | { 103 | Debug.print(F(" 0x")); 104 | // write() cannot hold 16-bit ints, but print() can 105 | Debug.print(reply_setup_H[i], HEX); 106 | // Debug.print(reply_setup_H[i] >> 8, HEX); 107 | // Debug.print(F("_0x")); 108 | // Debug.print(reply_setup_H[i] & 0xFF, HEX); 109 | } 110 | Debug.println(); 111 | 112 | // Stage 2 -- Max / Min Prices 113 | checksum = calcChecksum(commd_setup_L, 6); 114 | for (i = 0; i < 6; ++i) 115 | MDB_Send(commd_setup_L[i]); 116 | MDB_Send(checksum); 117 | while (true) 118 | { 119 | if (MDB_DataCount() == 0) 120 | continue; 121 | else 122 | { 123 | MDB_Read(&reply_setup_L); 124 | break; 125 | } 126 | } 127 | 128 | Debug.print(F("Incoming Max / Min Prices reply : ")); 129 | Debug.print(F("0x")); 130 | Debug.print(reply_setup_L, HEX); 131 | Debug.println(); 132 | } 133 | /* 134 | * Emulates POLL command 135 | */ 136 | void vmcPoll() 137 | { 138 | uint8_t i; // counter 139 | uint16_t reply; 140 | uint16_t comm_poll = VMC_POLL; 141 | Debug.println(F("VMC_POLL Call test")); 142 | MDB_Send(comm_poll); 143 | Debug.print(F("Incoming data :")); 144 | while (true) 145 | { 146 | if (MDB_DataCount() > 0) 147 | { 148 | MDB_Read(&reply); 149 | Debug.print(F(" 0x")); 150 | Debug.print(reply, HEX); 151 | } 152 | } 153 | } 154 | /* 155 | * Emulates VEND command 156 | */ 157 | void vmcVend() 158 | { 159 | 160 | } 161 | /* 162 | * Emulates READER command 163 | */ 164 | void vmcReader() 165 | { 166 | uint8_t i; // counter 167 | uint8_t checksum = 0; 168 | uint16_t reply[2]; 169 | uint16_t comm_reader_0[2] = {VMC_READER, 0x00}; 170 | uint16_t comm_reader_1[2] = {VMC_READER, 0x01}; 171 | uint16_t comm_reader_2[2] = {VMC_READER, 0x02}; 172 | 173 | Debug.println(F("VMC_READER Call test")); 174 | 175 | // Test 1 -- READER Disable 176 | checksum = calcChecksum(comm_reader_0, 2); 177 | for (i = 0; i < 2; ++i) 178 | MDB_Send(comm_reader_0[i]); 179 | MDB_Send(checksum); 180 | // Wait for reply 181 | while (true) 182 | if (MDB_DataCount() > 0) 183 | break; 184 | Debug.print(F("READER Disable reply :")); 185 | for (i = 0; i < 1; ++i) 186 | { 187 | MDB_Read(&reply[i]); 188 | Debug.print(F(" 0x")); 189 | Debug.print(reply[i], HEX); 190 | } 191 | Debug.println(); 192 | 193 | // Test 2 -- READER Enable 194 | checksum = calcChecksum(comm_reader_1, 2); 195 | for (i = 0; i < 2; ++i) 196 | MDB_Send(comm_reader_1[i]); 197 | MDB_Send(checksum); 198 | // Wait for reply 199 | while (true) 200 | if (MDB_DataCount() > 0) 201 | break; 202 | Debug.print(F("READER Enable reply :")); 203 | for (i = 0; i < 1; ++i) 204 | { 205 | MDB_Read(&reply[i]); 206 | Debug.print(F(" 0x")); 207 | Debug.print(reply[i], HEX); 208 | } 209 | Debug.println(); 210 | 211 | // Test 3 -- READER Cancelled 212 | checksum = calcChecksum(comm_reader_2, 2); 213 | for (i = 0; i < 2; ++i) 214 | MDB_Send(comm_reader_2[i]); 215 | MDB_Send(checksum); 216 | // Wait for reply 217 | while (true) 218 | if (MDB_DataCount() > 1) 219 | break; 220 | Debug.print(F("READER Cancel reply :")); 221 | for (i = 0; i < 2; ++i) 222 | { 223 | MDB_Read(&reply[i]); 224 | Debug.print(F(" 0x")); 225 | Debug.print(reply[i], HEX); 226 | } 227 | Debug.println(); 228 | } 229 | /* 230 | * Emulates EXPANSION command 231 | */ 232 | void vmcExpansion() 233 | { 234 | uint8_t i; // counter 235 | uint8_t checksum = VMC_EXPANSION + VMC_EXPANSION_REQUEST_ID; 236 | uint16_t expSend[32]; 237 | uint16_t periphID[31]; 238 | expSend[0] = VMC_EXPANSION; 239 | expSend[1] = VMC_EXPANSION_REQUEST_ID; 240 | // Make 29 data elements + 1 checksum 241 | for (i = 2; i < 31; ++i) 242 | { 243 | expSend[i] = 0x30; 244 | checksum += expSend[i]; 245 | } 246 | expSend[31] = checksum; 247 | // Send data 248 | for (i = 0; i < 32; ++i) 249 | MDB_Send(expSend[i]); 250 | 251 | Debug.print(111); 252 | while (true) 253 | if (MDB_DataCount() > 30) 254 | { 255 | for (i = 0; i < 31; ++i) 256 | MDB_Read(periphID + i); 257 | break; 258 | } 259 | Debug.print(222); 260 | Debug.print(F("Incoming Expansion Request ID data :")); 261 | for (i = 0; i < 31; ++i) 262 | { 263 | Debug.print(F(" 0x")); 264 | Debug.print(periphID[i], HEX); 265 | } 266 | Debug.println(); 267 | } 268 | 269 | uint8_t calcChecksum(uint16_t *array, uint8_t arr_size) 270 | { 271 | uint8_t retVal = 0x00; 272 | uint8_t i; 273 | for (i = 0; i < arr_size; ++i) 274 | retVal += *(array + i); 275 | return retVal; 276 | } 277 | 278 | -------------------------------------------------------------------------------- /MDB_SlaveAutonomous.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | // SoftwareSerial pins for debugging 7 | #define RX_DEBUG_PIN 6 8 | #define TX_DEBUG_PIN 7 9 | // Debug object 10 | SoftwareSerial Debug(RX_DEBUG_PIN, TX_DEBUG_PIN); 11 | 12 | // SPI Slaves Settings 13 | #define RESET_PIN 9 // SPI RESET Pin 14 | #define SS_PIN_RC522 8 // MFRC522 SS Pin 15 | #define SS_PIN_SDCRD 4 // SD Card SS Pin (ethernet shield SD card) 16 | #define SS_PIN_W5100 10 // W5100 SS Pin (ethernet shield Ethernet) 17 | 18 | typedef enum {RC522, SDCARD, W5100} SPISlave; // For function 'enableSPISlave(SPISlave slave)' 19 | 20 | // Create MFRC522 instance 21 | MFRC522 mfrc522(SS_PIN_RC522, RESET_PIN); 22 | // String object for containing HEX represented UID 23 | String uid_str_obj = ""; 24 | /* 25 | * Ethernet Settings 26 | * client_mac -- mandatory 27 | * client_ip -- in case of failed DHCP mode, use static IP 28 | * A server we connect to is a 'client' to us 29 | */ 30 | uint8_t client_mac[] = { 31 | 0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 32 | }; 33 | IPAddress client_ip(192, 168, 137, 232); // Static IP Mode in case of failed DHCP Mode 34 | IPAddress server_address(192, 168, 1, 139); 35 | uint16_t server_port = 8888; 36 | EthernetClient server; 37 | 38 | void setup() { 39 | // For debug LEDs 40 | DDRC |= 0b00111111; // PORTC LEDs for 6 commands in MDB_CommandHandler() switch-case 41 | Debug.begin(9600); 42 | MDB_Init(); 43 | sei(); 44 | // Init Ethernet Shield 45 | enableSPISlave(W5100); 46 | if (Ethernet.begin(client_mac) == 0) // Start in DHCP Mode 47 | Ethernet.begin(client_mac, client_ip); // If DHCP Mode failed, start in Static Mode 48 | 49 | // Give the Ethernet shield a second to initialize: 50 | delay(1000); 51 | unsigned long start_time = millis(); 52 | 53 | Debug.println(F("Ethernet ON")); 54 | // Init MFRC522 RFID reader 55 | enableSPISlave(RC522); 56 | mfrc522.PCD_Init(); 57 | } 58 | 59 | void loop() { 60 | // Debug.println(MDB_DataCount()); 61 | MDB_CommandHandler(); 62 | sessionHandler(); 63 | // without this delay READER_ENABLE command won't be in RX Buffer 64 | // Establish the reason of this later (maybe something is wrong with RX buffer reading) 65 | if (CSH_GetDeviceState() == CSH_S_DISABLED) 66 | delay(10); 67 | // switch (c) 68 | // { 69 | // case 0x30 : CSH_SetPollState(CSH_SESSION_CANCEL_REQUEST); break; 70 | // case 0x31 : CSH_SetPollState(CSH_END_SESSION); break; 71 | // default : break; 72 | // } 73 | } 74 | 75 | /* 76 | * Handler for two Cashless Device active states: 77 | * ENABLED -- device is waiting for a new card 78 | * VEND -- device is busy making transactions with the server 79 | */ 80 | void sessionHandler(void) 81 | { 82 | switch(CSH_GetDeviceState()) 83 | { 84 | case CSH_S_ENABLED : RFID_readerHandler(); break; 85 | case CSH_S_VEND : transactionHandler(); break; 86 | default : break; 87 | } 88 | // char c = Debug.read(); 89 | // if (c == 0x30) 90 | // CSH_SetPollState(CSH_END_SESSION); 91 | } 92 | /* 93 | * Waiting for RFID tag routine 94 | * I a new card is detected and it is present in the server's database, 95 | * server replies with available funds 96 | * then set them via functions from MDB.h, 97 | * and turn Cashless Device Poll Reply as BEGIN_SESSION 98 | */ 99 | void RFID_readerHandler(void) 100 | { 101 | String new_uid_str_obj; 102 | // String objects and indexes for parsing HTTP Response 103 | String http_response; 104 | String http_code; 105 | uint8_t start_index = 0; 106 | uint8_t end_index = 0; 107 | uint16_t user_funds = 0; 108 | // Look for new cards 109 | enableSPISlave(RC522); 110 | if ( ! mfrc522.PICC_IsNewCardPresent()) 111 | return; 112 | // Select one of the cards 113 | if ( ! mfrc522.PICC_ReadCardSerial()) 114 | return; 115 | 116 | new_uid_str_obj = ""; 117 | getUIDStrHex(&mfrc522, &new_uid_str_obj); 118 | if (uid_str_obj == new_uid_str_obj) 119 | return; 120 | 121 | uid_str_obj = new_uid_str_obj; 122 | // Check if there is no UID -- then just return 123 | if (uid_str_obj.length() == 0) 124 | return; 125 | 126 | Debug.println(uid_str_obj); 127 | enableSPISlave(W5100); 128 | // wait a second 129 | delay(2000); 130 | if (server.connect(server_address, server_port)) 131 | { 132 | // Make HTTP request 133 | server.print(F("GET /api/getrecord/")); 134 | server.print(uid_str_obj); 135 | server.print(F("/Funds")); 136 | server.println(F(" HTTP/1.1")); 137 | server.println(); 138 | } 139 | 140 | while (true) 141 | { 142 | if (server.available()) 143 | http_response += (char)server.read(); 144 | else if (!server.connected()) 145 | { 146 | server.stop(); 147 | break; 148 | } 149 | } 150 | 151 | end_index = http_response.indexOf('\n'); 152 | // Get header string, then get HTTP Code 153 | http_code = http_response.substring(0, end_index).substring(9, 12); 154 | if (http_code != F("200")) 155 | { 156 | // Maybe tell VMC to display message 'Card is not registered in the database' 157 | uid_str_obj = ""; // Clear global UID holder 158 | return; 159 | } 160 | 161 | // Now parse for funds available 162 | start_index = http_response.indexOf('\"') + 1; // +1, or we going to start with \" symbol 163 | end_index = http_response.indexOf('\"', start_index); 164 | user_funds = http_response.substring(start_index, end_index).toInt(); 165 | Debug.print(F("Funds: ")); 166 | Debug.println(user_funds); 167 | 168 | Debug.println(F("Poll State: Begin Session")); 169 | CSH_SetUserFunds(user_funds); // Set current user funds 170 | CSH_SetPollState(CSH_BEGIN_SESSION); // Set Poll Reply to BEGIN SESSION 171 | /* 172 | * SET FUNDS AND CONNECTION TIMEOUT SOMEWHERE, TO CANCEL SESSION AFTER 5 SECONDS !!! 173 | */ 174 | } 175 | /* 176 | * Transaction routine 177 | * At this point item_cost and item_number (or vend_amount) 178 | * are should be set by VendRequest() (in MDB.c) 179 | */ 180 | void transactionHandler(void) 181 | { 182 | // String objects and indexes for parsing HTTP Response 183 | String http_response; 184 | String http_code; 185 | uint8_t end_index = 0; 186 | 187 | uint16_t item_cost = CSH_GetItemCost(); 188 | uint16_t item_numb = CSH_GetVendAmount(); 189 | 190 | Debug.println(F("Vend Request"));; 191 | enableSPISlave(W5100); 192 | if (server.connect(server_address, server_port)) 193 | { 194 | // Make HTTP request 195 | // GET /api/getrecord/UID/Funds/VendRequest/item_cost/item_numb HTTP/1.1 196 | server.print(F("GET /api/getrecord/")); 197 | server.print(uid_str_obj); 198 | server.print(F("/Funds/VendRequest/")); 199 | server.print(String(item_cost)); 200 | server.print(F("/")); 201 | server.print(String(item_numb)); 202 | server.println(F(" HTTP/1.1")); 203 | server.println(); 204 | } 205 | // Clear global UID variable 206 | uid_str_obj = ""; 207 | while (true) 208 | { 209 | if (server.available()) 210 | http_response += (char)server.read(); 211 | else if (!server.connected()) 212 | { 213 | server.stop(); 214 | break; 215 | } 216 | } 217 | 218 | end_index = http_response.indexOf('\n'); 219 | // Get header string, then get HTTP Code 220 | http_code = http_response.substring(0, end_index).substring(9, 12); 221 | // If response is not 200 (or 403), tell VMC that vend is denied and abort 222 | if (http_code != F("200")) 223 | { 224 | CSH_SetPollState(CSH_VEND_DENIED); 225 | CSH_SetDeviceState(CSH_S_SESSION_IDLE); 226 | Debug.println(F("Vend Denied")); 227 | return; 228 | } 229 | // If code is 200 -- tell VMC that vend is approved 230 | CSH_SetPollState(CSH_VEND_APPROVED); 231 | CSH_SetDeviceState(CSH_S_SESSION_IDLE); 232 | Debug.println(F("Vend Approved")); 233 | } 234 | 235 | /* 236 | * 20-02-2016 237 | * Convert byte array with size of (4..10) 8-bit hex numbers into a string object 238 | * Example: array of 4 hex numbers, which represents 32-bit unsigned long: 239 | * {0x36, 0x6B, 0x1B, 0xDB} represents a 32-bit (4-word) number 0xDB1B6B36, which is converted into a string "63B6B1BD" 240 | * This string represented hex number appears reversed for human recognition, 241 | * although technically the least 4-bit hex digit accords to the first letter of a string, uuid_str[0] 242 | * and the most significant 4-bit hex digit placed as a last char element (except for '\0') 243 | * 244 | * changes the String &uid_str object by pointer, considered as output 245 | */ 246 | void getUIDStrHex(MFRC522 *card, String *uid_str) 247 | { 248 | char uuid_str[2 * card->uid.size]; 249 | 250 | // This cycle makes a conversion of the following manner for each 8-bit number: {0x1B} --> {0x0B, 0x01} 251 | for (byte i = 0; i < card->uid.size; ++i) { 252 | uuid_str[2 * i] = card->uid.uidByte[i] & 0b1111; 253 | uuid_str[2 * i + 1] = card->uid.uidByte[i] >> 4; 254 | } 255 | //uuid_str[2 * card->uid.size + 1] = '\0'; // Add a null-terminator to make it a C-style string 256 | 257 | /* 258 | * This cycle adds 0x30 or (0x41 - 0x0A) to actual numbers according to ASCII table 259 | * 0x00 to 0x09 digits become '0' to '9' chars (add 0x30) 260 | * 0x0A to 0x0F digits become 'A' to 'F' chars in UPPERCASE (add 0x41 and subtract 0x0A) 261 | * Last thing is to copy that into a String object, which is easier to handle 262 | */ 263 | for (byte i = 0; i < 2 * card->uid.size; ++i) 264 | { 265 | if (uuid_str[i] < 0x0A) 266 | uuid_str[i] = (uuid_str[i] + 0x30); 267 | else 268 | uuid_str[i] = (uuid_str[i] - 0x0A + 0x41); 269 | *uid_str += uuid_str[i]; 270 | } 271 | } 272 | 273 | /* 274 | * 20-02-2016 275 | * Enables selected SPI slave, disables all others 276 | * All slave SS pins must be #defined, slave must be of enumerated type 277 | */ 278 | void enableSPISlave(SPISlave slave) 279 | { 280 | // Automatically disable all other SPI Slaves 281 | switch(slave) 282 | { 283 | case RC522 : 284 | { 285 | pinMode(SS_PIN_RC522, OUTPUT); 286 | digitalWrite(SS_PIN_RC522, LOW); 287 | digitalWrite(SS_PIN_SDCRD, HIGH); 288 | digitalWrite(SS_PIN_W5100, HIGH); 289 | break; 290 | } 291 | case SDCARD : 292 | { 293 | pinMode(SS_PIN_SDCRD, OUTPUT); 294 | digitalWrite(SS_PIN_SDCRD, LOW); 295 | digitalWrite(SS_PIN_RC522, HIGH); 296 | digitalWrite(SS_PIN_W5100, HIGH); 297 | break; 298 | } 299 | case W5100 : 300 | { 301 | pinMode(SS_PIN_W5100, OUTPUT); 302 | digitalWrite(SS_PIN_W5100, LOW); 303 | digitalWrite(SS_PIN_RC522, HIGH); 304 | digitalWrite(SS_PIN_SDCRD, HIGH); 305 | break; 306 | } 307 | default : break; 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mdb-arduino-cashless 2 | Arduino "Cashless Device" project for MDB protocol 3 | 4 | Target configuration: 5 | 1) Arduino Uno (ATmega 328P) 6 | 2) MFRC522 13.56MHz RFID Reader 7 | 3) Ethernet Shield W5100 8 | 4) Vending Machine Controller, MultiDropBus Protocol (https://www.ccv.eu/wp-content/uploads/2018/05/mdb_interface_specification.pdf) 9 | 10 | Main features: 11 | 1) Target is a device of "Cashless Device" class. 12 | 2) Hardware USART of Arduino Uno configured for 9-bit word communication (9600 baud, 9 data bits, no parity, 1 stop-bit). 13 | 3) Payment is based on 13.56MHz RFID cards. 14 | 4) Balance of the card is stored on the HTTP Server. 15 | 4) Device makes HTTP request to Server to make a payment and then vend item in case of success. 16 | -------------------------------------------------------------------------------- /USART.c: -------------------------------------------------------------------------------- 1 | /* mm.dd.yyyy 2 | * Created: 02.26.2016 by Novoselov Ivan 3 | * ATmega328P (Arduino Uno) 4 | * For 9-bit, no parity, 1 stop-bit config only 5 | * 6 | */ 7 | // #include 8 | // #include 9 | // #include 10 | // #include 11 | #include "Arduino.h" 12 | 13 | #include "USART.h" 14 | #include "ringBuf.h" 15 | 16 | ringBuf_t usartReceiveBuf; 17 | ringBuf_t usartTransmitBuf; 18 | /* 19 | * Initialize UART 20 | * 21 | */ 22 | void USART_Init(uint16_t baud_rate) 23 | { 24 | uint16_t baud_setting = 0; 25 | // baud_setting = (F_CPU / 8 / baud_rate - 1) / 2; // full integer equialent of (F_CPU / (16 * baud_rate) - 1) 26 | UCSR0A |= (1 << U2X0); // try double speed 27 | baud_setting = (F_CPU / 4 / baud_rate - 1) / 2; 28 | 29 | /* Set Baud Rate */ 30 | UBRR0H = (uint8_t)(baud_setting >> 8); 31 | UBRR0L = (uint8_t)(baud_setting & 0x00FF); 32 | /* Enable Receiver and Transmitter */ 33 | UCSR0B |= (1 << RXEN0) | (1 << TXEN0); 34 | /* Enable Interrupt on Recieve Complete */ 35 | UCSR0B |= (1 << RXCIE0); 36 | /* Set Frame Format, 9-bit word, no parity, 1 stop-bit */ 37 | UCSR0B |= (1 << UCSZ02); 38 | UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00); 39 | 40 | rBufInit(&usartReceiveBuf); 41 | rBufInit(&usartTransmitBuf); 42 | } 43 | /* 44 | * Write data to usartTransmitBuf and update head. 45 | * Then enable Data Register Empty Interrupt. 46 | * USART_Transmit will handle the transmission itself (look into USART_ISR.c) 47 | */ 48 | void USART_TXBuf_Write(uint16_t data) 49 | { 50 | rBufPushFront(&usartTransmitBuf, data); 51 | USART_UDRI_Enable(); 52 | } 53 | /* 54 | * Read data to usartTransmitBuf 55 | * USART_Receive handles actual reception of the data (look into USART_ISR.c) 56 | */ 57 | void USART_RXBuf_Read(uint16_t *data) 58 | { 59 | rBufPopBack(&usartReceiveBuf, data); 60 | } 61 | /* 62 | * 63 | */ 64 | void USART_RXBuf_Peek(uint16_t *data) 65 | { 66 | rBufPeekBack(&usartReceiveBuf, data); 67 | } 68 | 69 | uint8_t USART_TXBuf_IsEmpty (void) 70 | { 71 | return rBufIsEmpty(&usartTransmitBuf); 72 | } 73 | 74 | uint8_t USART_RXBuf_Count (void) 75 | { 76 | return rBufElemCount(&usartReceiveBuf); 77 | } 78 | 79 | void USART_Transmit(void) 80 | { 81 | uint16_t transmitVal; 82 | /* 83 | * Wait for empty transmit buffer 84 | * When UDREn flag is 1 -- buffer is empty and ready to be written 85 | */ 86 | // while ( !(UCSR0A & (1 << UDRE0)) ) 87 | // ; 88 | rBufPopBack(&usartTransmitBuf, &transmitVal); 89 | /* 90 | * Copy 9-th bit to TXB8n 91 | * Clear this bit manually to prevent 92 | * If data does not exceed 0x0100, don't do anything 93 | */ 94 | UCSR0B &= ~(1 << TXB80); 95 | if (transmitVal & 0x0100) 96 | UCSR0B |= (1 << TXB80); 97 | /* Write the rest to UDRn register */ 98 | UDR0 = (uint8_t)transmitVal; 99 | } 100 | /* 101 | * Read UDRn RX register with 9-th bit from UCSRnB 102 | */ 103 | void USART_Receive(void) 104 | { 105 | uint8_t state, recH, recL; 106 | uint16_t data; 107 | 108 | // while ( !(UCSR0A & (1 << RXC0)) ) 109 | // ; 110 | state = UCSR0A; 111 | recH = UCSR0B; 112 | recL = UDR0; 113 | 114 | if ( state & (1 << UPE0) ) 115 | return; 116 | 117 | recH = 0x01 & (recH >> 1); 118 | data = (recH << 8) | recL; 119 | 120 | rBufPushFront(&usartReceiveBuf, data); 121 | } 122 | /* 123 | * Enable Data Register Empty Interrupt * 124 | */ 125 | void USART_UDRI_Enable(void) 126 | { 127 | UCSR0B |= (1 << UDRIE0); 128 | } 129 | /* 130 | * Disable Data Register Empty Interrupt 131 | */ 132 | void USART_UDRI_Disable(void) 133 | { 134 | UCSR0B &= ~(1 << UDRIE0); 135 | } 136 | -------------------------------------------------------------------------------- /USART.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * 4 | * 5 | */ 6 | 7 | #ifndef USART_H 8 | #define USART_H 9 | 10 | #include 11 | 12 | #ifndef F_CPU 13 | #define F_CPU 16000000UL 14 | #endif 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | void USART_Init (uint16_t baud_rate); 21 | void USART_TXBuf_Write (uint16_t data); 22 | void USART_RXBuf_Read (uint16_t *data); 23 | void USART_RXBuf_Peek (uint16_t *data); 24 | uint8_t USART_TXBuf_IsEmpty (void); 25 | uint8_t USART_RXBuf_Count (void); 26 | 27 | void USART_Transmit (void); 28 | void USART_Receive (void); 29 | 30 | void USART_UDRI_Enable (void); 31 | void USART_UDRI_Disable (void); 32 | 33 | #endif /* USART_H */ 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif -------------------------------------------------------------------------------- /USART_ISR.c: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "USART.h" 3 | 4 | ISR(USART_RX_vect) 5 | { 6 | USART_Receive(); 7 | } 8 | 9 | ISR(USART_UDRE_vect) 10 | { 11 | if ( ! (USART_TXBuf_IsEmpty()) ) 12 | { 13 | USART_Transmit(); 14 | return; 15 | } 16 | USART_UDRI_Disable(); 17 | } 18 | -------------------------------------------------------------------------------- /bit_reg_defs.h: -------------------------------------------------------------------------------- 1 | #ifndef BIT_REG_DEFS_H 2 | #define BIT_REG_DEFS_H 3 | 4 | #define SET_BIT(r, b) (r |= (1 << b)) // set bit in register 5 | #define CLR_BIT(r, b) (r &= ~(1 << b)) // clear bit in register 6 | #define TGL_BIT(r, b) (r ^= (1 << b)) // toggle bit in register 7 | #define CHK_BIT(r, b) (r & (1 << b)) // check if bit in register is set 8 | 9 | #endif -------------------------------------------------------------------------------- /libs/foo.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mdb_interface_specification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LanguidSmartass/mdb-arduino-cashless/6bde7361dd2a4815dab177778b31f7b519ded5e7/mdb_interface_specification.pdf -------------------------------------------------------------------------------- /ringBuf.c: -------------------------------------------------------------------------------- 1 | #include "ringBuf.h" 2 | 3 | /* 4 | * Initialize ringBuf_t struct 5 | * ringBuf_t instance must be declared 6 | * before the call of this function 7 | */ 8 | void rBufInit(ringBuf_t *_this) 9 | { 10 | // memset(_this->buf, 0, RING_BUF_SIZE); 11 | // _this->head = 0; 12 | // _this->tail = 0; 13 | // or 14 | memset(_this, 0, sizeof(*_this)); 15 | } 16 | 17 | /* 18 | * Set all data to zero (technically same as rBufInit) 19 | */ 20 | void rBufFlush(ringBuf_t *_this) 21 | { 22 | memset(_this, 0, sizeof(*_this)); // or //rBufInit(_this); 23 | } 24 | 25 | /* 26 | * Push data to buf[head] and increment head 27 | * if head + 1 exceeds BUF_SIZE, null it 28 | * if head + 1, just return because buffer is full 29 | */ 30 | void rBufPushFront(ringBuf_t *_this, uint16_t data) 31 | { 32 | // uint8_t next = _this->head + 1; 33 | // if (next >= RING_BUF_SIZE) 34 | // next = 0; 35 | uint8_t next = (_this->head + 1) & RING_BUF_MASK; 36 | // Ring buffer is full 37 | if (rBufIsFull(_this)) // (next == _this->tail) 38 | return; // -1; 39 | 40 | _this->buf[_this->head] = data; 41 | _this->head = next; 42 | } 43 | /* 44 | * Pop buf[tail] element and increment tail 45 | * if tail == head, just return because buffer is empty 46 | * if tail exceeds BUF_SIZE, null it 47 | */ 48 | void rBufPopBack(ringBuf_t *_this, uint16_t *data) 49 | { 50 | if (rBufIsEmpty(_this)) // (_this->head == _this->tail) 51 | return; // -1; 52 | 53 | *data = _this->buf[_this->tail]; // Store value by pointer 54 | _this->buf[_this->tail] = 0; // Clear the data, optional 55 | // Update index with masking 56 | _this->tail = (_this->tail + 1) & RING_BUF_MASK; 57 | } 58 | /* 59 | * Read buf[tail] element, but do not move tail or overwrite the element 60 | */ 61 | void rBufPeekBack(ringBuf_t *_this, uint16_t *data) 62 | { 63 | if (rBufIsEmpty(_this)) // (_this->head == _this->tail) 64 | return; 65 | *data = _this->buf[_this->tail]; 66 | } 67 | 68 | uint8_t rBufIsEmpty(ringBuf_t *_this) 69 | { 70 | return ( _this->head == _this->tail ); 71 | } 72 | 73 | uint8_t rBufIsFull(ringBuf_t *_this) 74 | { 75 | return ( _this->head + 1 == _this->tail ); 76 | } 77 | 78 | uint8_t rBufElemCount(ringBuf_t *_this) 79 | { 80 | return (( _this->head - _this->tail ) & RING_BUF_MASK); 81 | } -------------------------------------------------------------------------------- /ringBuf.h: -------------------------------------------------------------------------------- 1 | #ifndef RING_BUF_H 2 | #define RING_BUF_H 3 | 4 | #include 5 | 6 | /* 7 | * Change this size according to your needs 8 | */ 9 | #define RING_BUF_SIZE 64 10 | #define RING_BUF_MASK (RING_BUF_SIZE - 1) 11 | 12 | typedef struct 13 | { 14 | uint16_t buf[RING_BUF_SIZE]; 15 | volatile uint8_t head; 16 | volatile uint8_t tail; 17 | } ringBuf_t; 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | void rBufInit (ringBuf_t *_this); 24 | void rBufFlush (ringBuf_t *_this); 25 | 26 | void rBufPushFront (ringBuf_t *_this, uint16_t data); 27 | void rBufPopBack (ringBuf_t *_this, uint16_t *data); 28 | void rBufPeekBack (ringBuf_t *_this, uint16_t *data); 29 | 30 | uint8_t rBufIsEmpty (ringBuf_t *_this); 31 | uint8_t rBufIsFull (ringBuf_t *_this); 32 | uint8_t rBufElemCount (ringBuf_t *_this); 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | 38 | #endif /* RING_BUF_H */ 39 | -------------------------------------------------------------------------------- /source/foo.txt: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------