Daddy's Online Order Arduino Bot

by vanweb in Circuits > Arduino

71 Views, 1 Favorites, 0 Comments

Daddy's Online Order Arduino Bot

0002.jpg
5040.jpg
5000.jpg
4020a.jpg
part 3d Case.JPG

The Need: Online food order notification system for Daddy's Fried Chicken restaurant.

The Issue: My friends fried chicken restaurant ( https://daddyschicken.ca/ ) in Toronto accepts online orders which come to the restaurant as an email. If the counter person is not behind the counter at the time they may miss the email notification. I was asked if there was anything that I could do so that there was a visible persistent notification that the counter person would easily know that there was a new order as there was a time frame for 25 minutes from the time the order was placed to when it was received.

The Solution: Luckily the order system had an API that I could tap into, unfortunately the API was only for the payment record and not the order details. The solution consisted of 3 Steps:

  1. Create PHP page to read the API, filter out only the online orders for the previous 30 minutes and save the most recent order number (to be able to distinguish new orders from previously read orders).
  2. Program the Arduino UNO R4 WIFI to read the PHP page detect if there are any orders, if there are then determine if any of them are new.
  3. 3D print a "restaurant proof" enclosure to hold a 2 Line LCD and a strip of RGB LEDs attached to the Arduino in order to display the current order status and allow for order acknowledgement.

Downloads

Supplies

part Arduino WIFI.JPG
parts LCD.jpg
parts LED Strip.JPG
part 3d Case.JPG
  1. Arduino UNO R4 WIFI
  2. I2C LCD1602
  3. WS2812 RGB LEDs Strip of 8 NeoPixel LEDs
  4. Custom 3d printed case

The PHP

4010.jpg

I created a very basic PHP program that could be accessed via a web call. The first part sets the basics such as API Key, Order start time, file name and location for the simple text storage of the last order number along with the basic business hours of the restaurant so that the API will not be constantly called 7/24.

Using this information I used the PHP "curl" function to access the API pulling all records after the set order time (current time minus 25 minutes) pulling all this information into a JSON record. Lastly open the file containing the last new order number and saving it into a variable.

The second half of the program loops through the JSON file and only pulls out the Online order payments. If there is a new order it then saves that order number our file. The program then displays info from each record in the last 25 minutes: Record count, Order time, Sale dollar amount, Credit card brand used and Expiry date. When all records are parsed a final field is written that indicates if there is a new order or not.

Now I should have created this "file" as a JSON file but it was quicker/easier for me just to parse a text file in the Arduino then to have to figure out how to handle JSON ( as I have never programmed an Arduino there was enough for me to learn!).

This PHP would be called every 3 minutes by the Arduino and return all orders entered in the last 25 minutes (could be 0 orders) and if there is an order that had not been read before it would be indicated.


The PHP Code:


<?php
date_default_timezone_set("America/New_York");
$timeinhour = date("H");
$dayis = date("D");
$open = "No";


if (($dayis == "Mon" && $timeinhour >= 12 && $timeinhour <= 21))
{
$open = "Yes";
}
if (($dayis == "Thu" && $timeinhour >= 11 && $timeinhour <= 22))
{
$open = "Yes";
}
if (($dayis == "Fri" && $timeinhour >= 11 && $timeinhour <= 22))
{
$open = "Yes";
}
if (($dayis == "Sat" && $timeinhour >= 11 && $timeinhour <= 22))
{
$open = "Yes";
}
if (($dayis == "Sun" && $timeinhour >= 11 && $timeinhour <= 21))
{
$open = "Yes";
}

if ($open == "No") {
echo "{START= Closed =END} `OLD`";
} else {
$access_token="123xyz";
date_default_timezone_set("UTC");
$daDate = date("Y-m-d");
$daTime = date("H:i");
$daEnd = $daDate."T".$daTime.":00";
$oneHourAgoDate = date('Y-m-d', strtotime('-20 minutes'));
$oneHourAgoTime = date('H:i:s', strtotime('-20 minutes'));
$daStart = $oneHourAgoDate."T".$oneHourAgoTime;
$fileLocation = getenv("DOCUMENT_ROOT") . "/lastpickup.txt";

$curl = curl_init();
curl_setopt($curl,CURLOPT_URL, "https://api.fakedomain.com/v2/payments?begin_time=".$daStart."&end_time=".$daEnd);
curl_setopt($curl,CURLOPT_POST, "true");
curl_setopt($curl,CURLOPT_HTTPHEADER, array('authorization: Bearer '.$access_token,'content-type: application/json','Version: 2021-05-13','Content-Type: application/json'));

curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($curl);
$datasearch = json_decode($data, true);

$file = fopen($fileLocation,"r");
while ($line = fgets($file)) {
$savedOrder = $line;
}
fclose($file);

$recCnt = 1;
echo '{START='. PHP_EOL;
foreach($datasearch as $key => $value)
{
foreach($value as $key1 => $value1)
{
if ($value1[note] == 'Online Transaction' && $value1[card_details][status] == 'CAPTURED') {
if ($recCnt==1) {
$content = $value1[order_id];
if ($savedOrder!=$content) {
$file = fopen($fileLocation,"w");
fwrite($file,$content);
fclose($file);
}
}
$d=strtotime($value1[created_at]);
date_default_timezone_set("America/New_York");
echo '[' . $recCnt .": " . date('g:i', $d).date('a', $d).' ';
$spend = number_format((float)$value1[amount_money][amount], 2, '.', '');
$spend2 = number_format($spend/100, 2, '.', '');
echo '$'. $spend2 .' | ';
if ($value1[card_details][card][card_brand]=="AMERICAN_EXPRESS") {
echo 'AMEX ';
}
elseif ($value1[card_details][card][card_brand]=="MASTERCARD") {
echo 'MAST ';
}
elseif ($value1[card_details][card][card_brand]=="DISCOVERY") {
echo 'DISC ';
} else {
echo $value1[card_details][card][card_brand].' ';
};
echo $value1[card_details][card][last_4].' ';
echo $value1[card_details][card][exp_month].'/'. substr($value1[card_details][card][exp_year], -2).']';
echo PHP_EOL;
$recCnt = $recCnt+1;
}
}
}
echo '=END}'. PHP_EOL;

if ($content != $savedOrder & $content != "") {
echo "`NEW`". PHP_EOL;
} else {
echo "`OLD`". PHP_EOL;
}
}
?>

The Arduino Hardware

5040.jpg
parts LCD.jpg
parts LED Strip.JPG
5050.jpg

The hardware I used was pretty basic and required no custom boards, no resistors and no capacitors just the Arduino UNO R4 WIFI itself along with an I2C LCD1602 and a WS2812 Strip of 8 NeoPixel RGB LEDs and a few wire leads.

The Arduino Code

3010.jpg
3018.jpg
3030.jpg
3050.jpg
4020.jpg
4030.jpg

This was my first time programming an Arduino so don't roast me too hard :-).

The basics of what the program does is:

  1. Include the libraries and add the needed variables
  2. In the Setup function connect via WIFI to the above PHP page communicating the status via the LCD and displaying red LEDs while the board is connecting.
  3. Once connected the Loop function will read the PHP data and start processing the information.
  4. The first thing done is to determine the number of orders and if the data contains a new never read order.
  5. If there is a New order the LEDs with turn Green.
  6. Next the system reads the "Closed" flag if that is set the board briefly displays "Closed" on the LCD and then adds a half hour-ish delay before it looks again limiting the number of calls the system needs to make.
  7. If the restaurant is Open it next checks the number of orders that were received in the last 25 minutes.
  8. If there are no orders it displays "No Orders" on the LCD and waits about 3 minutes to call the PHP again.
  9. If there is One order the parsed data is displayed on the 2 lines of the LCD: Order time, Order dollar amount then on the second line Credit card type , last 4 digits of the card and the expiry date. (This information is enough to identify the order owner when it is picked up but cannot be used for nefarious purposes if intercepted.)
  10. This information stays on the LCD screen for the next 3 minutes until the PHP is called again.
  11. If more than one order is recieved (it is possible that there could be a number of orders recieved in the 25 minute window) the system determines the how many times the each order will need to be shown in the 3 minute window.
  12. The orders are displayed on the LCD with the newest first and shown for a short time then replaced with the next order and so on.. once all orders have been shown it loops back to the beginning until the time the PHP is called again.
  13. When there is a new order and the LEDs are displaying green the front counter person compares the information in the LCD to the recent email orders on the Tablet and if it has been received the click the red button which activates the board reset button this turns off the light and reconnects to the WIFI and reads the PHP orders again. As the "New" order was already read there would not be a "New" order flag and the Green light will not be activated but the order(s) will be displayed on the LCD.

And that is the logic... below is the actual code (with the private information removed).

#include "WiFiS3.h"
#include <ArduinoHttpClient.h>
#include <FastLED.h>
#include <LiquidCrystal_I2C.h>

#define NUM_LEDS 8
#define DATA_PIN 6
LiquidCrystal_I2C lcd(0x27, 16, 2);
CRGB leds[NUM_LEDS];

char ssid[] = "xxxxx";
char pass[] = "yyyyy";


char serverAddress[] = "api.123domain.com";
int port = 80;
WiFiClient wifi;
HttpClient client = HttpClient(wifi, serverAddress);
int status = WL_IDLE_STATUS;
int baseDelay = 120000;
int displayDelay = 5000;
int shortDelay = 20000;
int fastDelay = 150;
int quickDelay = 1500;
int closedDelay = 1800000;

void setup() {
lcd.init();
lcd.clear();
lcd.backlight();
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
Serial.begin(9600);
while ( status != WL_CONNECTED) {
digitalWrite(LED_BUILTIN, HIGH);
for (int dot = 0; dot < NUM_LEDS; dot++) {
leds[dot] = CRGB::Red;
FastLED.show();
delay(fastDelay);
}
Serial.println("Connecting");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Connecting");
status = WiFi.begin(ssid, pass);
}
for (int dot = 0; dot < NUM_LEDS; dot++) {
leds[dot] = CRGB::Black;
FastLED.show();
delay(fastDelay);
}
IPAddress ip = WiFi.localIP();
Serial.println("Connected");
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" Connected");
}

void loop() {
client.get("/pickup_order.php");
int statusCode = client.responseStatusCode();
String result = client.responseBody();
String totOrders0 = result.substring(result.indexOf('}')-42,result.indexOf('}')-42+6);
String totOrders = totOrders0.substring(totOrders0.indexOf('[')+1,totOrders0.indexOf(':'));
int numOrders = totOrders.toInt();
int numLoops = 15 / numOrders;

String customerNew = result.substring(result.indexOf('`')+1,result.indexOf('`')+4);
if (customerNew == "NEW"){
digitalWrite(LED_BUILTIN, HIGH);
for (int dot = 0; dot < NUM_LEDS; dot++) {
leds[dot] = CRGB::Green;
FastLED.show();
delay(fastDelay);
}
}

lcd.clear();
String customerFull = result.substring(result.indexOf('{')+8,result.indexOf('}'));
String customerClosed = result.substring(result.indexOf('=')+2,result.indexOf('=')+8);

if (customerClosed == "Closed"){
Serial.println("Closed");
lcd.backlight();
lcd.clear();
lcd.print(" Daddy's Closed");
delay(quickDelay);
lcd.clear();
lcd.noBacklight();
delay(closedDelay);
} else if (numOrders == 0) {
Serial.println("No Orders");
lcd.backlight();
lcd.clear();
lcd.print(" No Orders");
delay(displayDelay);
lcd.clear();
lcd.noBacklight();
} else if (numOrders == 1) {
if (customerNew == "NEW") {
Serial.print("NEW - ");
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("NEW - ");
}
Serial.println("One Order");
lcd.backlight();
lcd.print("One Order");
delay(displayDelay);

int indexPoint = 0;
String customerOrder = customerFull.substring(customerFull.indexOf('[',indexPoint)+1,customerFull.indexOf(']',indexPoint));
indexPoint = indexPoint + customerOrder.length() + 3;
Serial.println(customerOrder.substring(3,customerOrder.indexOf('|')));
Serial.println(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(customerOrder.substring(3,customerOrder.indexOf('|')));
lcd.setCursor(0, 1);
lcd.print(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));

} else {
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
if (customerNew == "NEW") {
Serial.print("NEW - ");
lcd.print("NEW - ");
}

Serial.print(numOrders);
Serial.println(" Orders");
lcd.print("Orders: ");
lcd.print(numOrders);
delay(displayDelay);
lcd.clear();

int indexPoint = 0;
String customerOrder = "";
for (int i = 0; i <= numLoops; i++) {
indexPoint = 0;
customerOrder="";

for (int order = 1; order <= numOrders; order++) {
customerOrder = customerFull.substring(customerFull.indexOf('[',indexPoint)+1,customerFull.indexOf(']',indexPoint));
indexPoint = indexPoint + customerOrder.length() + 3;
Serial.println(customerOrder.substring(3,customerOrder.indexOf('|')));
Serial.println(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(customerOrder.substring(3,customerOrder.indexOf('|')));
lcd.setCursor(0, 1);
lcd.print(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));
delay(displayDelay);
}
}

indexPoint = 0;
customerOrder = customerFull.substring(customerFull.indexOf('[',indexPoint)+1,customerFull.indexOf(']',indexPoint));
indexPoint = indexPoint + customerOrder.length() + 3;
Serial.println(customerOrder.substring(3,customerOrder.indexOf('|')));
Serial.println(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(customerOrder.substring(3,customerOrder.indexOf('|')));
lcd.setCursor(0, 1);
lcd.print(customerOrder.substring(customerOrder.indexOf('|')+2,customerOrder.indexOf(']')));
delay(displayDelay);
}

if (numOrders >= 2) {
delay(shortDelay);
} else {
delay(baseDelay);
}
}

Downloads

The 3D Printed Case

part 3d Case.JPG
part body.JPG
5030.jpg
5050.jpg
part screen.JPG
2000.jpg
part light.JPG
2010.jpg
2020.jpg
2040.jpg

I designed the case to protect the Arduino and display components especially since this is to be used in a restaurant environment. Also, because the system only has one button (a red Reset button) it is very user friendly and quick to communicate the needed information.

I downloaded a free Arduino case STL file and using TinkerCad ( https://www.tinkercad.com/things/dhntIMvg5LQ-weebly-alert?sharecode=cOHKeue2fV6xk0_kB85p2-FoQUvNh-HqQPBublgamo8 ) I added higher walls in order to fully contain the board and the wires and a hollow cylinder for the reset button to ride in. For the top I added a hole for the reset button and screw holes to mount the LCD. The Rest button was printed in translucent filament so that the internal board led would give it a bit of a glow. The "pin" of the button was split with a slightly fatter end so that one in place it could not slide out even if it was shaken upside down.

The LCD mount sits higher and swivels so that the screen can be tilted to an optimal angle so that it can be easily read. Again, screw holes were added so that everything could be securely attached.

Next the LED strip holder was made, the front being "clear" translucent filament with a green back. The light circle has built in tabs to hold the LED strip to the outer edge and grooves to account for the external wires.

Lastly an external base was made to hide and protect the USB port as this would only be needed for software updates. Plus to give the whole system a very solid base the feet have non-slip rubber pads added so it will not slide off the tiled counter.

All Together Now!

0002.jpg
0000.jpg
3010.jpg
3020.jpg
4020.jpg
5010.jpg

The Order Bot has been installed and working in the restaurant for a month now and has been a great help in making sure all food orders get processed. It need almost zero staff training as it is so intuitive after they are shown how to use it one time which is great in the high turnover hospitality industry.

I have always enjoyed building things but even more so when there is a real need and they help people be more productive. There are things I know that I could have done better but I think this is perfect for a "V1" iteration!

Downloads