feat: FTP server with Wi-Fi, NTP, LVGL UI — initial working build
- Rewrite main.cpp: SD_MMC mount, Wi-Fi from JSON, NTP Vienna, FTP server - Add ui.h/ui.cpp: LVGL status screen (IP, FTP status, SD bar, time) - Add build_framework_libs.py: dynamic ESP32 Core 3.x lib resolution - Remove unused sensor libs (IMU, magnetometer, fuel gauge, charger) - Add SimpleFTPServer + ArduinoJson dependencies - Build succeeds: RAM 19.2%, Flash 4.2%
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
#include "ui.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
/* ── colour palette ──────────────────────────────────────────────── */
|
||||
#define CLR_BG 0x111111
|
||||
#define CLR_PANEL_BG 0x1A1A1A
|
||||
#define CLR_PANEL_BRD 0x333333
|
||||
#define CLR_WHITE 0xFFFFFF
|
||||
#define CLR_GRAY 0x888888
|
||||
#define CLR_CYAN 0x00E5FF
|
||||
#define CLR_GREEN 0x4CAF50
|
||||
#define CLR_RED 0xF44336
|
||||
#define CLR_BAR_BG 0x2A2A2A
|
||||
#define CLR_BAR_FILL 0x4CAF50
|
||||
#define CLR_ERROR_BG 0x330000
|
||||
#define CLR_DIMMED 0x777777
|
||||
#define CLR_SEPARATOR 0x333333
|
||||
|
||||
/* ── screen dimensions ───────────────────────────────────────────── */
|
||||
#define SCR_W 410
|
||||
#define SCR_H 502
|
||||
|
||||
/* ── static widget handles ───────────────────────────────────────── */
|
||||
static lv_obj_t *lbl_title;
|
||||
static lv_obj_t *line_sep;
|
||||
static lv_obj_t *lbl_wifi;
|
||||
static lv_obj_t *lbl_ip;
|
||||
static lv_obj_t *panel_ftp;
|
||||
static lv_obj_t *lbl_ftp_status;
|
||||
static lv_obj_t *lbl_ftp_url;
|
||||
static lv_obj_t *lbl_ftp_creds;
|
||||
static lv_obj_t *lbl_sd_title;
|
||||
static lv_obj_t *bar_sd;
|
||||
static lv_obj_t *lbl_sd_text;
|
||||
static lv_obj_t *lbl_time;
|
||||
static lv_obj_t *lbl_date;
|
||||
static lv_obj_t *lbl_error;
|
||||
|
||||
/* ── cached IP for FTP URL ───────────────────────────────────────── */
|
||||
static char cached_ip[48] = "0.0.0.0";
|
||||
|
||||
/* ── helpers ─────────────────────────────────────────────────────── */
|
||||
static lv_obj_t *make_label(lv_obj_t *parent, const lv_font_t *font,
|
||||
uint32_t color, lv_align_t align,
|
||||
lv_coord_t x_ofs, lv_coord_t y_ofs,
|
||||
const char *text)
|
||||
{
|
||||
lv_obj_t *lbl = lv_label_create(parent);
|
||||
lv_label_set_text(lbl, text);
|
||||
lv_obj_set_style_text_font(lbl, font, 0);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_hex(color), 0);
|
||||
lv_obj_align(lbl, align, x_ofs, y_ofs);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
/* ── public API ──────────────────────────────────────────────────── */
|
||||
|
||||
void ui_init()
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* screen background */
|
||||
lv_obj_set_style_bg_color(scr, lv_color_hex(CLR_BG), 0);
|
||||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||||
|
||||
/* ── title ───────────────────────────────────────────────────── */
|
||||
lbl_title = make_label(scr, &lv_font_montserrat_26, CLR_WHITE,
|
||||
LV_ALIGN_TOP_MID, 0, 20, "FTP Explorer");
|
||||
|
||||
/* thin separator line */
|
||||
static lv_point_t line_pts[] = {{55, 0}, {355, 0}};
|
||||
line_sep = lv_line_create(scr);
|
||||
lv_line_set_points(line_sep, line_pts, 2);
|
||||
lv_obj_set_style_line_color(line_sep, lv_color_hex(CLR_SEPARATOR), 0);
|
||||
lv_obj_set_style_line_width(line_sep, 1, 0);
|
||||
lv_obj_align(line_sep, LV_ALIGN_TOP_LEFT, 0, 54);
|
||||
|
||||
/* ── WiFi section ────────────────────────────────────────────── */
|
||||
lbl_wifi = make_label(scr, &lv_font_montserrat_18, CLR_GREEN,
|
||||
LV_ALIGN_TOP_LEFT, 24, 70,
|
||||
LV_SYMBOL_WIFI " Disconnected");
|
||||
|
||||
lbl_ip = make_label(scr, &lv_font_montserrat_30, CLR_CYAN,
|
||||
LV_ALIGN_TOP_LEFT, 24, 100, "0.0.0.0");
|
||||
|
||||
/* ── FTP panel ───────────────────────────────────────────────── */
|
||||
panel_ftp = lv_obj_create(scr);
|
||||
lv_obj_set_size(panel_ftp, SCR_W - 40, 120);
|
||||
lv_obj_align(panel_ftp, LV_ALIGN_TOP_MID, 0, 145);
|
||||
lv_obj_set_style_bg_color(panel_ftp, lv_color_hex(CLR_PANEL_BG), 0);
|
||||
lv_obj_set_style_bg_opa(panel_ftp, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_color(panel_ftp, lv_color_hex(CLR_PANEL_BRD), 0);
|
||||
lv_obj_set_style_border_width(panel_ftp, 1, 0);
|
||||
lv_obj_set_style_radius(panel_ftp, 10, 0);
|
||||
lv_obj_set_style_pad_all(panel_ftp, 12, 0);
|
||||
lv_obj_clear_flag(panel_ftp, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
/* panel title */
|
||||
lv_obj_t *lbl_ftp_title = lv_label_create(panel_ftp);
|
||||
lv_label_set_text(lbl_ftp_title, "FTP Server");
|
||||
lv_obj_set_style_text_font(lbl_ftp_title, &lv_font_montserrat_18, 0);
|
||||
lv_obj_set_style_text_color(lbl_ftp_title, lv_color_hex(CLR_WHITE), 0);
|
||||
lv_obj_align(lbl_ftp_title, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
|
||||
lbl_ftp_status = lv_label_create(panel_ftp);
|
||||
lv_label_set_text(lbl_ftp_status, "Status: Idle");
|
||||
lv_obj_set_style_text_font(lbl_ftp_status, &lv_font_montserrat_18, 0);
|
||||
lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(CLR_GREEN), 0);
|
||||
lv_obj_align(lbl_ftp_status, LV_ALIGN_TOP_LEFT, 0, 28);
|
||||
|
||||
lbl_ftp_url = lv_label_create(panel_ftp);
|
||||
lv_label_set_text(lbl_ftp_url, "ftp://0.0.0.0");
|
||||
lv_obj_set_style_text_font(lbl_ftp_url, &lv_font_montserrat_18, 0);
|
||||
lv_obj_set_style_text_color(lbl_ftp_url, lv_color_hex(CLR_DIMMED), 0);
|
||||
lv_obj_align(lbl_ftp_url, LV_ALIGN_TOP_LEFT, 0, 54);
|
||||
|
||||
lbl_ftp_creds = lv_label_create(panel_ftp);
|
||||
lv_label_set_text(lbl_ftp_creds, "User: kode Pass: kode");
|
||||
lv_obj_set_style_text_font(lbl_ftp_creds, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_ftp_creds, lv_color_hex(CLR_GRAY), 0);
|
||||
lv_obj_align(lbl_ftp_creds, LV_ALIGN_TOP_LEFT, 0, 80);
|
||||
|
||||
/* ── SD card section ─────────────────────────────────────────── */
|
||||
lbl_sd_title = make_label(scr, &lv_font_montserrat_18, CLR_WHITE,
|
||||
LV_ALIGN_TOP_LEFT, 24, 280, "SD Card");
|
||||
|
||||
bar_sd = lv_bar_create(scr);
|
||||
lv_obj_set_size(bar_sd, SCR_W - 48, 14);
|
||||
lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 24, 310);
|
||||
lv_bar_set_range(bar_sd, 0, 100);
|
||||
lv_bar_set_value(bar_sd, 0, LV_ANIM_OFF);
|
||||
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_BG), 0);
|
||||
lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL),
|
||||
LV_PART_INDICATOR);
|
||||
lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_radius(bar_sd, 4, 0);
|
||||
lv_obj_set_style_radius(bar_sd, 4, LV_PART_INDICATOR);
|
||||
|
||||
lbl_sd_text = make_label(scr, &lv_font_montserrat_14, CLR_GRAY,
|
||||
LV_ALIGN_TOP_LEFT, 24, 330, "0.0 / 0.0 GB");
|
||||
|
||||
/* ── time / date ─────────────────────────────────────────────── */
|
||||
lbl_time = make_label(scr, &lv_font_montserrat_42, CLR_WHITE,
|
||||
LV_ALIGN_TOP_MID, 0, 400, "--:--:--");
|
||||
|
||||
lbl_date = make_label(scr, &lv_font_montserrat_18, CLR_GRAY,
|
||||
LV_ALIGN_TOP_MID, 0, 452, "--.--.----");
|
||||
|
||||
/* ── error overlay (hidden) ──────────────────────────────────── */
|
||||
lbl_error = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_error, "");
|
||||
lv_obj_set_width(lbl_error, SCR_W - 48);
|
||||
lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_18, 0);
|
||||
lv_obj_set_style_text_color(lbl_error, lv_color_hex(CLR_RED), 0);
|
||||
lv_obj_set_style_bg_color(lbl_error, lv_color_hex(CLR_ERROR_BG), 0);
|
||||
lv_obj_set_style_bg_opa(lbl_error, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_pad_all(lbl_error, 8, 0);
|
||||
lv_obj_set_style_radius(lbl_error, 6, 0);
|
||||
lv_obj_set_style_text_align(lbl_error, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_label_set_long_mode(lbl_error, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(lbl_error, LV_ALIGN_TOP_MID, 0, 360);
|
||||
lv_obj_add_flag(lbl_error, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
void ui_set_wifi(const char *ssid, const char *ip)
|
||||
{
|
||||
if (ssid && ssid[0]) {
|
||||
lv_label_set_text_fmt(lbl_wifi, LV_SYMBOL_WIFI " %s", ssid);
|
||||
lv_obj_set_style_text_color(lbl_wifi, lv_color_hex(CLR_GREEN), 0);
|
||||
} else {
|
||||
lv_label_set_text(lbl_wifi, LV_SYMBOL_WIFI " Disconnected");
|
||||
lv_obj_set_style_text_color(lbl_wifi, lv_color_hex(CLR_RED), 0);
|
||||
}
|
||||
|
||||
if (ip && ip[0]) {
|
||||
lv_label_set_text(lbl_ip, ip);
|
||||
snprintf(cached_ip, sizeof(cached_ip), "%s", ip);
|
||||
} else {
|
||||
lv_label_set_text(lbl_ip, "0.0.0.0");
|
||||
snprintf(cached_ip, sizeof(cached_ip), "0.0.0.0");
|
||||
}
|
||||
|
||||
/* keep FTP URL in sync */
|
||||
lv_label_set_text_fmt(lbl_ftp_url, "ftp://%s", cached_ip);
|
||||
}
|
||||
|
||||
void ui_set_ftp(const char *status)
|
||||
{
|
||||
lv_label_set_text_fmt(lbl_ftp_status, "Status: %s", status);
|
||||
}
|
||||
|
||||
void ui_set_time(const char *time_str)
|
||||
{
|
||||
/* expects "HH:MM:SS" — split into time and derive date display */
|
||||
lv_label_set_text(lbl_time, time_str);
|
||||
}
|
||||
|
||||
void ui_set_sd(uint64_t used_mb, uint64_t total_mb)
|
||||
{
|
||||
int pct = 0;
|
||||
if (total_mb > 0) {
|
||||
pct = (int)((used_mb * 100ULL) / total_mb);
|
||||
if (pct > 100) pct = 100;
|
||||
}
|
||||
lv_bar_set_value(bar_sd, pct, LV_ANIM_OFF);
|
||||
|
||||
/* show in GB with one decimal */
|
||||
float used_gb = used_mb / 1024.0f;
|
||||
float total_gb = total_mb / 1024.0f;
|
||||
lv_label_set_text_fmt(lbl_sd_text, "%.1f / %.1f GB", (double)used_gb,
|
||||
(double)total_gb);
|
||||
|
||||
/* colour the bar red when > 90 % */
|
||||
if (pct > 90) {
|
||||
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_RED),
|
||||
LV_PART_INDICATOR);
|
||||
} else {
|
||||
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL),
|
||||
LV_PART_INDICATOR);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_set_error(const char *msg)
|
||||
{
|
||||
if (msg && msg[0]) {
|
||||
lv_label_set_text(lbl_error, msg);
|
||||
lv_obj_clear_flag(lbl_error, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_label_set_text(lbl_error, "");
|
||||
lv_obj_add_flag(lbl_error, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user