AvBrand Exploring Technology
PopCARD: Pop Machine Cash Card - Source Code
This code is copyright © 2011 Avatar-X. If you use it, please let me know.
// POPCARD INTERFACE
// CREATED BY AvBrand.com, April 2011
#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>
#include "pitches.h"
// the media access control (ethernet hardware) address for the shield:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 10 }; //the IP address for the shield:
byte server[] = { 192, 168, 1, 11 }; // 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?
#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_BUTTON A1 // press to add money
#define PIN_BUZZER A2 // the piezo buzzer
#define PIN_RESET_NET A3 // network module reset
// 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 insert money with the button
#define MODE_ERROR 7 // Something went wrong.
#define MODE_WAIT_BUY 8 // Waiting for BUY confirmation.
#define MODE_NO_MONEY 9 // No money
#define MODE_NO_MONEY2 10 // No Money message #2
#define MODE_NO_MORE 11 // Can't put any money in right now, due to INHIBIT
#define MODE_UNKNOWN 12 // Unknown card was scanned
void setup()
{
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_BUTTON, 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.
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("Push Button");
lcd.setCursor(0,1);
lcd.print("to spend $1.00");
break;
}
// Check the button.
if (digitalRead(PIN_BUTTON) == HIGH)
{
// Are we allowed to add money?
if (digitalRead(PIN_BILL_ENABLE) == HIGH)
{
setMode(MODE_WAIT_BUY);
sendCommand('B', "");
} else {
// We've already reached the max amount, or maybe the pop machine isn't ready for us.
setMode(MODE_NO_MORE);
}
}
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:
if (mil - modeStart > 2000)
setMode(MODE_IDLE); // Jump out of this mode and go back to being ready to scan cards
break;
case MODE_NO_MORE:
if (mil - modeStart > 2000)
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();
}
}
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");
// 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! ");
// Turn on the RFID module.
digitalWrite(PIN_RFID_RESET, HIGH);
break;
case MODE_WAIT_NAME:
lcd.print("Please wait...");
break;
case MODE_WAIT_CREDIT:
lcd.setCursor(0,1);
lcd.print("Please wait...");
break;
case MODE_READY:
buyPrompt = 0;
lcd.print(myName);
showMoney();
break;
case MODE_ERROR:
lcd.print("An error occured.");
break;
case MODE_WAIT_BUY:
lcd.print("Adding Credit, ");
lcd.setCursor(0,1);
lcd.print("Please Wait!");
break;
case MODE_NO_MONEY:
// 1234567890123456
lcd.print("Not Enough Funds");
showMoney();
break;
case MODE_NO_MORE:
// 1234567890123456
lcd.print("Max Amount");
lcd.setCursor(0,1);
lcd.print("Reached.");
break;
case MODE_NO_MONEY2:
// 1234567890123456
lcd.print("See Alex");
lcd.setCursor(0,1);
lcd.print("to buy credit.");
break;
case MODE_UNKNOWN:
// 1234567890123456
lcd.print("Invalid or");
lcd.setCursor(0,1);
lcd.print("Unknown Card");
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);
delay(100);
digitalWrite(PIN_RESET_NET, HIGH);
delay(500);
Ethernet.begin(mac, ip);
delay(1000);
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)
{
myCredit = atof(dd); // Store the new credit amount
soundOneUp(); // Play the credit noise
addCredit(); // Add $1 to the vending machine.
delay(700); // Provide enough time for the pop machine to register the credit.
setMode(MODE_READY);
}
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);
}