Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
|
In the last two years, extensive QR code payments have been implemented in Thailand. The government has prepared a "PromptPay ThaiQR code" standard to enable the qr code in Thailand to use the same format and can be used across banks. So I have an idea to utilized the M5Stack Faces Calculator & "mPAY's QR unified API" to create cheap EDC. Because mPay's API is supported ThaiQR, VIA, Rabbit LinePay and Wechat, so I implemented all of them.
System DiagramM5Stack connect to the internet via wifi connection but it not connect directly to mPAY's API because I'm not sure if M5Stack will handle json or not. so I create simple web service for translate http get request from M5Stack to json that required for mPAY's API.
PS. I cannot disclose any information about the share server and mPAY's API. Sorry for that.
- Support QR code payment for "ThaiQR."
- Support QR code payment for "VIA."
- Support QR code payment for "Rabbit LinePay (th)."
- Support QR code payment for "WeChat."
- Can view payment history within device.
- If user not sure about payment status. it can inquiry payment status manually.
- Install M5Stack calculator face
- Power on the M5Stack
- If this is a first run you need to config WiFi and AP key. Press left button for setting. It will create AP just use your mobile connect to this AP then scan QR code for go to config page.
- press right button to start. it will ask PIN that associate with share server.
- In Main menu. use "SEL" for navigate menu selection. "LOGOUT" for go back to first screen. "ENT" to confirm menu selection.
/*
QR BsC Demo by using mpay qr unified api as payment engin
creator : Patti U-aksorn
*/
#include <M5Stack.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiClient.h>
#include "WebServer.h"
#include <HTTPClient.h>
#include <Preferences.h>
//#include "FS.h"
//#include "SD.h"
//#include "SPI.h"
//menu val
char* page="main";
boolean paymentMode=false;
boolean haveConfig=false;
boolean wifiConnect=false;
String pin;
boolean firstRun=true;
byte selPI=0;
byte selHis=0;
unsigned long lastTime=0;
unsigned long totalOrderFile=0;
String orderHistory[6];
//keyboard
#define KEYBOARD_I2C_ADDR 0X08
#define KEYBOARD_INT 5
//setting val
const IPAddress apIP(192, 168, 0, 1);
const char* apSSID = "mPAY_SETUP";
boolean settingMode;
String ssidList;
String wifi_ssid;
String wifi_password;
String app_key;
WebServer webServer(80);
Preferences preferences;
//web service val
const String baseURL = "http://your.api.gateway";
int httpStatus;
String amount="";
String orderID="";
String QRcode="";
String TXID="";
void setup() {
// put your setup code here, to run once:
m5.begin();
Serial.begin(115200);
Wire.begin();
pinMode(KEYBOARD_INT, INPUT_PULLUP);
M5.Lcd.fillScreen(WHITE);
}
void loop() {
// put your main code here, to run repeatedly:
M5.update();
if (page=="main") {
pageMain();
}
if (page=="config") {
pageConfig();
}
if (page=="login") {
pageLogin();
}
if (page=="formLogin") {
formLogin();
}
if (page=="selectPI") {
pageSelectPI();
}
if (page=="history") {
viewHistory();
}
if (page=="amount") {
inputAmount();
}
if (page=="QR") {
showQR();
}
if (page=="Success") {
successPage();
}
if (page=="orderdetail") {
orderDetail();
}
}
void pageMain() {
if (firstRun==true) {
//load logo
M5.Lcd.drawJpgFile(SD, "/image/logo.jpg");
//header
M5.Lcd.setCursor(2, 2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(1);
M5.Lcd.printf("mPAY Merchant Technical Team");
//create menu
M5.Lcd.setCursor(20, 225);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.printf(".SETTING.");
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(215, 225);
M5.Lcd.printf(".START.");
firstRun=false;
}
//buttom event
if (M5.BtnA.wasReleased()) {
//goto config mode
//Serial.println("button A");
page="config";
M5.Lcd.fillScreen(WHITE);
preferences.begin("wifi-config");
delay(10);
if (restoreConfig()) {
if (checkConnection()) {
settingMode = false;
startWebServer();
return;
}
}
settingMode = true;
setupMode();
} else if (M5.BtnC.wasReleased()) {
//goto payment mode
//M5.update();
page="login";
Serial.println("enter payment mode");
M5.Lcd.fillScreen(WHITE);
preferences.begin("wifi-config");
delay(500);
haveConfig=restoreConfig();
firstRun=true;
pageLogin();
}
}
void pageLogin() {
if (haveConfig==true) {
//if (wifiConnect==false) {
if (WiFi.status() != WL_CONNECTED) {
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(215, 225);
M5.Lcd.printf(".RETRY.");
if (M5.BtnC.wasReleased()) {
//re-try
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 80);
wifiConnect=checkConnection();
if (wifiConnect==true) M5.Lcd.fillScreen(WHITE);
}
} else {
//Serial.println("Wifi Connected.Please Login");
//create menu
M5.update();
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(30, 225);
M5.Lcd.printf(".BACK.");
M5.Lcd.setCursor(132, 225);
M5.Lcd.printf(".DEL.");
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(215, 225);
M5.Lcd.printf(".LOGIN.");
//goto login form
//M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
//formLogin();
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(4);
M5.Lcd.setCursor(10, 40);
M5.Lcd.printf("Enter PIN.");
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
//delay(500);
page="formLogin";
delay(500);
//formLogin();
}
} else {
Serial.println("Not Config. Please go to setting");
M5.Lcd.setCursor(5, 100);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.setTextSize(4);
M5.Lcd.println("No Config");
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(5, 130);
M5.Lcd.printf("Please go to Setting.");
}
}
void formLogin() {
//keyboard event
if(digitalRead(KEYBOARD_INT) == LOW) {
Wire.requestFrom(KEYBOARD_I2C_ADDR, 1); // request 1 byte from keyboard
while (Wire.available()) {
uint8_t key_val = Wire.read(); // receive a byte as character
if(key_val != 0) {
Serial.print("getting byte : ");
Serial.print(key_val);
if(key_val >= 0x20 && key_val < 0x7F) { // ASCII String
if (key_val=='=') {
Serial.println("call web service login");
wsLogin();
} else {
Serial.print(" KEY=");
Serial.print((char)key_val);
if (isDigit(char(key_val))==true) { //key pad 0-9
pin.concat(char(key_val));
Serial.print("PIN.=");
Serial.println(pin);
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
}
}
}
}
}
}
//display pin on LCD
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setTextSize(4);
int x = 160 - ((pin.length() * 20)/2);
M5.Lcd.setCursor(x, 115);
M5.Lcd.printf(pin.c_str());
//button event
if (M5.BtnA.wasReleased()) {
//go back to main page
M5.Lcd.fillScreen(WHITE);
firstRun=true;
pin="";
page="main";
} else if (M5.BtnB.wasReleased()) { //del 1 char form pin
if (pin.length()>0) {
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
pin.remove(pin.length()-1,1);
}
} else if (M5.BtnC.wasReleased()) { //connect ws to login
if (WiFi.status() == WL_CONNECTED) {
M5.Lcd.setCursor(5, 190);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.printf("Login....");
if (wsLogin()==true) {
M5.Lcd.setCursor(5, 190);
M5.Lcd.printf("PIN Corrected. ");
delay(500);
firstRun=true;
m5.update();
page="selectPI";
M5.Lcd.fillScreen(WHITE);
//pageSelectPI();
} else {
//M5.Lcd.setCursor(5, 190);
if (httpStatus==200) {
M5.Lcd.setCursor(5, 190);
M5.Lcd.printf("Wrong PIN ");
} else {
M5.Lcd.setCursor(5, 190);
M5.Lcd.printf("HTTP status = ");
M5.Lcd.print(httpStatus);
}
}
}
}
}
void pageSelectPI() {
if (WiFi.status() != WL_CONNECTED) {
firstRun=false;
page="main";
}
if (firstRun==true) {
lastTime=0;
//create header
M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("WIFI:OK");
M5.Lcd.setCursor(120, 2);
M5.Lcd.printf(wsGetDateTime().c_str());
//create display
M5.Lcd.fillRoundRect(40,30,270,40,10,BLUE);
M5.Lcd.fillRoundRect(40,75,270,40,10,BLUE);
M5.Lcd.fillRoundRect(40,120,270,40,10,BLUE);
M5.Lcd.fillRoundRect(40,165,270,40,10,BLUE);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(60, 40);
M5.Lcd.printf("ThaiQR & VIA");
M5.Lcd.setCursor(60, 85);
M5.Lcd.printf("LINE Pay");
M5.Lcd.setCursor(60, 130);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.printf("WeChat");
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(60, 175);
M5.Lcd.printf("view history");
//create menu
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(15, 225);
M5.Lcd.printf(".LOGOUT.");
M5.Lcd.setCursor(132, 225);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(".SEL.");
M5.Lcd.setCursor(227, 225);
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.printf(".ENT.");
updateSelectPI();
firstRun=false;
}
//button event
if (M5.BtnA.wasReleased()) { // logout to main page
//go back to main page
M5.Lcd.fillScreen(WHITE);
firstRun=true;
pin="";
selPI=0;
page="main";
} else if (M5.BtnB.wasReleased()) { // move selected PI
selPI=selPI+1;
if (selPI>3) selPI=0;
updateSelectPI();
} else if (M5.BtnC.wasReleased()) { // enter
if (selPI!=3) {
//goto input amount
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
page="amount";
} else {
//goto history
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
page="history";
}
}
}
void loadLast6order(fs::FS &fs, const char * dirname, uint8_t levels) {
unsigned long i=0;
String orderid;
Serial.printf("Listing last 6 orders : %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
i=i+1;
if (totalOrderFile-i < 6) {
orderid = file.name();
orderid = orderid.substring(7,21);
orderHistory[(i-totalOrderFile)*(-1)] = orderid;
Serial.print(" ORDERID: ");
Serial.print(orderid);
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
}
file = root.openNextFile();
}
for (int j=0; j<=5; j++) {
Serial.print(" history index : ");
Serial.print(j);
Serial.print(" ORDERID: ");
Serial.println(orderHistory[j]);
}
}
void viewHistory() {
if (firstRun==true) {
lastTime=0;
// list order file
//listDir(SD, "/", 0);
listDir(SD, "/order", 0);
loadLast6order(SD, "/order", 0);
//create header
M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("View Order History");
//create display history
M5.Lcd.setTextSize(3);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(45, 30);
M5.Lcd.printf(orderHistory[0].c_str());
M5.Lcd.setCursor(45, 60);
M5.Lcd.printf(orderHistory[1].c_str());
M5.Lcd.setCursor(45, 90);
M5.Lcd.printf(orderHistory[2].c_str());
M5.Lcd.setCursor(45, 120);
M5.Lcd.printf(orderHistory[3].c_str());
M5.Lcd.setCursor(45, 150);
M5.Lcd.printf(orderHistory[4].c_str());
M5.Lcd.setCursor(45, 180);
M5.Lcd.printf(orderHistory[5].c_str());
//create menu
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(30, 225);
M5.Lcd.printf(".BACK.");
M5.Lcd.setCursor(132, 225);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(".SEL.");
M5.Lcd.setCursor(227, 225);
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.printf(".ENT.");
firstRun=false;
updateSelectHistory();
}
//button event
if (M5.BtnA.wasReleased()) { // logout to main page
//go back to select pi
M5.Lcd.fillScreen(WHITE);
firstRun=true;
amount="";
selHis=0;
page="selectPI";
} else if (M5.BtnB.wasReleased()) { // move selected PI
selHis = selHis + 1;
if (selHis>5) selHis=0;
updateSelectHistory();
} else if (M5.BtnC.wasReleased()) { // enter
//goto order detail
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
page="orderdetail";
}
}
void updateSelectHistory() {
M5.Lcd.fillRect(0, 21, 45, 200, WHITE);
m5.update();
M5.Lcd.setTextSize(3);
if (selHis==0) {
M5.Lcd.setCursor(5, 30);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
if (selHis==1) {
M5.Lcd.setCursor(5, 60);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
if (selHis==2) {
M5.Lcd.setCursor(5, 90);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
if (selHis==3) {
M5.Lcd.setCursor(5, 120);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
if (selHis==4) {
M5.Lcd.setCursor(5, 150);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
if (selHis==5) {
M5.Lcd.setCursor(5, 180);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.printf(">>");
}
}
void orderDetail() {
if (firstRun==true) {
lastTime=0;
//load order detail
//create header
M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.print("Order ID : ");
M5.Lcd.print(orderHistory[selHis].c_str());
//create display history
String fileName = "/order/" + orderHistory[selHis] + ".txt";
String fileContent = readFile(SD,fileName.c_str());
M5.Lcd.setTextSize(3);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(20, 30);
M5.Lcd.print(fileContent);
//create menu
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(30, 225);
M5.Lcd.printf(".BACK.");
M5.Lcd.setCursor(115, 225);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(".REFUND.");
M5.Lcd.setCursor(227, 225);
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.printf(".CHK.");
firstRun=false;
}
//button event
if (M5.BtnA.wasReleased()) { // logout to main page
//go back to select pi
M5.Lcd.fillScreen(WHITE);
firstRun=true;
page="history";
} else if (M5.BtnB.wasReleased()) { // move selected PI
//selHis = selHis + 1;
//if (selHis>5) selHis=0;
//updateSelectHistory();
} else if (M5.BtnC.wasReleased()) { // enter
//goto order detail
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
page="orderdetail";
}
}
String readFile(fs::FS &fs, const char * path){
String detail;
char ch;
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return "";
}
Serial.print("Read from file: ");
while(file.available()){
ch = file.read();
detail = detail + ch;
Serial.write(file.read());
//Serial.write(detail.c_str());
}
Serial.println("Read from file: ");
Serial.print(detail);
return detail;
file.close();
}
void inputAmount() {
//m5.update();
if (WiFi.status() != WL_CONNECTED) {
firstRun=false;
pin="";
amount="";
page="main";
}
if (firstRun==true) {
lastTime=0;
//create header
M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("WIFI:OK");
M5.Lcd.setCursor(120, 2);
M5.Lcd.printf(wsGetDateTime().c_str());
//create display
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(4);
M5.Lcd.setCursor(10, 40);
M5.Lcd.printf("Enter Amount");
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
//create menu
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(30, 225);
M5.Lcd.printf(".BACK.");
M5.Lcd.setCursor(132, 225);
M5.Lcd.printf(".DEL.");
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(235, 225);
M5.Lcd.printf(".OK.");
firstRun=false;
}
//update current timestamp
/*
lastTime = millis();
if (lastTime > 60000) {
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setCursor(120, 2);
M5.Lcd.printf(wsGetDateTime().c_str());
lastTime=0;
}
*/
//keyboard event
if(digitalRead(KEYBOARD_INT) == LOW) {
Wire.requestFrom(KEYBOARD_I2C_ADDR, 1); // request 1 byte from keyboard
while (Wire.available()) {
uint8_t key_val = Wire.read(); // receive a byte as character
if(key_val != 0) {
Serial.print("getting byte : ");
Serial.print(key_val);
if(key_val >= 0x20 && key_val < 0x7F) { // ASCII String
if (key_val=='=') {
Serial.println("call web service QR");
//wsLogin();
} else {
Serial.print(" KEY=");
Serial.print((char)key_val);
if (isDigit(char(key_val))==true || key_val=='.') { //key pad 0-9
amount.concat(char(key_val));
Serial.print("Amount=");
Serial.println(amount);
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
}
}
}
}
}
}
//display amount
M5.Lcd.setTextColor(WHITE, BLUE);
M5.Lcd.setTextSize(4);
int x = 160 - ((amount.length() * 20)/2);
M5.Lcd.setCursor(x, 115);
M5.Lcd.printf(amount.c_str());
//button event
if (M5.BtnA.wasReleased()) { //back
//go back to main page
M5.Lcd.fillScreen(WHITE);
firstRun=true;
amount="";
page="selectPI";
} else if (M5.BtnB.wasReleased()) { //del 1 char form amount
if (amount.length()>0) {
M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
amount.remove(amount.length()-1,1);
} else {
amount="";
}
} else if (M5.BtnC.wasReleased()) { //create QR
if (WiFi.status() == WL_CONNECTED) {
M5.Lcd.setCursor(5, 190);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.printf("Checking amount...");
if (checkAmount()==true) {
M5.Lcd.setCursor(5, 190);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.printf("Creating QR Code...");
orderID=wsOrderID();
QRcode=wsQR();
if (QRcode!="fail") {
Serial.print("QR code = ");
Serial.println(QRcode);
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
page="QR";
} else {
M5.Lcd.setCursor(5, 190);
M5.Lcd.setTextColor(RED, WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.printf("Create QR Code Fail.");
}
} else {
M5.Lcd.setCursor(5, 190);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.printf("Amount not correct.");
}
}
}
}
void showQR() {
if (firstRun==true) {
lastTime=0;
//create QR code display
M5.Lcd.qrcode(QRcode,50,10,220,10);
//create header
String pi="";
if (selPI==0) pi="ThaiQR & VIA";
if (selPI==1) pi="Rabbit LINE Pay";
if (selPI==2) pi="Wechat";
//M5.Lcd.fillRect(0, 0, 320, 10, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("QR:");
M5.Lcd.setCursor(40, 3);
M5.Lcd.print(pi);
//create menu
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(30, 225);
M5.Lcd.printf(".BACK.");
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(215, 225);
M5.Lcd.printf(".QUERY.");
firstRun=false;
for (int i = 1; i <= 10; i++) { // loop auto check payment status for 1 min.
//buttom event
m5.update();
if (M5.BtnA.wasReleased()) { //back
//go back to main page
M5.Lcd.fillScreen(WHITE);
firstRun=true;
amount="";
page="selectPI";
} else if (M5.BtnC.wasReleased()) { //query payment status
if (chkPayment()==true) {
page="Success";
}
}
delay(3000);
if (chkPayment()==true) {
page="Success";
firstRun=true;
break;
}
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(290, 3);
M5.Lcd.print(i);
}
}
//buttom event
if (M5.BtnA.wasReleased()) { //back
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("Checking Payment... ");
String q=wsQuery();
String fileOrder = "/order/"+orderID+".txt";
writeFile(SD,fileOrder.c_str(),q.c_str());
//go back to main page
M5.Lcd.fillScreen(WHITE);
firstRun=true;
amount="";
page="selectPI";
} else if (M5.BtnC.wasReleased()) { //query payment status
if (chkPayment()==true) {
page="Success";
}
}
}
void successPage() {
if (firstRun==true) {
//create display
M5.Lcd.fillRoundRect(20,20,280,185,20,GREEN);
M5.Lcd.setTextSize(4);
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(50, 70);
M5.Lcd.printf(" SUCCESS");
M5.Lcd.setCursor(50, 120);
M5.Lcd.print(TXID);
//create menu
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, GREEN);
M5.Lcd.setCursor(235, 225);
M5.Lcd.printf(".OK.");
M5.Speaker.beep();
firstRun=false;
}
if (M5.BtnC.wasReleased()) { //back to select PI
M5.Lcd.fillScreen(WHITE);
firstRun=true;
amount="";
TXID="";
orderID="";
page="selectPI";
}
}
boolean chkPayment() {
if (WiFi.status() == WL_CONNECTED) {
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("Checking Payment... ");
String q=wsQuery();
if (q.substring(0,14)=="STATUS=SUCCESS") {
Serial.println("Payment SUCCESS");
//save payment status to sd card for history
String fileOrder = "/order/"+orderID+".txt";
writeFile(SD,fileOrder.c_str(),q.c_str());
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("Payment SUCCESS. ");
delay(1000);
int f=q.indexOf("TXID=");
TXID=q.substring(f+5);
Serial.print("TXID = ");
Serial.println(TXID);
M5.Lcd.fillScreen(WHITE);
firstRun=true;
m5.update();
//page="Success";
return true;
} else {
Serial.println("Payment PENDING");
//M5.Lcd.fillRect(0, 0, 320, 10, BLUE);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.setCursor(3, 3);
M5.Lcd.printf("Payment PENDING... ");
return false;
}
}
}
void updateSelectPI() {
m5.update();
M5.Lcd.setTextSize(3);
if (selPI==0) {
M5.Lcd.fillRoundRect(5,30,30,40,5,RED);
M5.Lcd.setCursor(10, 40);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(">");
} else {
M5.Lcd.fillRoundRect(5,30,30,40,5,WHITE);
M5.Lcd.setCursor(10, 40);
M5.Lcd.setTextColor(WHITE, WHITE);
M5.Lcd.printf(">");
}
if (selPI==1) {
M5.Lcd.fillRoundRect(5,75,30,40,5,RED);
M5.Lcd.setCursor(10, 85);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(">");
} else {
M5.Lcd.fillRoundRect(5,75,30,40,5,WHITE);
M5.Lcd.setCursor(10, 85);
M5.Lcd.setTextColor(WHITE, WHITE);
M5.Lcd.printf(">");
}
if (selPI==2) {
M5.Lcd.fillRoundRect(5,120,30,40,5,RED);
M5.Lcd.setCursor(10, 130);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(">");
} else {
M5.Lcd.fillRoundRect(5,120,30,40,5,WHITE);
M5.Lcd.setCursor(10, 130);
M5.Lcd.setTextColor(WHITE, WHITE);
M5.Lcd.printf(">");
}
if (selPI==3) {
M5.Lcd.fillRoundRect(5,165,30,40,5,RED);
M5.Lcd.setCursor(10, 175);
M5.Lcd.setTextColor(WHITE, RED);
M5.Lcd.printf(">");
} else {
M5.Lcd.fillRoundRect(5,165,30,40,5,WHITE);
M5.Lcd.setCursor(10, 175);
M5.Lcd.setTextColor(WHITE, WHITE);
M5.Lcd.printf(">");
}
}
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
}
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
unsigned long i=0;
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
i=i+1;
}
file = root.openNextFile();
}
totalOrderFile=i;
Serial.print(" Toltal Files : ");
Serial.println(i);
}
String wsQuery() {
String url = baseURL + app_key + "/inquery.asp?orderid=" + orderID;
Serial.print("URL = ");
Serial.println(url.c_str());
HTTPClient http;
http.begin(url); //HTTP
int httpCode = http.GET();
httpStatus = httpCode;
if(httpCode > 0) {
// HTTP header has been send and Server response header has been handled
//USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
Serial.print("HTTP status = ");
Serial.println(httpCode);
if(httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.print("WS return = ");
Serial.println(payload);
return payload;
} else {
return "fail";
}
} else {
//Serial.print("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
return "fail";
}
http.end();
}
String wsOrderID() {
String url = baseURL + app_key + "/getOrderID.asp";
Serial.print("URL = ");
Serial.println(url.c_str());
HTTPClient http;
http.begin(url); //HTTP
int httpCode = http.GET();
httpStatus = httpCode;
if(httpCode > 0) {
// HTTP header has been send and Server response header has been handled
//USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
Serial.print("HTTP status = ");
Serial.println(httpCode);
if(httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.print("OrderID = ");
Serial.println(payload);
return payload;
}
} else {
return "--";
}
...
This file has been truncated, please download it to see its full contents.
/*
Parsing.cpp - HTTP request parsing.
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"
//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif
static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
{
char *buf = nullptr;
dataLength = 0;
while (dataLength < maxLength) {
int tries = timeout_ms;
size_t newLength;
while (!(newLength = client.available()) && tries--) delay(1);
if (!newLength) {
break;
}
if (!buf) {
buf = (char *) malloc(newLength + 1);
if (!buf) {
return nullptr;
}
}
else {
char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
if (!newBuf) {
free(buf);
return nullptr;
}
buf = newBuf;
}
client.readBytes(buf + dataLength, newLength);
dataLength += newLength;
buf[dataLength] = '\0';
}
return buf;
}
bool WebServer::_parseRequest(WiFiClient& client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
//reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value =String();
}
// First line of HTTP request looks like "GET /path HTTP/1.1"
// Retrieve the "/path" part by finding the spaces
int addr_start = req.indexOf(' ');
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Invalid request: ");
DEBUG_OUTPUT.println(req);
#endif
return false;
}
String methodStr = req.substring(0, addr_start);
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
int hasSearch = url.indexOf('?');
if (hasSearch != -1){
searchStr = urlDecode(url.substring(hasSearch + 1));
url = url.substring(0, hasSearch);
}
_currentUri = url;
_chunked = false;
HTTPMethod method = HTTP_GET;
if (methodStr == "POST") {
method = HTTP_POST;
} else if (methodStr == "DELETE") {
method = HTTP_DELETE;
} else if (methodStr == "OPTIONS") {
method = HTTP_OPTIONS;
} else if (methodStr == "PUT") {
method = HTTP_PUT;
} else if (methodStr == "PATCH") {
method = HTTP_PATCH;
}
_currentMethod = method;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("method: ");
DEBUG_OUTPUT.print(methodStr);
DEBUG_OUTPUT.print(" url: ");
DEBUG_OUTPUT.print(url);
DEBUG_OUTPUT.print(" search: ");
DEBUG_OUTPUT.println(searchStr);
#endif
//attach handler
RequestHandler* handler;
for (handler = _firstHandler; handler; handler = handler->next()) {
if (handler->canHandle(_currentMethod, _currentUri))
break;
}
_currentHandler = handler;
String formData;
// below is needed only when POST type request
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
String boundaryStr;
String headerName;
String headerValue;
bool isForm = false;
bool isEncoded = false;
uint32_t contentLength = 0;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(),headerValue.c_str());
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("headerName: ");
DEBUG_OUTPUT.println(headerName);
DEBUG_OUTPUT.print("headerValue: ");
DEBUG_OUTPUT.println(headerValue);
#endif
if (headerName.equalsIgnoreCase("Content-Type")){
if (headerValue.startsWith("text/plain")){
isForm = false;
} else if (headerValue.startsWith("application/x-www-form-urlencoded")){
isForm = false;
isEncoded = true;
} else if (headerValue.startsWith("multipart/")){
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
isForm = true;
}
} else if (headerName.equalsIgnoreCase("Content-Length")){
contentLength = headerValue.toInt();
} else if (headerName.equalsIgnoreCase("Host")){
_hostHeader = headerValue;
}
}
if (!isForm){
size_t plainLength;
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
}
if (contentLength > 0) {
if (searchStr != "") searchStr += '&';
if(isEncoded){
//url encoded form
String decoded = urlDecode(plainBuf);
size_t decodedLen = decoded.length();
memcpy(plainBuf, decoded.c_str(), decodedLen);
plainBuf[decodedLen] = 0;
searchStr += plainBuf;
}
_parseArguments(searchStr);
if(!isEncoded){
//plain post json or other data
RequestArgument& arg = _currentArgs[_currentArgCount++];
arg.key = "plain";
arg.value = String(plainBuf);
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Plain: ");
DEBUG_OUTPUT.println(plainBuf);
#endif
free(plainBuf);
}
}
if (isForm){
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
}
}
} else {
String headerName;
String headerValue;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(),headerValue.c_str());
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("headerName: ");
DEBUG_OUTPUT.println(headerName);
DEBUG_OUTPUT.print("headerValue: ");
DEBUG_OUTPUT.println(headerValue);
#endif
if (headerName.equalsIgnoreCase("Host")){
_hostHeader = headerValue;
}
}
_parseArguments(searchStr);
}
client.flush();
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Request: ");
DEBUG_OUTPUT.println(url);
DEBUG_OUTPUT.print(" Arguments: ");
DEBUG_OUTPUT.println(searchStr);
#endif
return true;
}
bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
for (int i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value=headerValue;
return true;
}
}
return false;
}
void WebServer::_parseArguments(String data) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args: ");
DEBUG_OUTPUT.println(data);
#endif
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
_currentArgs = new RequestArgument[1];
return;
}
_currentArgCount = 1;
for (int i = 0; i < (int)data.length(); ) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
_currentArgs = new RequestArgument[_currentArgCount+1];
int pos = 0;
int iarg;
for (iarg = 0; iarg < _currentArgCount;) {
int equal_sign_index = data.indexOf('=', pos);
int next_arg_index = data.indexOf('&', pos);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("pos ");
DEBUG_OUTPUT.print(pos);
DEBUG_OUTPUT.print("=@ ");
DEBUG_OUTPUT.print(equal_sign_index);
DEBUG_OUTPUT.print(" &@ ");
DEBUG_OUTPUT.println(next_arg_index);
#endif
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("arg missing value: ");
DEBUG_OUTPUT.println(iarg);
#endif
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
continue;
}
RequestArgument& arg = _currentArgs[iarg];
arg.key = data.substring(pos, equal_sign_index);
arg.value = data.substring(equal_sign_index + 1, next_arg_index);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("arg ");
DEBUG_OUTPUT.print(iarg);
DEBUG_OUTPUT.print(" key: ");
DEBUG_OUTPUT.print(arg.key);
DEBUG_OUTPUT.print(" value: ");
DEBUG_OUTPUT.println(arg.value);
#endif
++iarg;
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
}
_currentArgCount = iarg;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
}
void WebServer::_uploadWriteByte(uint8_t b){
if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
}
_currentUpload.buf[_currentUpload.currentSize++] = b;
}
uint8_t WebServer::_uploadReadByte(WiFiClient& client){
int res = client.read();
if(res == -1){
while(!client.available() && client.connected())
yield();
res = client.read();
}
return (uint8_t)res;
}
bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
(void) len;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
DEBUG_OUTPUT.print(boundary);
DEBUG_OUTPUT.print(" Length: ");
DEBUG_OUTPUT.println(len);
#endif
String line;
int retry = 0;
do {
line = client.readStringUntil('\r');
++retry;
} while (line.length() == 0 && retry < 3);
client.readStringUntil('\n');
//start reading the form
if (line == ("--"+boundary)){
RequestArgument* postArgs = new RequestArgument[32];
int postArgsLen = 0;
while(1){
String argName;
String argValue;
String argType;
String argFilename;
bool argIsFile = false;
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
int nameStart = line.indexOf('=');
if (nameStart != -1){
argName = line.substring(nameStart+2);
nameStart = argName.indexOf('=');
if (nameStart == -1){
argName = argName.substring(0, argName.length() - 1);
} else {
argFilename = argName.substring(nameStart+2, argName.length() - 1);
argName = argName.substring(0, argName.indexOf('"'));
argIsFile = true;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg FileName: ");
DEBUG_OUTPUT.println(argFilename);
#endif
//use GET to set the filename if uploading using blob
if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Name: ");
DEBUG_OUTPUT.println(argName);
#endif
argType = "text/plain";
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
argType = line.substring(line.indexOf(':')+2);
//skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Type: ");
DEBUG_OUTPUT.println(argType);
#endif
if (!argIsFile){
while(1){
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--"+boundary)) break;
if (argValue.length() > 0) argValue += "\n";
argValue += line;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Value: ");
DEBUG_OUTPUT.println(argValue);
DEBUG_OUTPUT.println();
#endif
RequestArgument& arg = postArgs[postArgsLen++];
arg.key = argName;
arg.value = argValue;
if (line == ("--"+boundary+"--")){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
} else {
_currentUpload.status = UPLOAD_FILE_START;
_currentUpload.name = argName;
_currentUpload.filename = argFilename;
_currentUpload.type = argType;
_currentUpload.totalSize = 0;
_currentUpload.currentSize = 0;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Start File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.println(_currentUpload.type);
#endif
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.status = UPLOAD_FILE_WRITE;
uint8_t argByte = _uploadReadByte(client);
readfile:
while(argByte != 0x0D){
if (!client.connected()) return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if (argByte == 0x0A){
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if ((char)argByte != '-'){
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if ((char)argByte != '-'){
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
client.readBytes(endBuf, boundary.length());
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.status = UPLOAD_FILE_END;
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("End File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.print(_currentUpload.type);
DEBUG_OUTPUT.print(" Size: ");
DEBUG_OUTPUT.println(_currentUpload.totalSize);
#endif
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if (line == "--"){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t i = 0;
while(i < boundary.length()){
_uploadWriteByte(endBuf[i++]);
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
}
}
}
}
int iarg;
int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++){
RequestArgument& arg = postArgs[postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
if (_currentArgs) delete[] _currentArgs;
_currentArgs = new RequestArgument[postArgsLen];
for (iarg = 0; iarg < postArgsLen; iarg++){
RequestArgument& arg = _currentArgs[iarg];
arg.key = postArgs[iarg].key;
arg.value = postArgs[iarg].value;
}
_currentArgCount = iarg;
if (postArgs) delete[] postArgs;
return true;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Error: line: ");
DEBUG_OUTPUT.println(line);
#endif
return false;
}
String WebServer::urlDecode(const String& text)
{
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
while (i < len)
{
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len))
{
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
}
else {
if (encodedChar == '+')
{
decodedChar = ' ';
}
else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
}
return decoded;
}
bool WebServer::_parseFormUploadAborted(){
_currentUpload.status = UPLOAD_FILE_ABORTED;
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
return false;
}
/*
WebServer.cpp - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include <libb64/cencode.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"
#include "FS.h"
#include "detail/RequestHandlersImpl.h"
//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif
const char * AUTHORIZATION_HEADER = "Authorization";
WebServer::WebServer(IPAddress addr, int port)
: _server(addr, port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}
WebServer::WebServer(int port)
: _server(port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}
WebServer::~WebServer() {
if (_currentHeaders)
delete[]_currentHeaders;
_headerKeysCount = 0;
RequestHandler* handler = _firstHandler;
while (handler) {
RequestHandler* next = handler->next();
delete handler;
handler = next;
}
close();
}
void WebServer::begin() {
_currentStatus = HC_NONE;
_server.begin();
if(!_headerKeysCount)
collectHeaders(0, 0);
}
bool WebServer::authenticate(const char * username, const char * password){
if(hasHeader(AUTHORIZATION_HEADER)){
String authReq = header(AUTHORIZATION_HEADER);
if(authReq.startsWith("Basic")){
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username)+strlen(password)+1;
char *toencode = new char[toencodeLen + 1];
if(toencode == NULL){
authReq = String();
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
if(encoded == NULL){
authReq = String();
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
authReq = String();
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
}
authReq = String();
}
return false;
}
void WebServer::requestAuthentication(){
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
send(401);
}
void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void WebServer::addHandler(RequestHandler* handler) {
_addRequestHandler(handler);
}
void WebServer::_addRequestHandler(RequestHandler* handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
}
else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {
WiFiClient client = _server.available();
if (!client) {
return;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("New client");
#endif
_currentClient = client;
_currentStatus = HC_WAIT_READ;
_statusChange = millis();
}
if (!_currentClient.connected()) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
}
// Wait for data from client to become available
if (_currentStatus == HC_WAIT_READ) {
if (!_currentClient.available()) {
if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
}
yield();
return;
}
if (!_parseRequest(_currentClient)) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
}
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
if (!_currentClient.connected()) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
} else {
_currentStatus = HC_WAIT_CLOSE;
_statusChange = millis();
return;
}
}
if (_currentStatus == HC_WAIT_CLOSE) {
if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
} else {
yield();
return;
}
}
}
void WebServer::close() {
_server.end();
}
void WebServer::stop() {
close();
}
void WebServer::sendHeader(const String& name, const String& value, bool first) {
String headerLine = name;
headerLine += ": ";
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
}
else {
_responseHeaders += headerLine;
}
}
void WebServer::setContentLength(size_t contentLength) {
_contentLength = contentLength;
}
void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
response = "HTTP/1."+String(_currentVersion)+" ";
response += String(code);
response += " ";
response += _responseCodeToString(code);
response += "\r\n";
if (!content_type)
content_type = "text/html";
sendHeader("Content-Type", content_type, true);
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
sendHeader("Content-Length", String(contentLength));
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
sendHeader("Content-Length", String(_contentLength));
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
//let's do chunked
_chunked = true;
sendHeader("Accept-Ranges","none");
sendHeader("Transfer-Encoding","chunked");
}
sendHeader("Connection", "close");
response += _responseHeaders;
response += "\r\n";
_responseHeaders = String();
}
void WebServer::send(int code, const char* content_type, const String& content) {
String header;
// Can we asume the following?
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClient.write(header.c_str(), header.length());
if(content.length())
sendContent(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
_currentClient.write(header.c_str(), header.length());
sendContent_P(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void WebServer::send(int code, char* content_type, const String& content) {
send(code, (const char*)content_type, content);
}
void WebServer::send(int code, const String& content_type, const String& content) {
send(code, (const char*)content_type.c_str(), content);
}
void WebServer::sendContent(const String& content) {
const char * footer = "\r\n";
size_t len = content.length();
if(_chunked) {
char * chunkSize = (char *)malloc(11);
if(chunkSize){
sprintf(chunkSize, "%x%s", len, footer);
_currentClient.write(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClient.write(content.c_str(), len);
if(_chunked){
_currentClient.write(footer, 2);
}
}
void WebServer::sendContent_P(PGM_P content) {
sendContent_P(content, strlen_P(content));
}
void WebServer::sendContent_P(PGM_P content, size_t size) {
const char * footer = "\r\n";
if(_chunked) {
char * chunkSize = (char *)malloc(11);
if(chunkSize){
sprintf(chunkSize, "%x%s", size, footer);
_currentClient.write(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClient.write(content, size);
if(_chunked){
_currentClient.write(footer, 2);
}
}
String WebServer::arg(String name) {
for (int i = 0; i < _currentArgCount; ++i) {
if ( _currentArgs[i].key == name )
return _currentArgs[i].value;
}
return String();
}
String WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return String();
}
String WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return String();
}
int WebServer::args() {
return _currentArgCount;
}
bool WebServer::hasArg(String name) {
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String WebServer::header(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
}
return String();
}
void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 1;
if (_currentHeaders)
delete[]_currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders[0].key = AUTHORIZATION_HEADER;
for (int i = 1; i < _headerKeysCount; i++){
_currentHeaders[i].key = headerKeys[i-1];
}
}
String WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return String();
}
String WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return String();
}
int WebServer::headers() {
return _headerKeysCount;
}
bool WebServer::hasHeader(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String WebServer::hostHeader() {
return _hostHeader;
}
void WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("request handler not found");
#endif
}
else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
#ifdef DEBUG_ESP_HTTP_SERVER
if (!handled) {
DEBUG_OUTPUT.println("request handler failed to handle request");
}
#endif
}
if (!handled) {
if(_notFoundHandler) {
_notFoundHandler();
}
else {
send(404, "text/plain", String("Not found: ") + _currentUri);
}
}
_currentUri = String();
}
String WebServer::_responseCodeToString(int code) {
switch (code) {
case 100: return F("Continue");
case 101: return F("Switching Protocols");
case 200: return F("OK");
case 201: return F("Created");
case 202: return F("Accepted");
case 203: return F("Non-Authoritative Information");
case 204: return F("No Content");
case 205: return F("Reset Content");
case 206: return F("Partial Content");
case 300: return F("Multiple Choices");
case 301: return F("Moved Permanently");
case 302: return F("Found");
case 303: return F("See Other");
case 304: return F("Not Modified");
case 305: return F("Use Proxy");
case 307: return F("Temporary Redirect");
case 400: return F("Bad Request");
case 401: return F("Unauthorized");
case 402: return F("Payment Required");
case 403: return F("Forbidden");
case 404: return F("Not Found");
case 405: return F("Method Not Allowed");
case 406: return F("Not Acceptable");
case 407: return F("Proxy Authentication Required");
case 408: return F("Request Time-out");
case 409: return F("Conflict");
case 410: return F("Gone");
case 411: return F("Length Required");
case 412: return F("Precondition Failed");
case 413: return F("Request Entity Too Large");
case 414: return F("Request-URI Too Large");
case 415: return F("Unsupported Media Type");
case 416: return F("Requested range not satisfiable");
case 417: return F("Expectation Failed");
case 500: return F("Internal Server Error");
case 501: return F("Not Implemented");
case 502: return F("Bad Gateway");
case 503: return F("Service Unavailable");
case 504: return F("Gateway Time-out");
case 505: return F("HTTP Version not supported");
default: return "";
}
}
/*
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#ifndef WEBSERVER_H
#define WEBSERVER_H
#include <functional>
#include <WiFi.h>
#include <Update.h>
#include <WiFiUdp.h>
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
#define HTTP_UPLOAD_BUFLEN 2048
#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
class WebServer;
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // file size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
#include "detail/RequestHandler.h"
namespace fs {
class FS;
}
class WebServer
{
public:
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
~WebServer();
void begin();
void handleClient();
void close();
void stop();
bool authenticate(const char * username, const char * password);
void requestAuthentication();
typedef std::function<void(void)> THandlerFunction;
void on(const String &uri, THandlerFunction handler);
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void addHandler(RequestHandler* handler);
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
void onNotFound(THandlerFunction fn); //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads
String uri() { return _currentUri; }
HTTPMethod method() { return _currentMethod; }
WiFiClient client() { return _currentClient; }
HTTPUpload& upload() { return _currentUpload; }
String arg(String name); // get request argument value by name
String arg(int i); // get request argument value by number
String argName(int i); // get request argument name by number
int args(); // get arguments count
bool hasArg(String name); // check if argument exists
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
String header(String name); // get request header value by name
String header(int i); // get request header value by number
String headerName(int i); // get request header name by number
int headers(); // get header count
bool hasHeader(String name); // check if header exists
String hostHeader(); // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char* content_type = NULL, const String& content = String(""));
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void setContentLength(size_t contentLength);
void sendHeader(const String& name, const String& value, bool first = false);
void sendContent(const String& content);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
static String urlDecode(const String& text);
template<typename T> size_t streamFile(T &file, const String& contentType){
setContentLength(file.size());
if (String(file.name()).endsWith(".gz") &&
contentType != "application/x-gzip" &&
contentType != "application/octet-stream"){
sendHeader("Content-Encoding", "gzip");
}
send(200, contentType, "");
return _currentClient.write(file);
}
protected:
void _addRequestHandler(RequestHandler* handler);
void _handleRequest();
bool _parseRequest(WiFiClient& client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
uint8_t _uploadReadByte(WiFiClient& client);
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
bool _collectHeader(const char* headerName, const char* headerValue);
struct RequestArgument {
String key;
String value;
};
WiFiServer _server;
WiFiClient _currentClient;
HTTPMethod _currentMethod;
String _currentUri;
uint8_t _currentVersion;
HTTPClientStatus _currentStatus;
unsigned long _statusChange;
RequestHandler* _currentHandler;
RequestHandler* _firstHandler;
RequestHandler* _lastHandler;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount;
RequestArgument* _currentArgs;
HTTPUpload _currentUpload;
int _headerKeysCount;
RequestArgument* _currentHeaders;
size_t _contentLength;
String _responseHeaders;
String _hostHeader;
bool _chunked;
};
#endif //WebServer_H
Comments