AvBrand Exploring Technology
PopCARD: Pop Machine Cash Card - Source Code v2
This code is copyright © 2011 Avatar-X. If you use it, please let me know.
// POPCARD INTERFACE
// CREATED BY AvBrand.com, April 2011
// UPDATED OCTOBER 2011 for V2
#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>
#include "pitches.h"
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // the media access control (ethernet hardware) address for the shield:
byte ip[] = { 192, 168, 1, 11 }; //the IP address for the shield:
byte server[] = { 192, 168, 1, 1 }; // The server to connect to.
char data[50]; // Data buffer for TCP comms
byte dataPos = 0; // Position in data buffer.
char myName[20]; // Current user's name
float myCredit = 0; // Current credit
char serRead[20]; // Serial input buffer
byte serPos = 0; // Position in input buffer.
byte CURR_MODE = 0; // Current operation mode.
long modeStart = 0; // When did we enter this mode?
byte buyPrompt = 0; // Have we shown the "push to buy" prompt yet?
long lastPing = 0; // When did we receive the last ping?
byte TAKE_BUTTONS = 0; // When 1, we "steal" the buttons.
int buySelection = 0; // The selection we want to buy.
byte isSoldOut[10];
#define PIN_RFID_RX 0
#define PIN_RFID_RESET 2
#define PIN_LCD_RS 3
#define PIN_LCD_ENABLE 4
#define PIN_LCD_D4 5
#define PIN_LCD_D5 6
#define PIN_LCD_D6 7
#define PIN_LCD_D7 8
#define PIN_VEND_RELAY 9 // pulse twice to add $1 to machine
#define PIN_BILL_ENABLE A0 // when HIGH, the vending machine can accept money.
#define PIN_BUZZER A2 // the piezo buzzer
#define PIN_RESET_NET A3 // network module reset
#define PIN_SELECTION_1 23
#define PIN_SELECTION_2 25
#define PIN_SELECTION_3 27
#define PIN_SELECTION_4 29
#define PIN_SELECTION_5 31
#define PIN_SELECTION_6 33
#define PIN_SELECTION_7 35
#define PIN_SELECTION_8 37
#define PIN_SELECTION_9 39
#define PIN_BUTTON_1 22
#define PIN_BUTTON_2 24
#define PIN_BUTTON_3 26
#define PIN_BUTTON_4 28
#define PIN_BUTTON_5 30
#define PIN_BUTTON_6 32
#define PIN_BUTTON_7 34
#define PIN_BUTTON_8 36
#define PIN_BUTTON_9 38
// Sold out switches. Grounded when pressed, so if HIGH, selection is IN STOCK (NOT SOLD OUT)
#define PIN_SOLDOUT_1 40
#define PIN_SOLDOUT_2 41
#define PIN_SOLDOUT_3 42
#define PIN_SOLDOUT_4 43
#define PIN_SOLDOUT_5 44
#define PIN_SOLDOUT_6 45
#define PIN_SOLDOUT_7 46
#define PIN_SOLDOUT_8 47
#define PIN_SOLDOUT_9 48
// LCD and TCP clients
LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7);
Client client(server, 6643);
#define MODE_STARTUP 0 // Initial mode
#define MODE_CONNECT 1 // Connecting
#define MODE_UNAVAIL 2 // Unavailable / Not connected
#define MODE_IDLE 3 // Idle, Not doing anything
#define MODE_WAIT_NAME 4 // Tag was scanned, waiting for the name of the person
#define MODE_WAIT_CREDIT 5 // Name received, now waiting for the credit amount to show up.
#define MODE_READY 6 // Ready for the user to make a selection
#define MODE_ERROR 7 // Something went wrong.
#define MODE_WAIT_BUY 8 // Waiting for BUY confirmation.
#define MODE_NO_MONEY 9 // Not enough money for this selection
#define MODE_NO_MONEY2 10 // No Money message #2
#define MODE_BUYING 11 // Selection purchase process in progress
#define MODE_UNKNOWN 12 // Unknown card was scanned
#define MODE_SOLDOUT 13 // The selection is sold out.
#define MODE_ENJOY 14 // Enjoy your selection.
void setup()
{
// SPI pins on other arduino.
pinMode(10, INPUT);
pinMode(11, INPUT);
pinMode(12, INPUT);
pinMode(13, INPUT);
pinMode(PIN_SELECTION_1, OUTPUT);
pinMode(PIN_SELECTION_2, OUTPUT);
pinMode(PIN_SELECTION_3, OUTPUT);
pinMode(PIN_SELECTION_4, OUTPUT);
pinMode(PIN_SELECTION_5, OUTPUT);
pinMode(PIN_SELECTION_6, OUTPUT);
pinMode(PIN_SELECTION_7, OUTPUT);
pinMode(PIN_SELECTION_8, OUTPUT);
pinMode(PIN_SELECTION_9, OUTPUT);
// Selection buttons
pinMode(PIN_BUTTON_1, INPUT);
pinMode(PIN_BUTTON_2, INPUT);
pinMode(PIN_BUTTON_3, INPUT);
pinMode(PIN_BUTTON_4, INPUT);
pinMode(PIN_BUTTON_5, INPUT);
pinMode(PIN_BUTTON_6, INPUT);
pinMode(PIN_BUTTON_7, INPUT);
pinMode(PIN_BUTTON_8, INPUT);
pinMode(PIN_BUTTON_9, INPUT);
// Internal pullups
digitalWrite(PIN_BUTTON_1, HIGH);
digitalWrite(PIN_BUTTON_2, HIGH);
digitalWrite(PIN_BUTTON_3, HIGH);
digitalWrite(PIN_BUTTON_4, HIGH);
digitalWrite(PIN_BUTTON_5, HIGH);
digitalWrite(PIN_BUTTON_6, HIGH);
digitalWrite(PIN_BUTTON_7, HIGH);
digitalWrite(PIN_BUTTON_8, HIGH);
digitalWrite(PIN_BUTTON_9, HIGH);
// Sold out switches
pinMode(PIN_SOLDOUT_1, INPUT);
pinMode(PIN_SOLDOUT_2, INPUT);
pinMode(PIN_SOLDOUT_3, INPUT);
pinMode(PIN_SOLDOUT_4, INPUT);
pinMode(PIN_SOLDOUT_5, INPUT);
pinMode(PIN_SOLDOUT_6, INPUT);
pinMode(PIN_SOLDOUT_7, INPUT);
pinMode(PIN_SOLDOUT_8, INPUT);
pinMode(PIN_SOLDOUT_9, INPUT);
for (int i = 0; i < 10; i++) isSoldOut[i] = 0;
// Internal pullups
digitalWrite(PIN_SOLDOUT_1, HIGH);
digitalWrite(PIN_SOLDOUT_2, HIGH);
digitalWrite(PIN_SOLDOUT_3, HIGH);
digitalWrite(PIN_SOLDOUT_4, HIGH);
digitalWrite(PIN_SOLDOUT_5, HIGH);
digitalWrite(PIN_SOLDOUT_6, HIGH);
digitalWrite(PIN_SOLDOUT_7, HIGH);
digitalWrite(PIN_SOLDOUT_8, HIGH);
digitalWrite(PIN_SOLDOUT_9, HIGH);
buzz(NOTE_C4, 8); // Startup sound
delay(500);
// Start up the ethernet interface.
pinMode(PIN_RESET_NET, OUTPUT);
digitalWrite(PIN_RESET_NET, LOW);
// Startup sound again
buzz(NOTE_D4, 8);
// Set up the ethernet and serial.
Ethernet.begin(mac, ip);
Serial.begin(9600);
// Give the ethernet time to start up.
delay(500);
// Startup sound position 3
buzz(NOTE_E4, 8);
// Set up the LCD module
lcd.begin(16, 2);
lcd.print("Startup");
buzz(NOTE_G4, 8);
// Set up my pins.
pinMode(PIN_VEND_RELAY, OUTPUT);
pinMode(PIN_RFID_RESET, OUTPUT);
pinMode(PIN_BILL_ENABLE, INPUT);
pinMode(PIN_BUZZER, OUTPUT);
// Play the "One Up" sound to start
soundOneUp();
// Turn on the RFID module
digitalWrite(PIN_RFID_RESET, HIGH);
// Set the initial mode and make the connection.
setMode(MODE_STARTUP);
connectToServer();
}
void loop ()
{
// Main program loop.
int selButton = 0;
long mil = millis();
// Did the timer loop around? Prevent errors or hangs by the timer looping back to 0
if (lastPing > mil) lastPing = mil;
if (modeStart > mil) modeStart = mil;
// Is my TCP connection still alive?
if ((!client.connected() && lastPing == 0) || mil - lastPing > 15000)
{
// Reconnect.
connectToServer();
}
// If we are connected, then the rest of the logic can run.
switch (CURR_MODE)
{
case MODE_IDLE:
checkRFID(); // See if a tag has been scanned.
break;
case MODE_READY: // Only allow us to be in this mode for 8 seconds
if (mil - modeStart > 8000)
{
setMode(MODE_IDLE); // Jump out of this mode and go back to being ready to scan cards
}
if (mil - modeStart > 4000 && buyPrompt == 0) // Display buy prompt after 4 seconds
{
buyPrompt = 1;
// 1234567890123456
lcd.clear();
lcd.print("Make a selection");
lcd.setCursor(0,1);
lcd.print(" with buttons ");
break;
}
// Wait for the user to make a selection with one of the buttons
selButton = checkButtons();
// Was one of the buttons pressed?
if (selButton > 0)
{
// Is this selection in stock?
if (isSelectionInStock(selButton) == 1)
{
// See if we have enough money to buy this selection.
setMode(MODE_WAIT_BUY);
buySelection = selButton;
char bb[0];
bb[0] = selButton + 48;
sendCommand('B', bb);
} else {
// It's not in stock. Display a message.
setMode(MODE_SOLDOUT);
// Play a sad sound.
buzz(NOTE_G5, 8);
buzz(NOTE_G4, 8);
buzz(NOTE_G3, 8);
}
}
break;
case MODE_WAIT_NAME: // Timeout for these modes
case MODE_WAIT_CREDIT:
case MODE_WAIT_BUY:
if (mil - modeStart > 8000)
setMode(MODE_ERROR); // Jump out of this mode and go back to being ready to scan cards
break;
case MODE_ERROR:
case MODE_NO_MONEY2:
case MODE_UNKNOWN:
case MODE_ENJOY:
if (mil - modeStart > 2000)
setMode(MODE_IDLE); // Jump out of this mode and go back to being ready to scan cards
break;
case MODE_SOLDOUT:
if (mil - modeStart > 3000)
setMode(MODE_READY); // Go back to the credit display.
break;
case MODE_NO_MONEY:
if (mil - modeStart > 2000)
setMode(MODE_NO_MONEY2); // Show the 2nd 'No Money' message
break;
}
if (client.connected())
{
// See if the client has sent us any data.
checkNetData();
}
scanButtons();
}
void setMode(byte b) // Change to this mode.
{
CURR_MODE = b;
modeStart = millis(); // When did we enter this mode?
lcd.clear();
switch(b)
{
case MODE_STARTUP:
// 1234567890123456
lcd.print("Starting Up...");
break;
case MODE_CONNECT:
lcd.print("Connecting...");
break;
case MODE_UNAVAIL:
lcd.print("Cards Not Avail.");
lcd.setCursor(0,1);
lcd.print("Use Coins Below");
TAKE_BUTTONS = 0;
// Turn off the RFID module.
digitalWrite(PIN_RFID_RESET, LOW);
break;
case MODE_IDLE:
// 1234567890123456
lcd.print(" Scan your ");
lcd.setCursor(0,1);
lcd.print(" PopCARD now! ");
TAKE_BUTTONS = 0;
// Turn on the RFID module.
digitalWrite(PIN_RFID_RESET, HIGH);
break;
case MODE_WAIT_NAME:
TAKE_BUTTONS = 1; // Don't send the selection buttons to the vending machine.
lcd.print("Please wait...");
break;
case MODE_WAIT_CREDIT:
TAKE_BUTTONS = 1; // Don't send the selection buttons to the vending machine.
lcd.setCursor(0,1);
lcd.print("Please wait...");
break;
case MODE_READY:
TAKE_BUTTONS = 1; // Don't send the selection buttons to the vending machine.
buyPrompt = 0;
lcd.print(myName);
showMoney();
break;
case MODE_ERROR:
TAKE_BUTTONS = 0;
lcd.print("An error occured.");
break;
case MODE_WAIT_BUY:
TAKE_BUTTONS = 1; // Don't send the selection buttons to the vending machine.
lcd.print("Adding Credit, ");
lcd.setCursor(0,1);
lcd.print("Please Wait!");
break;
case MODE_SOLDOUT:
TAKE_BUTTONS = 1; // Don't send the selection buttons to the vending machine.
// 1234567890123456
lcd.print(" Sorry, that is ");
lcd.setCursor(0,1);
lcd.print(" SOLD OUT! ");
break;
case MODE_NO_MONEY:
// 1234567890123456
lcd.print("Not Enough Funds");
lcd.setCursor(0,1);
lcd.print("Something else? ");
showMoney();
break;
case MODE_NO_MONEY2:
// 1234567890123456
lcd.print("See Alex, Unit#5");
lcd.setCursor(0,1);
lcd.print("to buy credit.");
break;
case MODE_UNKNOWN:
TAKE_BUTTONS = 0;
// 1234567890123456
lcd.print("Invalid or");
lcd.setCursor(0,1);
lcd.print("Unknown Card");
break;
case MODE_ENJOY:
// 1234567890123456
lcd.print(" Enjoy your ");
lcd.setCursor(0,1);
lcd.print(" beverage! ");
TAKE_BUTTONS = 1;
break;
}
}
void showMoney()
{
lcd.setCursor(0,1);
// 1234567890123456
// Credit: $XX.XX
lcd.print("Credit: $");
lcd.print(myCredit, DEC);
lcd.setCursor(myCredit < 10 ? 13 : 14, 1);
lcd.print(" ");
}
void connectToServer()
{
lastPing = millis();
setMode(MODE_CONNECT);
// Reset the network module before all connections.
buzz(NOTE_C4, 8);
digitalWrite(PIN_RESET_NET, LOW);
scanDelay(100);
digitalWrite(PIN_RESET_NET, HIGH);
scanDelay(500);
Ethernet.begin(mac, ip);
scanDelay(1000);
// Reset the sold out status so they get resent to the server.
for (int i = 0; i < 10; i++) isSoldOut[i] = 2;
client.stop(); // Close the connection
if (client.connect())
{
setMode(MODE_IDLE);
}
else
{
setMode(MODE_UNAVAIL);
}
}
void checkNetData()
{
// See if there is data available on the network interface.
if (client.available() > 0)
{
byte d = client.read();
switch (d)
{
case 0x02: // Start of transmission
dataPos = 0;
break;
case 0x03: // End of transmission
data[dataPos] = 0; // Blank out the end.
readCommand();
break;
default: // Data byte. Put it in the buffer.
data[dataPos] = d;
dataPos++;
}
}
}
void readCommand() // Parse an input command from the network interface.
{
// First byte should be the command.
char dd[20];
// Copy the data.
for (byte i = 1; i <= dataPos && i < 21; i++)
dd[i-1] = data[i];
switch (data[0])
{
case 'P': // Ping
lastPing = millis();
break;
case 'N': // Name has arrived. Copy into MyName
if (CURR_MODE == MODE_WAIT_NAME)
{
for (byte i = 1; i <= dataPos && i < 21; i++)
myName[i-1] = data[i];
setMode(MODE_WAIT_CREDIT);
}
break;
case 'C': // Credit amount has arrived
if (CURR_MODE == MODE_WAIT_CREDIT)
{
myCredit = atof(dd);
setMode(MODE_READY);
}
break;
case 'Y': // Yes, you may buy.
if (CURR_MODE == MODE_WAIT_BUY)
{
// Buy this selection!
if (buySelection > 0)
{
buyNow(buySelection, atoi(dd));
buySelection = 0;
}
// After the buy, return to idle mode.
setMode(MODE_ENJOY);
}
break;
case 'X': // Not enough money.
if (CURR_MODE == MODE_WAIT_BUY)
{
// Play a sad sound.
buzz(NOTE_G5, 8);
buzz(NOTE_G4, 8);
buzz(NOTE_G3, 8);
// Show the no money message.
setMode(MODE_NO_MONEY);
}
break;
case 'R': // Unknown card.
setMode(MODE_UNKNOWN);
break;
}
}
void sendCommand(byte cmd, char cData[]) // Send a command to the host.
{
// Send a command back to the server.
client.write(0x02);
client.write(cmd);
client.print(cData);
client.write(0x03);
}
void checkRFID()
{
// Check the serial port for data from the RFID module.
if (Serial.available() > 0)
{
byte d = Serial.read();
switch (d)
{
case 0x02: // Start of transmission
serPos = 0;
break;
case 0x03: // End of transmission
readTag();
break;
default: // Add to buffer.
serRead[serPos] = d;
serPos++;
}
}
}
void readTag() // Parse the data from a card.
{
serRead[10] = 0;
Serial.print("Card Read:");
Serial.println(serRead);
setMode(MODE_WAIT_NAME);
// Make a beep
buzz(NOTE_B4, 8);
buzz(NOTE_E5, 4);
// Ask the server who this is.
sendCommand('S', serRead);
// Turn off the RFID module.
digitalWrite(PIN_RFID_RESET, LOW);
}
void addCredit()
{
// Pulsing the 'VEND NO' and 'VEND COM' lines on the vendor
// twice tells it that $1 has been inserted.
digitalWrite(PIN_VEND_RELAY, HIGH);
delay(150);
digitalWrite(PIN_VEND_RELAY, LOW);
delay(400);
digitalWrite(PIN_VEND_RELAY, HIGH);
delay(150);
digitalWrite(PIN_VEND_RELAY, LOW);
}
void buzz(long note, long dTime)
{
// Play a tone.
tone(PIN_BUZZER, note, 1000 / dTime);
delay(1000/dTime);
noTone(PIN_BUZZER);
}
void soundOneUp()
{
// Make the one-up sound.
tone(PIN_BUZZER, NOTE_E5, 125);
delay(125);
tone(PIN_BUZZER, NOTE_G5, 125);
delay(125);
tone(PIN_BUZZER, NOTE_E6, 125);
delay(125);
tone(PIN_BUZZER, NOTE_C6, 125);
delay(125);
tone(PIN_BUZZER, NOTE_D6, 125);
delay(125);
tone(PIN_BUZZER, NOTE_G6, 125);
delay(125);
noTone(PIN_BUZZER);
}
void scanDelay(int t)
{
// Do a delay but also scan the buttons during that time.
long ms = millis();
do {
// Add other functions here if needed
scanButtons();
} while (millis() - ms < t && millis() > ms);
}
void scanButtons()
{
// watch the button inputs
digitalWrite(PIN_SELECTION_1, digitalRead(PIN_BUTTON_1) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_2, digitalRead(PIN_BUTTON_2) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_3, digitalRead(PIN_BUTTON_3) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_4, digitalRead(PIN_BUTTON_4) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_5, digitalRead(PIN_BUTTON_5) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_6, digitalRead(PIN_BUTTON_6) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_7, digitalRead(PIN_BUTTON_7) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_8, digitalRead(PIN_BUTTON_8) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
digitalWrite(PIN_SELECTION_9, digitalRead(PIN_BUTTON_9) == HIGH || TAKE_BUTTONS == 1 ? LOW : HIGH);
checkSoldOut(1, PIN_SOLDOUT_1);
checkSoldOut(2, PIN_SOLDOUT_2);
checkSoldOut(3, PIN_SOLDOUT_3);
checkSoldOut(4, PIN_SOLDOUT_4);
checkSoldOut(5, PIN_SOLDOUT_5);
checkSoldOut(6, PIN_SOLDOUT_6);
checkSoldOut(7, PIN_SOLDOUT_7);
checkSoldOut(8, PIN_SOLDOUT_8);
checkSoldOut(9, PIN_SOLDOUT_9);
}
void checkSoldOut(int n, int pin)
{
int isOut = (digitalRead(pin) == LOW ? 1 : 0);
if (isOut != isSoldOut[n-1])
{
// Tell the server the sold out status.
isSoldOut[n-1] = isOut;
char bb[2];
bb[0] = n + 48;
bb[1] = isOut == 1 ? 'Y' : 'N';
sendCommand('L', bb);
}
}
int checkButtons()
{
// Return the first button that was pressed.
if (digitalRead(PIN_BUTTON_1) == LOW) return 1;
if (digitalRead(PIN_BUTTON_2) == LOW) return 2;
if (digitalRead(PIN_BUTTON_3) == LOW) return 3;
if (digitalRead(PIN_BUTTON_4) == LOW) return 4;
if (digitalRead(PIN_BUTTON_5) == LOW) return 5;
if (digitalRead(PIN_BUTTON_6) == LOW) return 6;
if (digitalRead(PIN_BUTTON_7) == LOW) return 7;
if (digitalRead(PIN_BUTTON_8) == LOW) return 8;
if (digitalRead(PIN_BUTTON_9) == LOW) return 9;
return 0;
}
byte isSelectionInStock(int n)
{
// Check to see if the selection is in stock.
// A value of HIGH means the sold-out switch is NOT depressed, meaning the selection is in stock.
if (n == 1) return (digitalRead(PIN_SOLDOUT_1) == HIGH ? 1 : 0);
if (n == 2) return (digitalRead(PIN_SOLDOUT_2) == HIGH ? 1 : 0);
if (n == 3) return (digitalRead(PIN_SOLDOUT_3) == HIGH ? 1 : 0);
if (n == 4) return (digitalRead(PIN_SOLDOUT_4) == HIGH ? 1 : 0);
if (n == 5) return (digitalRead(PIN_SOLDOUT_5) == HIGH ? 1 : 0);
if (n == 6) return (digitalRead(PIN_SOLDOUT_6) == HIGH ? 1 : 0);
if (n == 7) return (digitalRead(PIN_SOLDOUT_7) == HIGH ? 1 : 0);
if (n == 8) return (digitalRead(PIN_SOLDOUT_8) == HIGH ? 1 : 0);
if (n == 9) return (digitalRead(PIN_SOLDOUT_9) == HIGH ? 1 : 0);
}
void buyNow(int b, int cost)
{
// Buy this selection!.
// We need to add one credit for each cost dollar.
int dollLeft = cost;
do {
dollLeft--;
soundOneUp(); // Play the credit noise
addCredit(); // Add $1 to the vending machine.
delay(900); // Provide enough time for the pop machine to register the credit.
// Wait until the vend relay says it's ok.
if (dollLeft > 0) // But only if we still have to put money in.
{
long ms = millis();
do {
if (digitalRead(PIN_BILL_ENABLE) == HIGH) break;
} while (millis() - ms < 1000 && millis() > ms);
}
} while (dollLeft > 0);
delay(300);
// NOW, press the selection button!
int selPin = 0;
if (b == 1) selPin = PIN_SELECTION_1;
if (b == 2) selPin = PIN_SELECTION_2;
if (b == 3) selPin = PIN_SELECTION_3;
if (b == 4) selPin = PIN_SELECTION_4;
if (b == 5) selPin = PIN_SELECTION_5;
if (b == 6) selPin = PIN_SELECTION_6;
if (b == 7) selPin = PIN_SELECTION_7;
if (b == 8) selPin = PIN_SELECTION_8;
if (b == 9) selPin = PIN_SELECTION_9;
digitalWrite(selPin, HIGH);
// Give the vender time to see it
delay(1000);
digitalWrite(selPin, LOW);
// Make sure it doesn't try to reconnect uselessly now.
lastPing = millis();
// All done!
}