Skip to content

Commit 3d33b7f

Browse files
committed
feat(ui): Recipe sync, Spanish translation, thread-safe queue, and footer cleanup
1 parent fa450df commit 3d33b7f

File tree

8 files changed

+565
-157
lines changed

8 files changed

+565
-157
lines changed

display/src/core/ESPNowManager.hpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,26 @@ inline void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
2121
inline struct_message last_sync_data;
2222
inline unsigned long last_sync_time = 0;
2323
inline bool is_server_connected = false;
24+
inline std::function<void(const RecipeSyncData&)> on_recipe_recv_cb = nullptr;
2425

2526
inline void onDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
2627
if (len == sizeof(struct_message)) {
2728
struct_message msg;
2829
memcpy(&msg, data, sizeof(msg));
2930

30-
if (msg.id == 101) { // Sync Response
31+
if (msg.id == REMOTE_CMD_SYNC_RESPONSE) { // Sync Response
3132
last_sync_data = msg;
3233
last_sync_time = millis();
3334
is_server_connected = true;
3435
printf("[ESP-NOW] Pump data received from server!\n");
3536
}
37+
else if (msg.id == REMOTE_CMD_RECIPE_DATA) { // Recipe Part Received
38+
is_server_connected = true;
39+
last_sync_time = millis(); // Alive
40+
if (on_recipe_recv_cb) {
41+
on_recipe_recv_cb(msg.recipeData);
42+
}
43+
}
3644
}
3745
}
3846

@@ -97,8 +105,20 @@ class ESPNowManager {
97105
void requestPumpSync() {
98106
struct_message msg;
99107
memset(&msg, 0, sizeof(msg));
100-
msg.id = 100; // Request Sync
108+
msg.id = REMOTE_CMD_SYNC_REQUEST; // Request Sync
109+
esp_now_send(broadcastAddress, (uint8_t *) &msg, sizeof(msg));
110+
}
111+
112+
void requestRecipeSync() {
113+
struct_message msg;
114+
memset(&msg, 0, sizeof(msg));
115+
msg.id = REMOTE_CMD_RECIPE_SYNC_REQUEST;
101116
esp_now_send(broadcastAddress, (uint8_t *) &msg, sizeof(msg));
117+
printf("[ESP-NOW] Requested Recipe Sync...\n");
118+
}
119+
120+
void setRecipeCallback(std::function<void(const RecipeSyncData&)> cb) {
121+
on_recipe_recv_cb = cb;
102122
}
103123

104124
bool isConnected() {
@@ -112,7 +132,7 @@ class ESPNowManager {
112132
struct_message msg;
113133
memset(&msg, 0, sizeof(msg));
114134

115-
msg.id = 99;
135+
msg.id = REMOTE_CMD_DRINK_ORDER;
116136
strncpy(msg.choose, drinkName.c_str(), sizeof(msg.choose) - 1);
117137

118138
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &msg, sizeof(msg));

display/src/core/remote_protocol.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ struct PumpSyncData {
2121
float calibration[4];
2222
};
2323

24+
struct RecipeSyncData {
25+
uint8_t index;
26+
uint8_t total;
27+
char name[32];
28+
uint16_t ingredientsMl[4]; // [0]=Pump1, [1]=Pump2...
29+
};
30+
31+
enum ActionType {
32+
REMOTE_CMD_DRINK_ORDER = 99,
33+
REMOTE_CMD_SYNC_REQUEST = 100,
34+
REMOTE_CMD_SYNC_RESPONSE = 101,
35+
REMOTE_CMD_RECIPE_SYNC_REQUEST = 102,
36+
REMOTE_CMD_RECIPE_DATA = 103,
37+
// Legacy/Other inputs
38+
REMOTE_CMD_JOYSTICK = 1,
39+
REMOTE_CMD_GYRO = 2
40+
};
41+
2442
typedef struct __attribute__((packed)) struct_message {
2543
int id;
2644
float temp;
@@ -31,6 +49,7 @@ typedef struct __attribute__((packed)) struct_message {
3149
char giroscope[96]; // Command from gyro (aligned)
3250
};
3351
PumpSyncData pumpValues; // Used for ID 101 (Sync Response)
52+
RecipeSyncData recipeData; // Used for ID 103 (Recipe Data)
3453
};
3554
ValuesGiroscope giroscopeValues;
3655
JoystickData joystickValues;

display/src/ui/components/footer/MyFooter.cpp

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,13 @@ lv_obj_t* create_custom_footer(lv_obj_t* parent, const void* icon_src, lv_event_
5252
lv_obj_add_event_cb(img_btn, event_cb, LV_EVENT_CLICKED, NULL);
5353
}
5454

55-
// Status Label (New: Encapsulated here)
56-
lv_obj_t * status_label = lv_label_create(footer);
57-
lv_label_set_text(status_label, "Listo para servir");
58-
lv_obj_set_style_text_color(status_label, lv_color_white(), 0);
59-
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_20, 0);
60-
lv_obj_align(status_label, LV_ALIGN_LEFT_MID, 20, 0);
61-
55+
// Status Label Removed
56+
6257
return footer;
6358
}
6459

6560
void footer_set_status_text(lv_obj_t* footer, const char* text) {
66-
if (!footer) return;
67-
// The label is the second child (index 1) because the image is index 0
68-
lv_obj_t * label = lv_obj_get_child(footer, 1);
69-
if (label && lv_obj_check_type(label, &lv_label_class)) {
70-
lv_label_set_text(label, text);
71-
}
61+
// Disabled
7262
}
7363

7464
lv_obj_t* create_nav_footer(lv_obj_t* parent, lv_event_cb_t on_back, lv_event_cb_t on_next) {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#ifndef MY_MODAL_HPP
2+
#define MY_MODAL_HPP
3+
4+
#include <lvgl.h>
5+
#include <cstdio>
6+
#include <cstring>
7+
8+
// Callback to close the modal
9+
inline void modal_close_cb(lv_event_t * e) {
10+
lv_obj_t * modal_overlay = (lv_obj_t *)lv_event_get_user_data(e);
11+
if (modal_overlay) {
12+
lv_obj_del(modal_overlay);
13+
}
14+
}
15+
16+
/**
17+
* @brief Creates a modal dialog overlay.
18+
*
19+
* @param parent Parent object (usually NULL or the active screen)
20+
* @param title Title of the modal
21+
* @param message Message body
22+
* @param on_confirm Callback for confirm button (LV_EVENT_CLICKED)
23+
* @param on_cancel Callback for cancel button (LV_EVENT_CLICKED)
24+
* @param user_data Pointer to pass to callbacks (e.g. drink name string)
25+
*/
26+
inline void create_custom_modal(lv_obj_t * parent, const char * title, const char * message,
27+
lv_event_cb_t on_confirm, lv_event_cb_t on_cancel, void * user_data) {
28+
29+
// 1. Full Screen Overlay (Dim Background)
30+
lv_obj_t * overlay = lv_obj_create(parent ? parent : lv_scr_act());
31+
lv_obj_set_size(overlay, LV_PCT(100), LV_PCT(100));
32+
lv_obj_set_style_bg_color(overlay, lv_color_black(), 0);
33+
lv_obj_set_style_bg_opa(overlay, LV_OPA_70, 0); // Semi-transparent
34+
lv_obj_set_style_border_width(overlay, 0, 0);
35+
lv_obj_center(overlay);
36+
37+
// Block clicks on background
38+
lv_obj_add_flag(overlay, LV_OBJ_FLAG_CLICKABLE);
39+
40+
// 2. Modal Box
41+
lv_obj_t * mbox = lv_obj_create(overlay);
42+
lv_obj_set_size(mbox, 400, 250);
43+
lv_obj_center(mbox);
44+
lv_obj_set_style_bg_color(mbox, lv_color_hex(0x303030), 0);
45+
lv_obj_set_style_border_color(mbox, lv_color_hex(0xFFFFFF), 0);
46+
lv_obj_set_style_border_width(mbox, 2, 0);
47+
lv_obj_set_flex_flow(mbox, LV_FLEX_FLOW_COLUMN);
48+
lv_obj_set_flex_align(mbox, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
49+
50+
// 3. Title
51+
lv_obj_t * lbl_title = lv_label_create(mbox);
52+
lv_label_set_text(lbl_title, title);
53+
lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_20, 0);
54+
lv_obj_set_style_text_color(lbl_title, lv_color_hex(0xFFA500), 0); // Orange
55+
56+
// 4. Message
57+
lv_obj_t * lbl_msg = lv_label_create(mbox);
58+
lv_label_set_text(lbl_msg, message);
59+
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP);
60+
lv_obj_set_width(lbl_msg, LV_PCT(90));
61+
lv_obj_set_style_text_align(lbl_msg, LV_TEXT_ALIGN_CENTER, 0);
62+
lv_obj_set_style_text_font(lbl_msg, &lv_font_montserrat_16, 0);
63+
lv_obj_set_style_text_color(lbl_msg, lv_color_white(), 0);
64+
65+
// 5. Buttons Container
66+
lv_obj_t * btn_cont = lv_obj_create(mbox);
67+
lv_obj_set_size(btn_cont, LV_PCT(100), LV_SIZE_CONTENT);
68+
lv_obj_set_style_bg_opa(btn_cont, LV_OPA_TRANSP, 0);
69+
lv_obj_set_style_border_width(btn_cont, 0, 0);
70+
lv_obj_set_flex_flow(btn_cont, LV_FLEX_FLOW_ROW);
71+
lv_obj_set_flex_align(btn_cont, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
72+
73+
// Cancel Button
74+
lv_obj_t * btn_cancel = lv_btn_create(btn_cont);
75+
lv_obj_set_size(btn_cancel, 120, 50);
76+
lv_obj_set_style_bg_color(btn_cancel, lv_color_hex(0xFF0000), 0); // Red
77+
lv_obj_t * lbl_cancel = lv_label_create(btn_cancel);
78+
lv_label_set_text(lbl_cancel, "CANCEL");
79+
lv_obj_center(lbl_cancel);
80+
81+
// Add event to close modal + user callback
82+
lv_obj_add_event_cb(btn_cancel, modal_close_cb, LV_EVENT_CLICKED, overlay);
83+
if (on_cancel) {
84+
lv_obj_add_event_cb(btn_cancel, on_cancel, LV_EVENT_CLICKED, user_data);
85+
}
86+
87+
// Confirm Button
88+
lv_obj_t * btn_confirm = lv_btn_create(btn_cont);
89+
lv_obj_set_size(btn_confirm, 120, 50);
90+
lv_obj_set_style_bg_color(btn_confirm, lv_color_hex(0x00AA00), 0); // Green
91+
lv_obj_t * lbl_confirm = lv_label_create(btn_confirm);
92+
lv_label_set_text(lbl_confirm, "CONFIRM");
93+
lv_obj_center(lbl_confirm);
94+
95+
// Add event to close modal + user callback
96+
lv_obj_add_event_cb(btn_confirm, modal_close_cb, LV_EVENT_CLICKED, overlay);
97+
if (on_confirm) {
98+
lv_obj_add_event_cb(btn_confirm, on_confirm, LV_EVENT_CLICKED, user_data);
99+
}
100+
}
101+
102+
#endif // MY_MODAL_HPP
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#ifndef MY_RECIPE_MODAL_HPP
2+
#define MY_RECIPE_MODAL_HPP
3+
4+
#include <lvgl.h>
5+
#include <vector>
6+
#include <Arduino.h>
7+
8+
// --- Data Models (Local for UI Config) ---
9+
struct ConfigIngredient {
10+
String name;
11+
int pumpId; // 1-4
12+
int amountMl;
13+
};
14+
15+
struct ConfigCocktail {
16+
String name;
17+
const void* icon; // LVGL Image src
18+
uint32_t color;
19+
std::vector<ConfigIngredient> ingredients;
20+
};
21+
22+
// --- Callback Type ---
23+
typedef void (*RecipeSaveCallback)(ConfigCocktail* modifiedCocktail);
24+
25+
// --- State Management ---
26+
static lv_obj_t * g_recipe_modal = NULL;
27+
static ConfigCocktail * g_current_cocktail = NULL;
28+
static RecipeSaveCallback g_save_cb = NULL;
29+
30+
// Helper to close modal
31+
inline void recipe_modal_close_cb(lv_event_t * e) {
32+
if (g_recipe_modal) {
33+
lv_obj_del(g_recipe_modal);
34+
g_recipe_modal = NULL;
35+
g_current_cocktail = NULL;
36+
}
37+
}
38+
39+
// Helper to update ml label
40+
inline void ingredient_slider_cb(lv_event_t * e) {
41+
lv_obj_t * slider = (lv_obj_t *)lv_event_get_target(e);
42+
lv_obj_t * label = (lv_obj_t *)lv_event_get_user_data(e);
43+
44+
int val = lv_slider_get_value(slider);
45+
lv_label_set_text_fmt(label, "%d ml", val);
46+
47+
// Note: In a real implementation we would update the temp struct here
48+
}
49+
50+
// Create the dynamic modal
51+
inline void create_recipe_modal(lv_obj_t * parent, ConfigCocktail * cocktail, RecipeSaveCallback on_save) {
52+
g_current_cocktail = cocktail;
53+
g_save_cb = on_save;
54+
55+
// 1. Overlay
56+
g_recipe_modal = lv_obj_create(parent ? parent : lv_scr_act());
57+
lv_obj_set_size(g_recipe_modal, LV_PCT(100), LV_PCT(100));
58+
lv_obj_set_style_bg_color(g_recipe_modal, lv_color_black(), 0);
59+
lv_obj_set_style_bg_opa(g_recipe_modal, LV_OPA_80, 0);
60+
lv_obj_set_style_border_width(g_recipe_modal, 0, 0);
61+
lv_obj_center(g_recipe_modal);
62+
lv_obj_add_flag(g_recipe_modal, LV_OBJ_FLAG_CLICKABLE); // Block clicks
63+
64+
// 2. Modal Box
65+
lv_obj_t * mbox = lv_obj_create(g_recipe_modal);
66+
lv_obj_set_size(mbox, 500, 350);
67+
lv_obj_center(mbox);
68+
lv_obj_set_style_bg_color(mbox, lv_color_hex(0x202020), 0);
69+
lv_obj_set_style_border_color(mbox, lv_color_hex(cocktail->color), 0); // Border matches drink color
70+
lv_obj_set_style_border_width(mbox, 2, 0);
71+
lv_obj_set_flex_flow(mbox, LV_FLEX_FLOW_COLUMN);
72+
73+
// 3. Header
74+
lv_obj_t * title = lv_label_create(mbox);
75+
lv_label_set_text_fmt(title, "EDITAR: %s", cocktail->name.c_str());
76+
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
77+
lv_obj_set_style_text_color(title, lv_color_hex(cocktail->color), 0);
78+
lv_obj_set_style_align(title, LV_ALIGN_TOP_MID, 0);
79+
80+
// 4. Ingredients List (Scrollable)
81+
lv_obj_t * list = lv_obj_create(mbox);
82+
lv_obj_set_size(list, LV_PCT(100), LV_PCT(65));
83+
lv_obj_set_style_bg_opa(list, LV_OPA_TRANSP, 0);
84+
lv_obj_set_style_border_width(list, 0, 0);
85+
lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN);
86+
lv_obj_set_style_pad_row(list, 15, 0);
87+
88+
for (auto &ing : cocktail->ingredients) {
89+
lv_obj_t * item = lv_obj_create(list);
90+
lv_obj_set_width(item, LV_PCT(100));
91+
lv_obj_set_height(item, LV_SIZE_CONTENT);
92+
lv_obj_set_style_bg_opa(item, LV_OPA_TRANSP, 0);
93+
lv_obj_set_style_border_width(item, 0, 0);
94+
lv_obj_set_style_pad_left(item, 10, 0);
95+
lv_obj_set_style_pad_right(item, 10, 0);
96+
97+
// Label: Name + Amount
98+
lv_obj_t * header = lv_obj_create(item);
99+
lv_obj_set_size(header, LV_PCT(100), LV_SIZE_CONTENT);
100+
lv_obj_set_style_bg_opa(header, LV_OPA_TRANSP, 0);
101+
lv_obj_set_style_border_width(header, 0, 0);
102+
lv_obj_set_style_pad_all(header, 0, 0);
103+
lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW);
104+
lv_obj_set_flex_align(header, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
105+
106+
lv_obj_t * name_lbl = lv_label_create(header);
107+
lv_label_set_text(name_lbl, ing.name.c_str());
108+
lv_obj_set_style_text_color(name_lbl, lv_color_hex(0xAAAAAA), 0);
109+
110+
lv_obj_t * val_lbl = lv_label_create(header);
111+
lv_label_set_text_fmt(val_lbl, "%d ml", ing.amountMl);
112+
lv_obj_set_style_text_color(val_lbl, lv_color_white(), 0);
113+
114+
// Slider
115+
lv_obj_t * slider = lv_slider_create(item);
116+
lv_obj_set_width(slider, LV_PCT(95)); // Reduce width slightly to avoid edge clipping
117+
lv_obj_set_align(slider, LV_ALIGN_CENTER); // Center it
118+
lv_slider_set_range(slider, 0, 300); // 0 to 300ml
119+
lv_slider_set_value(slider, ing.amountMl, LV_ANIM_OFF);
120+
121+
// Style slider
122+
lv_obj_set_style_bg_color(slider, lv_color_hex(0x505050), LV_PART_MAIN);
123+
lv_obj_set_style_bg_color(slider, lv_color_hex(cocktail->color), LV_PART_INDICATOR);
124+
lv_obj_set_style_bg_color(slider, lv_color_white(), LV_PART_KNOB);
125+
126+
lv_obj_add_event_cb(slider, ingredient_slider_cb, LV_EVENT_VALUE_CHANGED, val_lbl);
127+
}
128+
129+
// 5. Buttons
130+
lv_obj_t * btn_cont = lv_obj_create(mbox);
131+
lv_obj_set_size(btn_cont, LV_PCT(100), LV_SIZE_CONTENT);
132+
lv_obj_set_style_bg_opa(btn_cont, LV_OPA_TRANSP, 0);
133+
lv_obj_set_style_border_width(btn_cont, 0, 0);
134+
lv_obj_set_flex_flow(btn_cont, LV_FLEX_FLOW_ROW);
135+
lv_obj_set_flex_align(btn_cont, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
136+
lv_obj_set_style_pad_top(btn_cont, 10, 0);
137+
138+
// Close Button (Full width for now, user didn't ask for generic save logic yet)
139+
lv_obj_t * btn_close = lv_btn_create(btn_cont);
140+
lv_obj_set_width(btn_close, LV_PCT(100));
141+
lv_obj_set_style_bg_color(btn_close, lv_color_hex(0x444444), 0);
142+
lv_obj_t * lbl_close = lv_label_create(btn_close);
143+
lv_label_set_text(lbl_close, "CERRAR");
144+
lv_obj_center(lbl_close);
145+
lv_obj_add_event_cb(btn_close, recipe_modal_close_cb, LV_EVENT_CLICKED, NULL);
146+
147+
}
148+
149+
#endif // MY_RECIPE_MODAL_HPP

0 commit comments

Comments
 (0)