Add files via upload
This commit is contained in:
13
proxy/Dockerfile
Normal file
13
proxy/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM python:3.11-slim-bookworm
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
EXPOSE 5009
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD [ "python", "app.py" ]
|
||||||
57
proxy/app.py
Normal file
57
proxy/app.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from mock_data import get_mock_data
|
||||||
|
|
||||||
|
# тестовый режим с возможностью заказа питсы с гравием(!)
|
||||||
|
MOCK = False
|
||||||
|
|
||||||
|
API_URL = 'https://api.papajohns.ru'
|
||||||
|
|
||||||
|
|
||||||
|
class PapaJohnsProxy(object):
|
||||||
|
@cherrypy.expose
|
||||||
|
def default(self, *args, **kwargs):
|
||||||
|
path = cherrypy.request.path_info
|
||||||
|
method = cherrypy.request.method
|
||||||
|
headers = dict(cherrypy.request.headers)
|
||||||
|
query_string = cherrypy.request.query_string
|
||||||
|
data = cherrypy.request.body.read() if cherrypy.request.body.length else None
|
||||||
|
|
||||||
|
if MOCK:
|
||||||
|
time.sleep(1)
|
||||||
|
return json.dumps(get_mock_data(path))
|
||||||
|
|
||||||
|
url = f'{API_URL}{path}'
|
||||||
|
|
||||||
|
if query_string:
|
||||||
|
url += f'?{query_string}'
|
||||||
|
|
||||||
|
headers_to_forward = {
|
||||||
|
k: v for k, v in headers.items()
|
||||||
|
if k.lower() not in ['host', 'content-length']
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.request(
|
||||||
|
method=method,
|
||||||
|
url=url,
|
||||||
|
headers=headers_to_forward,
|
||||||
|
data=data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
cherrypy.response.status = response.status_code
|
||||||
|
return response.text
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
cherrypy.response.status = 500
|
||||||
|
return json.dumps({"error": str(e)})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': 5009})
|
||||||
|
cherrypy.quickstart(PapaJohnsProxy())
|
||||||
7
proxy/docker-compose.yml
Normal file
7
proxy/docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
pizza-proxy:
|
||||||
|
build: .
|
||||||
|
container_name: pizza-proxy
|
||||||
|
ports:
|
||||||
|
- 5009:5009
|
||||||
|
restart: unless-stopped
|
||||||
222
proxy/mock_data.py
Normal file
222
proxy/mock_data.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
def get_mock_data(path: str) -> dict:
|
||||||
|
if '/order/save' in path:
|
||||||
|
return ORDER_SUCCESS
|
||||||
|
return ORDER_FAIL
|
||||||
|
if '/order/status' in path:
|
||||||
|
if ORDER_STATUS["order_status"] < 5:
|
||||||
|
ORDER_STATUS["order_status"] += 1
|
||||||
|
return ORDER_STATUS
|
||||||
|
if '/cart/add' in path:
|
||||||
|
ORDER_STATUS["order_status"] = -1
|
||||||
|
return CART_ADD
|
||||||
|
if '/catalog/category-goods' in path:
|
||||||
|
return PIZZAS
|
||||||
|
|
||||||
|
|
||||||
|
CART_ADD = {
|
||||||
|
"unauthorized_token": "7904e2634334760a642a169c0f7c67f0",
|
||||||
|
"cart_id": 123456789,
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"name": "С ананасами"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ORDER_FAIL = {
|
||||||
|
"success": False,
|
||||||
|
"message": {
|
||||||
|
"composition": [
|
||||||
|
"Минимальная стоимость заказа на доставку 99999 ₽"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": 400
|
||||||
|
}
|
||||||
|
|
||||||
|
ORDER_SUCCESS = {
|
||||||
|
"order_id": 69420
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ORDER_STATUS = {
|
||||||
|
"order_status": -1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PIZZAS = [
|
||||||
|
{
|
||||||
|
"goods": [
|
||||||
|
{
|
||||||
|
"id": 1234,
|
||||||
|
"name": "С ананасами",
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"id": 123430,
|
||||||
|
"price": 599,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 23
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123431,
|
||||||
|
"price": 879,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123432,
|
||||||
|
"price": 1079,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123433,
|
||||||
|
"price": 1379,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123410,
|
||||||
|
"price": 879,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123411,
|
||||||
|
"price": 1079,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123412,
|
||||||
|
"price": 1379,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"good_type": "promotional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4321,
|
||||||
|
"name": "С гравием",
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"id": 223430,
|
||||||
|
"price": 429,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 23
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223431,
|
||||||
|
"price": 429,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223432,
|
||||||
|
"price": 4279,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223433,
|
||||||
|
"price": 4279,
|
||||||
|
"kind": {
|
||||||
|
"id": 3
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223410,
|
||||||
|
"price": 429,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223411,
|
||||||
|
"price": 4279,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 223412,
|
||||||
|
"price": 4279,
|
||||||
|
"kind": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"stuffed_crust": "none",
|
||||||
|
"size": {
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"good_type": "promotional"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
2
proxy/requirements.txt
Normal file
2
proxy/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CherryPy==18.9.0
|
||||||
|
requests==2.31.0
|
||||||
6
scripts/config_copy.py
Normal file
6
scripts/config_copy.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Import('env')
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
if not os.path.isfile("include/config.h"):
|
||||||
|
shutil.copy("config.example", "include/config.h")
|
||||||
158
src/main.cpp
Normal file
158
src/main.cpp
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#include "parser.h"
|
||||||
|
#include "requests.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "menu.h"
|
||||||
|
|
||||||
|
|
||||||
|
ArudinoStreamParser parser;
|
||||||
|
PizzaHandler handler;
|
||||||
|
|
||||||
|
Request r;
|
||||||
|
uint32_t order_id;
|
||||||
|
uint32_t trackLastUpdate;
|
||||||
|
char unauthorized_token[33];
|
||||||
|
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
WiFi.disconnect();
|
||||||
|
delay(100);
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
Menu &menu = Menu::getInstance();
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
menu.tick();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.setMenuPage(Menu::PRELOAD);
|
||||||
|
parser.setHandler(&handler);
|
||||||
|
char path[70];
|
||||||
|
sprintf(path, "/catalog/category-goods?category=pizza&page_size=100&city_id=%d", CITY_ID);
|
||||||
|
r.request("GET", path, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addToCart(uint32_t pizzaId) {
|
||||||
|
JsonDocument add_pizza;
|
||||||
|
|
||||||
|
JsonObject composition = add_pizza["composition"].add<JsonObject>();
|
||||||
|
composition["good_id"] = pizzaId;
|
||||||
|
composition["type"] = "good";
|
||||||
|
add_pizza["city_id"] = CITY_ID;
|
||||||
|
|
||||||
|
char payload[350];
|
||||||
|
|
||||||
|
serializeJson(add_pizza, payload);
|
||||||
|
char* responce = r.request("POST", "/cart/add", payload);
|
||||||
|
|
||||||
|
|
||||||
|
JsonDocument pizza_add_responce;
|
||||||
|
|
||||||
|
DeserializationError error = deserializeJson(pizza_add_responce, responce);
|
||||||
|
free(responce);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pizza_add_responce.clear();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(unauthorized_token, pizza_add_responce["unauthorized_token"]);
|
||||||
|
|
||||||
|
pizza_add_responce.clear();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool placeOrder() {
|
||||||
|
JsonDocument order;
|
||||||
|
|
||||||
|
|
||||||
|
JsonObject user_data = order["user_data"].to<JsonObject>();
|
||||||
|
user_data["username"] = ORDER_NAME;
|
||||||
|
user_data["phone"] = ORDER_PHONE;
|
||||||
|
user_data["email"] = ORDER_EMAIL;
|
||||||
|
user_data["subscription_state"] = false;
|
||||||
|
user_data["sms_state"] = false;
|
||||||
|
|
||||||
|
JsonObject address_coordinates = order["address_coordinates"]["coordinates"].to<JsonObject>();
|
||||||
|
address_coordinates["latitude"] = ORDER_GPS_LAT;
|
||||||
|
address_coordinates["longitude"] = ORDER_GPS_LON;
|
||||||
|
|
||||||
|
order["unauthorized_token"] = unauthorized_token;
|
||||||
|
order["pay_type"] = "card_to_courier";
|
||||||
|
order["city_id"] = CITY_ID;
|
||||||
|
#ifdef ORDER_RESTAURANT_ID
|
||||||
|
order["restaurant_id"] = ORDER_RESTAURANT_ID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
char payload[350];
|
||||||
|
|
||||||
|
serializeJson(order, payload);
|
||||||
|
|
||||||
|
char* responce = r.request("POST", "/order/save", payload);
|
||||||
|
JsonDocument order_place_responce;
|
||||||
|
DeserializationError error = deserializeJson(order_place_responce, responce);
|
||||||
|
free(responce);
|
||||||
|
if (error) {
|
||||||
|
order_place_responce.clear();
|
||||||
|
return 1;
|
||||||
|
} else if (order_place_responce["success"] == false) {
|
||||||
|
order_place_responce.clear();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
order_id = order_place_responce["order_id"];
|
||||||
|
order_place_responce.clear();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::TrackingStatus getStatus() {
|
||||||
|
char path[100];
|
||||||
|
sprintf(path, "/order/status?city_id=%d&unauthorized_token=%s&order_id=%d", CITY_ID, unauthorized_token, order_id);
|
||||||
|
char* responce = r.request("GET", path);
|
||||||
|
JsonDocument orderStatus;
|
||||||
|
|
||||||
|
DeserializationError error = deserializeJson(orderStatus, responce);
|
||||||
|
free(responce);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return Menu::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::TrackingStatus status = static_cast<Menu::TrackingStatus>(orderStatus["order_status"]);
|
||||||
|
orderStatus.clear();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Menu &menu = Menu::getInstance();
|
||||||
|
menu.tick();
|
||||||
|
|
||||||
|
if (menu.getMenuPage() == Menu::PLACING_ORDER) {
|
||||||
|
// питса выбрана, можно заказывать
|
||||||
|
uint32_t selectedPizzaId = menu.getSelectedPizzaId();
|
||||||
|
|
||||||
|
bool error = addToCart(selectedPizzaId);
|
||||||
|
if (error) {
|
||||||
|
menu.setMenuPage(Menu::PIZZA_SELECT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = placeOrder();
|
||||||
|
if (error) {
|
||||||
|
menu.setMenuPage(Menu::PIZZA_SELECT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
menu.setMenuPage(Menu::TRACKING);
|
||||||
|
|
||||||
|
} else if (menu.getMenuPage() == Menu::TRACKING) {
|
||||||
|
if (millis() - trackLastUpdate > TRACK_UPDATE_INTERVAL) {
|
||||||
|
Menu::TrackingStatus status = getStatus();
|
||||||
|
if (status != Menu::UNKNOWN) {
|
||||||
|
menu.setTrackingStatus(status);
|
||||||
|
}
|
||||||
|
trackLastUpdate = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
349
src/menu.cpp
Normal file
349
src/menu.cpp
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
#include "menu.h"
|
||||||
|
|
||||||
|
|
||||||
|
Menu::Menu() : lcd(0x3F, 16, 2), enc(ENCPIN1, ENCPIN2, ENCBTNPIN) {
|
||||||
|
Wire.begin(4, 5);
|
||||||
|
lcd.init();
|
||||||
|
lcd.backlight();
|
||||||
|
pinMode(BRBPIN, INPUT_PULLUP);
|
||||||
|
pinMode(LEDPIN, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Menu::setMenuPage(Page page) {
|
||||||
|
switch (page) {
|
||||||
|
case PRELOAD:
|
||||||
|
case PIZZA_SELECT:
|
||||||
|
lcd.cursor_off();
|
||||||
|
menuPage = page;
|
||||||
|
break;
|
||||||
|
case DOUGH_SELECT:
|
||||||
|
if (menuPage != PIZZA_SELECT && menuPage != SIZE_SELECT) return;
|
||||||
|
selectedDough = Pizzas::TRADITIONAL;
|
||||||
|
pizzaSizesIndex = 0;
|
||||||
|
menuPage = page;
|
||||||
|
initVariations();
|
||||||
|
break;
|
||||||
|
case SIZE_SELECT:
|
||||||
|
if (menuPage != DOUGH_SELECT) return;
|
||||||
|
menuPage = page;
|
||||||
|
break;
|
||||||
|
case CONFIRMATION:
|
||||||
|
if (menuPage != SIZE_SELECT) return;
|
||||||
|
menuPage = page;
|
||||||
|
break;
|
||||||
|
case PLACING_ORDER: {
|
||||||
|
if (menuPage != CONFIRMATION) return;
|
||||||
|
lcd.cursor_off();
|
||||||
|
Pizzas::Variation var = pizzaSizes[pizzaSizesIndex];
|
||||||
|
selectedPizzaId = var.id;
|
||||||
|
menuPage = page;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TRACKING:
|
||||||
|
if (menuPage != PLACING_ORDER) return;
|
||||||
|
menuPage = page;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
void Menu::setTrackingStatus(TrackingStatus new_status) {
|
||||||
|
if (trackingStatus == new_status) return;
|
||||||
|
trackingStatus = new_status;
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::updateIndex() {
|
||||||
|
if (menuPage != PIZZA_SELECT) return;
|
||||||
|
drawIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::initVariations() {
|
||||||
|
Pizzas::Pizza pizza = pizzas.get(curentPizzaIndex);
|
||||||
|
for (int i = 0; i < 4; i++) pizzaSizes[i] = {};
|
||||||
|
pizzaSizesIndex = 0;
|
||||||
|
uint8_t sizesCount;
|
||||||
|
for (int i=0; i<pizza.variations_count; i++) {
|
||||||
|
if (pizza.variations[i].dough != selectedDough) continue;
|
||||||
|
pizzaSizes[sizesCount] = pizza.variations[i];
|
||||||
|
sizesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::tick() {
|
||||||
|
animation();
|
||||||
|
enc.tick();
|
||||||
|
|
||||||
|
if (enc.click()) {
|
||||||
|
switch (menuPage) {
|
||||||
|
case PIZZA_SELECT:
|
||||||
|
setMenuPage(DOUGH_SELECT);
|
||||||
|
break;
|
||||||
|
case DOUGH_SELECT:
|
||||||
|
setMenuPage(SIZE_SELECT);
|
||||||
|
break;
|
||||||
|
case SIZE_SELECT:
|
||||||
|
setMenuPage(CONFIRMATION);
|
||||||
|
break;
|
||||||
|
case CONFIRMATION:
|
||||||
|
setMenuPage(PIZZA_SELECT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enc.hold()) {
|
||||||
|
switch(menuPage) {
|
||||||
|
case DOUGH_SELECT:
|
||||||
|
case SIZE_SELECT:
|
||||||
|
case CONFIRMATION:
|
||||||
|
setMenuPage(PIZZA_SELECT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (enc.turn()) {
|
||||||
|
scroll(enc.dir());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!digitalRead(BRBPIN) && !brbflag) {
|
||||||
|
if (menuPage == SIZE_SELECT) {
|
||||||
|
brbflag = true;
|
||||||
|
setMenuPage(CONFIRMATION);
|
||||||
|
} else if (menuPage == CONFIRMATION) {
|
||||||
|
brbflag = true;
|
||||||
|
setMenuPage(PLACING_ORDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char* cutString(const char* str, size_t len) {
|
||||||
|
size_t str_len = strlen(str);
|
||||||
|
char* new_str = (char*)malloc((str_len + 1) * sizeof(char));
|
||||||
|
|
||||||
|
strcpy(new_str, str);
|
||||||
|
|
||||||
|
uint8_t c = 0;
|
||||||
|
for (size_t i = 0; i < str_len; i++) {
|
||||||
|
if (new_str[i] != 0xD0 && new_str[i] != 0xD1) {
|
||||||
|
c++;
|
||||||
|
if (c >= len) {
|
||||||
|
new_str[i+1] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Menu::drawPrice(uint16_t price) {
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
lcd.print(price, DEC);
|
||||||
|
lcd.print("RUB ");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawSizes() {
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
Pizzas::Variation pizza = pizzaSizes[i];
|
||||||
|
if (pizza.id) {
|
||||||
|
lcd.print(pizza.size, DEC);
|
||||||
|
} else {
|
||||||
|
lcd.print(" ");
|
||||||
|
}
|
||||||
|
lcd.print(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawIndex() {
|
||||||
|
uint8_t curentPizza = curentPizzaIndex + 1;
|
||||||
|
uint8_t totalPizzas = pizzas.get_count();
|
||||||
|
lcd.setCursor(11, 1);
|
||||||
|
if (curentPizza < 10) lcd.print(" ");
|
||||||
|
if (totalPizzas < 10) lcd.print(" ");
|
||||||
|
lcd.print(curentPizza, DEC);
|
||||||
|
lcd.print("/");
|
||||||
|
lcd.print(totalPizzas, DEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawTrackingStatus() {
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
if (trackingStatus == UNKNOWN) {
|
||||||
|
lcd.print("Получение...");
|
||||||
|
} else {
|
||||||
|
lcd.print(trackingStatusStrings[trackingStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawDough(Pizzas::Dough dough) {
|
||||||
|
switch (dough) {
|
||||||
|
case Pizzas::TRADITIONAL:
|
||||||
|
lcd.print("обычн");
|
||||||
|
break;
|
||||||
|
case Pizzas::THIN:
|
||||||
|
lcd.print("тонк");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lcd.print(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawPriceShort(uint16_t price) {
|
||||||
|
lcd.setCursor(11, 0);
|
||||||
|
for (int d=1000; price<d; d/=10) lcd.print(" ");;
|
||||||
|
lcd.print(price, DEC);
|
||||||
|
lcd.print("p");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawSizeSelectUpdate() {
|
||||||
|
Pizzas::Variation var = pizzaSizes[pizzaSizesIndex];
|
||||||
|
drawPriceShort(var.price);
|
||||||
|
uint8_t cursor = pizzaSizesIndex * 4;
|
||||||
|
lcd.setCursor(cursor, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawPizzaName(char* name) {
|
||||||
|
char* shortName = cutString(name, 16);
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print(shortName);
|
||||||
|
free(shortName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::drawVariation(Pizzas::Variation var) {
|
||||||
|
lcd.setCursor(0, 1);
|
||||||
|
lcd.write(var.dough == Pizzas::THIN ? '_' : 0xFF);
|
||||||
|
lcd.print(" ");
|
||||||
|
lcd.print(var.size, DEC);
|
||||||
|
lcd.print(" ");
|
||||||
|
lcd.print(var.price, DEC);
|
||||||
|
lcd.print("p");
|
||||||
|
lcd.setCursor(11, 1);
|
||||||
|
lcd.print("conf?");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Menu::scroll(int8_t direction) {
|
||||||
|
switch (menuPage) {
|
||||||
|
case PIZZA_SELECT: {
|
||||||
|
uint8_t newIndex constrain(curentPizzaIndex + direction, 0, pizzas.get_count() - 1);
|
||||||
|
if (curentPizzaIndex == newIndex) return;
|
||||||
|
curentPizzaIndex = newIndex;
|
||||||
|
draw();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DOUGH_SELECT: {
|
||||||
|
selectedDough = (selectedDough == Pizzas::THIN) ? Pizzas::TRADITIONAL : Pizzas::THIN;
|
||||||
|
initVariations();
|
||||||
|
draw();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIZE_SELECT: {
|
||||||
|
uint8_t sizesCount;
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
if (pizzaSizes[i].id) sizesCount++;
|
||||||
|
}
|
||||||
|
pizzaSizesIndex = constrain(pizzaSizesIndex + direction, 0, sizesCount - 1);
|
||||||
|
drawSizeSelectUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void Menu::animation() {
|
||||||
|
switch (menuPage) {
|
||||||
|
case WIFI_CONNECT:
|
||||||
|
if (millis() - animationTick > 500) {
|
||||||
|
animationState = !animationState;
|
||||||
|
animationTick = millis();
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DOUGH_SELECT:
|
||||||
|
case SIZE_SELECT:
|
||||||
|
if (millis() - animationTick > 300) {
|
||||||
|
if (animationState) {
|
||||||
|
lcd.cursor_on();
|
||||||
|
} else {
|
||||||
|
lcd.cursor_off();
|
||||||
|
}
|
||||||
|
animationState = !animationState;
|
||||||
|
animationTick = millis();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Menu::draw() {
|
||||||
|
switch (menuPage) {
|
||||||
|
case WIFI_CONNECT:
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
if (animationState) {
|
||||||
|
lcd.print("WIFI CONNECTING ");
|
||||||
|
} else {
|
||||||
|
lcd.print("WIFI CONNECTING.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PRELOAD:
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print("Загрузка питс...");
|
||||||
|
break;
|
||||||
|
case PIZZA_SELECT: {
|
||||||
|
Pizzas::Pizza pizza = pizzas.get(curentPizzaIndex);
|
||||||
|
lcd.clear();
|
||||||
|
drawPizzaName(pizza.name);
|
||||||
|
drawPrice(pizza.price);
|
||||||
|
drawIndex();
|
||||||
|
digitalWrite(LEDPIN, LOW);
|
||||||
|
brbflag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DOUGH_SELECT: {
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
drawDough(Pizzas::TRADITIONAL);
|
||||||
|
drawDough(Pizzas::THIN);
|
||||||
|
Pizzas::Variation var = pizzaSizes[pizzaSizesIndex];
|
||||||
|
drawPriceShort(var.price);
|
||||||
|
drawSizes();
|
||||||
|
uint8_t cursor = (selectedDough == Pizzas::TRADITIONAL) ? 0 : 6;
|
||||||
|
lcd.setCursor(cursor, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SIZE_SELECT:
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
drawDough(selectedDough);
|
||||||
|
drawSizes();
|
||||||
|
drawSizeSelectUpdate();
|
||||||
|
break;
|
||||||
|
case CONFIRMATION: {
|
||||||
|
Pizzas::Pizza pizza = pizzas.get(curentPizzaIndex);
|
||||||
|
Pizzas::Variation var = pizzaSizes[pizzaSizesIndex];
|
||||||
|
lcd.clear();
|
||||||
|
drawPizzaName(pizza.name);
|
||||||
|
drawVariation(var);
|
||||||
|
digitalWrite(LEDPIN, HIGH);
|
||||||
|
delay(3000);
|
||||||
|
brbflag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PLACING_ORDER:
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print("Заказываем...");
|
||||||
|
digitalWrite(LEDPIN, LOW);
|
||||||
|
break;
|
||||||
|
case TRACKING:
|
||||||
|
lcd.clear();
|
||||||
|
lcd.setCursor(0, 0);
|
||||||
|
lcd.print("Статус:");
|
||||||
|
drawTrackingStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/parser.cpp
Normal file
102
src/parser.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#include "parser.h"
|
||||||
|
|
||||||
|
|
||||||
|
void PizzaHandler::startDocument() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::startArray(ElementPath path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::startObject(ElementPath path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::value(ElementPath path, ElementValue value) {
|
||||||
|
char fullPath[200] = "";
|
||||||
|
const char* currentKey = path.getKey();
|
||||||
|
Menu &menu = Menu::getInstance();
|
||||||
|
menu.tick();
|
||||||
|
if (path.getCount() > 2) {
|
||||||
|
char grand[50] = "";
|
||||||
|
path.get(-2)->toString(grand);
|
||||||
|
char parent[50] = "";
|
||||||
|
path.get(-1)->toString(parent);
|
||||||
|
if (currentKey[0] == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!strcmp(grand, "goods")) {
|
||||||
|
if(!strcmp(currentKey, "name")) {
|
||||||
|
value.toString(last_pizza.name);
|
||||||
|
uint8_t len = strlen(last_pizza.name);
|
||||||
|
|
||||||
|
for (int i = 0; i < len - 1; ++i) {
|
||||||
|
last_pizza.name[i] = last_pizza.name[i + 1];
|
||||||
|
}
|
||||||
|
last_pizza.name[len - 2] = '\0';
|
||||||
|
|
||||||
|
} else if (!strcmp(currentKey, "id")) {
|
||||||
|
last_pizza.id = value.getInt();
|
||||||
|
ElementSelector* parent_selector = path.getParent();
|
||||||
|
|
||||||
|
char key[50] = "";
|
||||||
|
parent_selector->toString(key);
|
||||||
|
} else if (!strcmp(currentKey, "good_type")) {
|
||||||
|
if (strlen(last_pizza.name) && last_pizza.id && last_pizza.variations_count) {
|
||||||
|
pizzas.add_pizza(last_pizza);
|
||||||
|
if (menu.getMenuPage() == Menu::PRELOAD) {
|
||||||
|
menu.setMenuPage(Menu::PIZZA_SELECT);
|
||||||
|
} else {
|
||||||
|
menu.updateIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_pizza = {};
|
||||||
|
}
|
||||||
|
} else if (!strcmp(grand, "variations")) {
|
||||||
|
if(!strcmp(currentKey, "id")) {
|
||||||
|
char var_str[10];
|
||||||
|
value.toString(var_str);
|
||||||
|
new_variation.id = value.getInt();
|
||||||
|
} else if(!strcmp(currentKey, "price")) {
|
||||||
|
new_variation.price = value.getInt();
|
||||||
|
#ifdef MINPRICE
|
||||||
|
if (new_variation.price < MINPRICE) {
|
||||||
|
new_variation = {};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (new_variation.price < last_pizza.price || last_pizza.price == 0) {
|
||||||
|
last_pizza.price = new_variation.price;
|
||||||
|
}
|
||||||
|
} else if (!strcmp(currentKey, "stuffed_crust")) {
|
||||||
|
char crust[10];
|
||||||
|
value.toString(crust);
|
||||||
|
if(strcmp(crust, "\"none\"")) {
|
||||||
|
new_variation = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!strcmp(parent, "kind")) {
|
||||||
|
if(!strcmp(currentKey, "id")) {
|
||||||
|
new_variation.dough = static_cast<Pizzas::Dough>(value.getInt());
|
||||||
|
}
|
||||||
|
} else if (!strcmp(parent, "size")) {
|
||||||
|
if(!strcmp(currentKey, "value")) {
|
||||||
|
new_variation.size = value.getInt();
|
||||||
|
if (new_variation.id) {
|
||||||
|
last_pizza.variations[last_pizza.variations_count] = new_variation;
|
||||||
|
last_pizza.variations_count++;
|
||||||
|
}
|
||||||
|
new_variation = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::endArray(ElementPath path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::endObject(ElementPath path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::endDocument() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PizzaHandler::whitespace(char c) {
|
||||||
|
}
|
||||||
67
src/requests.cpp
Normal file
67
src/requests.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include "requests.h"
|
||||||
|
|
||||||
|
|
||||||
|
char* Request::_get_url(const char* path) {
|
||||||
|
size_t len = strlen(API_URL) + strlen(path) + 1;
|
||||||
|
char* url = (char*)malloc(len);
|
||||||
|
strcpy(url, API_URL);
|
||||||
|
strcat(url, path);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClient* Request::_get_client(const char* method, char* url) {
|
||||||
|
HTTPClient* http = new HTTPClient();
|
||||||
|
http->begin(url);
|
||||||
|
if (!strcmp(method, "POST")) {
|
||||||
|
http->addHeader("Content-Type", "application/json");
|
||||||
|
http->addHeader("Accept", "application/json");
|
||||||
|
} else {
|
||||||
|
http->addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8");
|
||||||
|
}
|
||||||
|
http->addHeader("Accept-Language", "en-US,en;q=0.5");
|
||||||
|
http->setUserAgent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0");
|
||||||
|
return http;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::request(const char* method, char* path, ArudinoStreamParser& parser) {
|
||||||
|
char* url = _get_url(path);
|
||||||
|
HTTPClient* http = _get_client(method, url);
|
||||||
|
|
||||||
|
int httpCode = http->GET();
|
||||||
|
if (httpCode == HTTP_CODE_OK) {
|
||||||
|
http->writeToStream(&parser);
|
||||||
|
}
|
||||||
|
http->end();
|
||||||
|
delete http;
|
||||||
|
free(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* Request::request(const char* method, const char* path, const char* payload) {
|
||||||
|
char* url = _get_url(path);
|
||||||
|
HTTPClient* http = _get_client(method, url);
|
||||||
|
|
||||||
|
int httpCode = http->POST(payload);
|
||||||
|
|
||||||
|
const size_t len = http->getSize() + 1;
|
||||||
|
char* response = (char*)malloc(len);
|
||||||
|
http->getString().toCharArray(response, len);
|
||||||
|
http->end();
|
||||||
|
delete http;
|
||||||
|
free(url);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* Request::request(const char* method, const char* path) {
|
||||||
|
char* url = _get_url(path);
|
||||||
|
HTTPClient* http = _get_client(method, url);
|
||||||
|
|
||||||
|
int httpCode = http->GET();
|
||||||
|
|
||||||
|
const size_t len = http->getSize() + 1;
|
||||||
|
char* response = (char*)malloc(len);
|
||||||
|
http->getString().toCharArray(response, len);
|
||||||
|
http->end();
|
||||||
|
delete http;
|
||||||
|
free(url);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user