mirror of https://github.com/meshcore-dev/MeshCore
committed by
GitHub
76 changed files with 153327 additions and 78 deletions
@ -0,0 +1,51 @@ |
|||
{ |
|||
"build": { |
|||
"arduino":{ |
|||
"ldscript": "esp32s3_out.ld", |
|||
"memory_type": "qio_opi", |
|||
"partitions": "elecrow_partitions.csv" |
|||
}, |
|||
"core": "esp32", |
|||
"extra_flags": [ |
|||
"-DBOARD_HAS_PSRAM", |
|||
"-DARDUINO_USB_MODE=1", |
|||
"-DARDUINO_RUNNING_CORE=1", |
|||
"-DARDUINO_EVENT_RUNNING_CORE=1" |
|||
], |
|||
"f_cpu": "240000000L", |
|||
"f_flash": "80000000L", |
|||
"flash_mode": "qio", |
|||
"hwids": [ |
|||
[ |
|||
"0x303A", |
|||
"0x1001" |
|||
] |
|||
], |
|||
"mcu": "esp32s3", |
|||
"variant": "esp32s3" |
|||
}, |
|||
"connectivity": [ |
|||
"wifi" |
|||
], |
|||
"debug": { |
|||
"default_tool": "esp-builtin", |
|||
"onboard_tools": [ |
|||
"esp-builtin" |
|||
], |
|||
"openocd_target": "esp32s3.cfg" |
|||
}, |
|||
"frameworks": [ |
|||
"arduino", |
|||
"espidf" |
|||
], |
|||
"name": "Espressif ESP32-S3-DevKitC-1-N8 -ELECROW", |
|||
"upload": { |
|||
"flash_size": "4MB", |
|||
"maximum_ram_size": 327680, |
|||
"maximum_size": 8388608, |
|||
"require_upload_port": true, |
|||
"speed": 921600 |
|||
}, |
|||
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html", |
|||
"vendor": "Espressif" |
|||
} |
|||
|
@ -0,0 +1,25 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
#define TAG "clock_task" |
|||
|
|||
void clock_task(void *pvParameters) { |
|||
|
|||
vTaskSuspend(NULL); |
|||
|
|||
ESP_LOGI(TAG, "Clock manager: Task running on core %d", xPortGetCoreID()); |
|||
|
|||
uiManager->clearDateTime(); |
|||
|
|||
// TODO: sync clock
|
|||
while (1) { |
|||
// uiManager->updateDateTime(
|
|||
// myClock->getTimeStruct()
|
|||
// );
|
|||
// uiManager->updateValues();
|
|||
vTaskDelay(DELAY_CLOCK_TASK / portTICK_PERIOD_MS); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
#define TAG "lvgl_task" |
|||
|
|||
void lvgl_task(void *pvParameters) { |
|||
|
|||
vTaskSuspend(NULL); |
|||
|
|||
ESP_LOGI(TAG, "UI manager: Task running on core %d", xPortGetCoreID()); |
|||
|
|||
while (1) { |
|||
lv_timer_handler(); |
|||
vTaskDelay(DELAY_LVGL_TASK / portTICK_PERIOD_MS); |
|||
} |
|||
} |
|||
@ -0,0 +1,805 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
#include "uiManager.h" |
|||
|
|||
#include "../src/fonts/fonts.h" |
|||
|
|||
#include <helpers/ContactInfo.h> |
|||
#include <helpers/AdvertDataHelpers.h> |
|||
|
|||
#if defined(LANG_GR) |
|||
const char *UIManager::days[7] = {"Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ"}; |
|||
const char *UIManager::months[12] = {"Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαι", "Ιουν", |
|||
"Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ"}; |
|||
#elif defined(LANG_EN) |
|||
const char *UIManager::days[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; |
|||
const char *UIManager::months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
|||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; |
|||
#endif |
|||
|
|||
#define TAG "UIManager" |
|||
|
|||
extern void handleCommand(char *msg); |
|||
|
|||
UIManager::UIManager() { |
|||
|
|||
tmp_buf = (char*)malloc(128); |
|||
|
|||
lv_disp_t * dispp = lv_disp_get_default(); |
|||
lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), |
|||
false, LV_FONT_DEFAULT); |
|||
lv_disp_set_theme(dispp, theme); |
|||
ui_Screen1_screen_init(); |
|||
ui____initial_actions0 = lv_obj_create(NULL); |
|||
lv_disp_load_scr(ui_Screen1); |
|||
|
|||
} |
|||
|
|||
void UIManager::format_time(uint32_t ts, char *buf, size_t len) |
|||
{ |
|||
time_t t = ts; |
|||
struct tm *tm_info = localtime(&t); |
|||
strftime(buf, len, "%H:%M:%S", tm_info); |
|||
} |
|||
|
|||
void UIManager::format_datetime(char *buf, size_t size, const struct tm *timeinfo) { |
|||
char tmp[64]; |
|||
strftime(tmp, sizeof(tmp), "%a, %d %b %Y", timeinfo); |
|||
|
|||
int wday = timeinfo->tm_wday; // 0=Κυρ ... 6=Σαβ
|
|||
int mon = timeinfo->tm_mon; // 0=Ιαν ... 11=Δεκ
|
|||
|
|||
// replace %a and %b with selected language
|
|||
snprintf(buf, size, "%s, %02d %s %d", days[wday], timeinfo->tm_mday, months[mon], 1900 + timeinfo->tm_year); |
|||
} |
|||
|
|||
void UIManager::updateDateTime(const struct tm timeinfo) { |
|||
// TODO: Add to settings "Date format"
|
|||
char date_str[50]; |
|||
format_datetime(date_str, sizeof(date_str), &timeinfo); |
|||
lv_label_set_text(ui_ValueDate, date_str); |
|||
|
|||
// TODO: Add to settings "Hour format"
|
|||
strftime(tmp_buf, 50, "%H:%M", &timeinfo); // 24h format
|
|||
//strftime(tmp_buf, 50, "%I:%M %p", &timeinfo); // 12h format
|
|||
lv_label_set_text(ui_ValueTime, tmp_buf); |
|||
|
|||
// TODO: Add to settings "dim at night"
|
|||
// TODO: Add to settings "dim hours"
|
|||
// TODO: Add to settings "dim percentage"
|
|||
if (timeinfo.tm_hour > 21 || timeinfo.tm_hour < 7) { |
|||
setNightMode(true); |
|||
} else { |
|||
setNightMode(false); |
|||
} |
|||
} |
|||
|
|||
void UIManager::clearDateTime() { |
|||
#if defined(LANG_EN) |
|||
uiManager->updateInfo("Clock sync...", COLOR_WHITE); |
|||
#elif defined(LANG_GR) |
|||
uiManager->updateInfo("Συγχρονισμός ώρας...", COLOR_WHITE); |
|||
#endif |
|||
lv_label_set_text(ui_ValueDate, "--- --/--/----"); |
|||
lv_label_set_text(ui_ValueTime, "--:--"); |
|||
} |
|||
|
|||
void UIManager::timestampToTime(time_t timestamp, char *buffer, size_t buffer_size) { |
|||
struct tm *time_info; |
|||
time_info = localtime(×tamp); |
|||
strftime(buffer, buffer_size, "%H:%M", time_info); |
|||
} |
|||
|
|||
const char* UIManager::convertDegreesToDirection(int degrees) { |
|||
// Normalize degrees to [0, 360)
|
|||
degrees = degrees % 360; |
|||
if (degrees < 0) degrees += 360; |
|||
|
|||
#if defined(LANG_EN) |
|||
static constexpr const char* dirs[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}; |
|||
#elif defined(LANG_GR) |
|||
static constexpr const char* dirs[] = {"Β", "ΒΑ", "Α", "ΝΑ", "Ν", "ΝΔ", "Δ", "ΒΔ"}; |
|||
#else |
|||
#error "No Language defined!" |
|||
#endif |
|||
|
|||
// Each direction covers 45°, starting at N = 0°
|
|||
int index = static_cast<int>((degrees + 22.5) / 45.0) % 8; |
|||
return dirs[index]; |
|||
} |
|||
|
|||
|
|||
int UIManager::windSpeedToBeaufort(float speed) { |
|||
static const float limits[] = { |
|||
0.5, 1.5, 3.3, 5.5, 7.9, 10.7, |
|||
13.8, 17.1, 20.7, 24.4, 28.4, 32.6 |
|||
}; |
|||
|
|||
for (int i = 0; i < 12; ++i) |
|||
if (speed < limits[i]) |
|||
return i; |
|||
return 12; |
|||
} |
|||
|
|||
void UIManager::updateValues() { |
|||
lv_label_set_text(ui_ValueTime, "--:--"); |
|||
} |
|||
|
|||
void UIManager::updateInfo(const char *str, uint32_t color) { |
|||
// lv_label_set_text(ui_ValueLastUpdate, str);
|
|||
// lv_obj_set_style_text_color(ui_ValueLastUpdate, lv_color_hex(color), LV_PART_MAIN | LV_STATE_DEFAULT);
|
|||
} |
|||
|
|||
void UIManager::addChatBubble(const char *time_str, const char *sender, const char *msg,bool is_self) |
|||
{ |
|||
// Remove oldest
|
|||
if (chat_count >= MAX_CHAT_MESSAGES) { |
|||
lv_obj_del(chat_items[0]); |
|||
memmove(&chat_items[0], &chat_items[1], sizeof(lv_obj_t*) * (MAX_CHAT_MESSAGES - 1)); |
|||
chat_count--; |
|||
} |
|||
|
|||
// Row container (align bubble left/right)
|
|||
lv_obj_t *row = lv_obj_create(ui_ChannelMessages); |
|||
lv_obj_set_width(row, lv_pct(100)); |
|||
lv_obj_set_height(row, LV_SIZE_CONTENT); |
|||
lv_obj_set_style_bg_opa(row, 0, 0); |
|||
lv_obj_set_style_pad_all(row, 0, 0); |
|||
lv_obj_set_style_border_width(row, 0, 0); |
|||
lv_obj_set_style_outline_width(row, 0, 0); |
|||
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); |
|||
|
|||
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); |
|||
lv_obj_set_flex_align(row, |
|||
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START); |
|||
|
|||
// Bubble container (COLUMN)
|
|||
lv_obj_t *bubble = lv_obj_create(row); |
|||
lv_obj_set_width(bubble, lv_pct(85)); |
|||
lv_obj_set_height(bubble, LV_SIZE_CONTENT); |
|||
lv_obj_set_style_radius(bubble, 12, 0); |
|||
lv_obj_set_style_pad_all(bubble, 10, 0); |
|||
lv_obj_set_style_bg_color(bubble, |
|||
is_self ? lv_color_hex(0x1E88E5) : lv_color_hex(0x2C2C2C), 0); |
|||
|
|||
// IMPORTANT: vertical layout inside bubble
|
|||
lv_obj_set_flex_flow(bubble, LV_FLEX_FLOW_COLUMN); |
|||
lv_obj_set_flex_align(bubble, |
|||
LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START); |
|||
|
|||
// Header row (sender + time)
|
|||
lv_obj_t *hdr = lv_obj_create(bubble); |
|||
lv_obj_set_width(hdr, lv_pct(100)); |
|||
lv_obj_set_height(hdr, LV_SIZE_CONTENT); |
|||
lv_obj_set_style_bg_opa(hdr, 0, 0); |
|||
lv_obj_set_style_border_width(hdr, 0, 0); |
|||
lv_obj_set_style_pad_all(hdr, 0, 0); |
|||
lv_obj_set_style_outline_width(hdr, 0, 0); |
|||
|
|||
lv_obj_set_flex_flow(hdr, LV_FLEX_FLOW_ROW); |
|||
lv_obj_set_flex_align(hdr, |
|||
LV_FLEX_ALIGN_SPACE_BETWEEN, |
|||
LV_FLEX_ALIGN_START, //LV_FLEX_ALIGN_CENTER, // Ευθυγράμμιση ονόματος/ώρας στον κάθετο άξονα
|
|||
LV_FLEX_ALIGN_START); |
|||
|
|||
lv_obj_t *lbl_sender = lv_label_create(hdr); |
|||
lv_label_set_text(lbl_sender, sender); |
|||
lv_obj_set_style_text_color(lbl_sender, |
|||
is_self ? lv_color_hex(0xE3F2FD) : lv_color_hex(0x90CAF9), 0); |
|||
lv_obj_set_style_text_font(lbl_sender, &lv_font_arial_22, 0); |
|||
|
|||
lv_obj_t *lbl_time = lv_label_create(hdr); |
|||
lv_label_set_text(lbl_time, time_str); |
|||
lv_obj_set_style_text_color(lbl_time, lv_color_hex(0xB0B0B0), 0); |
|||
lv_obj_set_style_text_font(lbl_time, &lv_font_arial_20, 0); |
|||
|
|||
// Message body (below header)
|
|||
lv_obj_t *lbl_msg = lv_label_create(bubble); |
|||
lv_label_set_text(lbl_msg, msg); |
|||
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP); |
|||
lv_obj_set_width(lbl_msg, lv_pct(100)); |
|||
|
|||
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0); |
|||
lv_obj_set_style_text_font(lbl_msg, &lv_font_arial_26, 0); |
|||
|
|||
// Spacing between header and text
|
|||
//lv_obj_set_style_margin_top(lbl_msg, 6, 0);
|
|||
lv_obj_set_style_pad_row(bubble, 6, 0); |
|||
|
|||
chat_items[chat_count++] = row; |
|||
|
|||
lv_obj_scroll_to_view(row, LV_ANIM_ON); |
|||
} |
|||
|
|||
void UIManager::addPrivateChatBubble(const char *time_str, const char *msg, bool is_self) { |
|||
|
|||
lv_obj_set_style_pad_bottom(ui_ContactMessages, 20, 0); |
|||
|
|||
// 1. Row container
|
|||
lv_obj_t* row = LvObj(ui_ContactMessages) |
|||
.width(lv_pct(100)) |
|||
.height(LV_SIZE_CONTENT) |
|||
.bgOpa(0) |
|||
.border(0) |
|||
.padAll(4) |
|||
.scrollable(false) |
|||
.flexFlow(LV_FLEX_FLOW_ROW) |
|||
.flexAlign( |
|||
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START |
|||
); |
|||
|
|||
// 2. Aligner (Column) - label and time
|
|||
lv_obj_t* aligner = LvObj(row) |
|||
.width(LV_SIZE_CONTENT) |
|||
.height(LV_SIZE_CONTENT) |
|||
.bgOpa(0) |
|||
.border(0) |
|||
.padAll(0) |
|||
.scrollable(false) |
|||
.flexFlow(LV_FLEX_FLOW_ROW) |
|||
.flexAlign( |
|||
is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START, |
|||
LV_FLEX_ALIGN_START |
|||
); |
|||
lv_obj_set_style_pad_row(aligner, 4, 0); |
|||
|
|||
// 3. Το Label-Bubble (Ενοποιημένο)
|
|||
lv_obj_t *lbl_msg = lv_label_create(aligner); |
|||
|
|||
// Long mode για wrap
|
|||
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP); |
|||
lv_label_set_text(lbl_msg, msg); |
|||
lv_obj_set_style_text_font(lbl_msg, &lv_font_arial_22, 0); |
|||
|
|||
// Fixed max width για wrap
|
|||
lv_obj_set_width(lbl_msg, 400); // max πλάτος
|
|||
lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT); // αυτόματο ύψος
|
|||
|
|||
// Bubble style
|
|||
lv_obj_set_style_bg_opa(lbl_msg, LV_OPA_COVER, 0); |
|||
lv_obj_set_style_radius(lbl_msg, 12, 0); |
|||
lv_obj_set_style_pad_all(lbl_msg, 12, 0); |
|||
|
|||
if(is_self) { |
|||
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0x1E88E5), 0); |
|||
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0); |
|||
} else { |
|||
lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0xFFFFFF), 0); |
|||
lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0x000000), 0); |
|||
} |
|||
|
|||
// 4. Η Ώρα
|
|||
lv_obj_t *lbl_time = lv_label_create(aligner); |
|||
lv_label_set_text(lbl_time, time_str); |
|||
lv_obj_set_style_text_color(lbl_time, lv_color_hex(0x808080), 0); |
|||
lv_obj_set_style_text_font(lbl_time, &lv_font_arial_14, 0); |
|||
|
|||
lv_obj_scroll_to_view(row, LV_ANIM_ON); |
|||
} |
|||
|
|||
void UIManager::getInitials(const char *name, char *out) { |
|||
out[0] = 0; |
|||
if (!name || !name[0]) return; |
|||
|
|||
const char *p = name; |
|||
while (*p && !isalnum((unsigned char)*p)) { |
|||
p++; |
|||
} |
|||
|
|||
char first = (*p) ? *p : name[0]; |
|||
char second = 0; |
|||
const char *space = strchr(name, ' '); |
|||
|
|||
if (space) { |
|||
const char *s = space + 1; |
|||
while (*s && !isalnum((unsigned char)*s)) { |
|||
s++; |
|||
} |
|||
if (*s) { |
|||
second = *s; |
|||
} |
|||
} |
|||
|
|||
out[0] = toupper((unsigned char)first); |
|||
if (second) { |
|||
out[1] = toupper((unsigned char)second); |
|||
out[2] = 0; |
|||
} else { |
|||
out[1] = 0; |
|||
} |
|||
} |
|||
|
|||
void UIManager::formatLastSeen(uint32_t ts, char *out, size_t len) { |
|||
if (ts == 0) { |
|||
snprintf(out, len, "Never"); |
|||
return; |
|||
} |
|||
|
|||
time_t t = (time_t)ts; |
|||
struct tm *tm = localtime(&t); |
|||
|
|||
if (tm == nullptr) { |
|||
snprintf(out, len, "Unknown"); |
|||
return; |
|||
} |
|||
|
|||
snprintf(out, len, "%02d:%02d %02d/%02d/%02d", |
|||
tm->tm_hour, |
|||
tm->tm_min, |
|||
tm->tm_mday, |
|||
tm->tm_mon + 1, |
|||
(tm->tm_year + 1900) % 100); // Προσθέτουμε το 1900 και παίρνουμε τα τελευταία 2 ψηφία
|
|||
} |
|||
|
|||
static void onContactClick(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->handleContactClick(e); |
|||
} |
|||
|
|||
void UIManager::handleContactClick(lv_event_t *e) |
|||
{ |
|||
lv_obj_t *row = lv_event_get_target(e); |
|||
ContactInfo *c = (ContactInfo*) lv_obj_get_user_data(row); |
|||
|
|||
Serial.printf("Clicked: %s\n", c->name); |
|||
} |
|||
|
|||
void UIManager::addContactToUI(ContactInfo c) |
|||
{ |
|||
const int ROW_W = 200; |
|||
const int ROW_H = 64; |
|||
const int AVATAR = 44; |
|||
const int PAD = 4; |
|||
|
|||
// ============================
|
|||
// List row button
|
|||
// ============================
|
|||
lv_obj_t* btn = lv_list_add_btn(ui_Contacts, nullptr, nullptr); |
|||
|
|||
LvObj(btn, true) |
|||
.size(ROW_W, ROW_H) |
|||
.padAll(0) |
|||
.bgColor(0x000000) |
|||
.bgOpa(LV_OPA_COVER) |
|||
.noDecor() |
|||
.border(1) |
|||
.scrollable(false); |
|||
|
|||
lv_obj_set_layout(btn, 0); |
|||
lv_obj_set_style_border_side(btn, LV_BORDER_SIDE_BOTTOM, 0); |
|||
lv_obj_set_style_border_color(btn, lv_color_hex(0x222222), 0); |
|||
lv_obj_set_style_bg_color(btn, lv_color_hex(0x111111), LV_STATE_PRESSED); |
|||
|
|||
// Store ContactInfo
|
|||
auto* store = new ContactInfo(c); |
|||
lv_obj_set_user_data(btn, store); |
|||
|
|||
lv_obj_add_event_cb(btn, onContactClick, LV_EVENT_CLICKED, this); |
|||
|
|||
// ============================
|
|||
// Avatar container (hidden when SHOW_CONTACT_AVATAR is false)
|
|||
// ============================
|
|||
#ifdef SHOW_CONTACT_AVATAR |
|||
lv_obj_t* content = LvObj(btn) |
|||
.size(ROW_H, ROW_H) |
|||
.position(0, 0) |
|||
.padAll(0) |
|||
.bgOpa(LV_OPA_TRANSP) |
|||
.border(0) |
|||
.scrollable(false) |
|||
.raw(); |
|||
|
|||
// Avatar circle
|
|||
lv_obj_t* avatar = LvObj(content) |
|||
.size(AVATAR, AVATAR) |
|||
.position(0, 10) |
|||
.radius(LV_RADIUS_CIRCLE) |
|||
.bgColor(0x4A90E2) |
|||
.border(0) |
|||
.raw(); |
|||
|
|||
// Initials
|
|||
char initials[4]; |
|||
if (c.type == ADV_TYPE_REPEATER) { |
|||
strcpy(initials, "(R)"); |
|||
} else { |
|||
getInitials(c.name, initials); |
|||
} |
|||
|
|||
LvLabel(avatar) |
|||
.text(initials) |
|||
.font(&lv_font_arial_22) |
|||
.textColor(0xFFFFFF) |
|||
.align(LV_ALIGN_CENTER); |
|||
#endif |
|||
|
|||
// ============================
|
|||
// Text column
|
|||
// ============================
|
|||
#ifdef SHOW_CONTACT_AVATAR |
|||
int text_x = PAD + AVATAR + PAD; |
|||
#else |
|||
int text_x = 0; |
|||
#endif |
|||
int text_w = ROW_W - text_x - PAD; |
|||
|
|||
lv_obj_t* text_col = LvObj(btn) |
|||
.position(text_x, 0) |
|||
.size(text_w, ROW_H - PAD) |
|||
.padAll(0) |
|||
.bgOpa(LV_OPA_TRANSP) |
|||
.border(0) |
|||
.scrollable(false) |
|||
.raw(); |
|||
|
|||
// Name label
|
|||
LvLabel(text_col) |
|||
.text(c.name) |
|||
.position(0, PAD + 4) |
|||
.width(text_w) |
|||
.font(&lv_font_arial_20) |
|||
.textColor(0xFFFFFF) |
|||
.wrap(false); |
|||
|
|||
// Last seen
|
|||
char lastSeen[32]; |
|||
formatLastSeen(c.lastmod, lastSeen, sizeof(lastSeen)); |
|||
|
|||
LvLabel(text_col) |
|||
.text(lastSeen) |
|||
.position(0, PAD + 32) |
|||
.width(text_w) |
|||
.font(&lv_font_arial_16) |
|||
.textColor(0x888888) |
|||
.wrap(false); |
|||
|
|||
// ============================
|
|||
// Disable child clicks
|
|||
// ============================
|
|||
#ifdef SHOW_CONTACT_AVATAR |
|||
LvObj(avatar, true).clickable(false); |
|||
#endif |
|||
LvObj(text_col, true).clickable(false); |
|||
} |
|||
|
|||
void UIManager::onShowKeyboard() |
|||
{ |
|||
LvKeyboard(ui_Keyboard, true).show(true); |
|||
LvObj(ui_DimOverlay, true).clickable(true); |
|||
LvObj(ui_ChannelInput, true).positionY(channelInputBaseKeybOnY); |
|||
LvObj(ui_SendBtn, true).positionY(channelInputBaseKeybOnY); |
|||
} |
|||
|
|||
void UIManager::onHideKeyboard() |
|||
{ |
|||
LvKeyboard(ui_Keyboard, true).show(false); |
|||
LvObj(ui_DimOverlay, true).clickable(false); |
|||
LvObj(ui_ChannelInput, true).positionY(channelInputBaseY); |
|||
LvObj(ui_SendBtn, true).positionY(channelInputBaseY); |
|||
} |
|||
|
|||
static void s_onChannelInputFocus(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->onChannelInputFocus(e); |
|||
} |
|||
|
|||
void UIManager::onChannelInputFocus(lv_event_t* e) |
|||
{ |
|||
lv_obj_t* ta = lv_event_get_target(e); |
|||
if(!ui_Keyboard || !ta) return; |
|||
|
|||
lv_keyboard_set_textarea(ui_Keyboard, ta); |
|||
onShowKeyboard(); |
|||
} |
|||
|
|||
static void s_onSendClick(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->onSendClick(e); |
|||
} |
|||
|
|||
void UIManager::onSendClick(lv_event_t* e) |
|||
{ |
|||
|
|||
char fullMessage[260]; |
|||
char msgCopy[200]; |
|||
|
|||
const char* msg = lv_textarea_get_text(ui_ChannelInput); |
|||
if(msg == NULL || msg[0] == '\0') return; |
|||
|
|||
strncpy(msgCopy, msg, sizeof(msgCopy) - 1); |
|||
msgCopy[sizeof(msgCopy) - 1] = '\0'; |
|||
|
|||
lv_textarea_set_text(ui_ChannelInput, ""); |
|||
|
|||
snprintf(fullMessage, sizeof(fullMessage), "public %s", msgCopy); |
|||
//handleCommand(fullMessage);
|
|||
handleCommand(msgCopy); |
|||
|
|||
char time_buf[16]; |
|||
time_t now = time(NULL); |
|||
struct tm t; |
|||
localtime_r(&now, &t); |
|||
|
|||
sprintf(time_buf, "%02d:%02d:%02d\n", t.tm_hour, t.tm_min, t.tm_sec); |
|||
addChatBubble(time_buf, "Me", msgCopy, true); |
|||
|
|||
onHideKeyboard(); |
|||
} |
|||
|
|||
static void s_onKeyboardEvent(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->onKeyboardEvent(e); |
|||
} |
|||
|
|||
void UIManager::onKeyboardEvent(lv_event_t* e) |
|||
{ |
|||
lv_event_code_t code = lv_event_get_code(e); |
|||
|
|||
if(code == LV_EVENT_READY || code == LV_EVENT_CANCEL) |
|||
{ |
|||
LvKeyboard(ui_Keyboard, true).show(false); |
|||
|
|||
LvObj(ui_ChannelInput, true).positionY(channelInputBaseY); |
|||
LvObj(ui_SendBtn, true).positionY(channelInputBaseY); |
|||
|
|||
LvObj(ui_DimOverlay, true) |
|||
.bgOpa(0) |
|||
.clickable(false); |
|||
} |
|||
} |
|||
|
|||
|
|||
static void s_onDimOverlayClick(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->onDimOverlayClick(e); |
|||
} |
|||
|
|||
void UIManager::onDimOverlayClick(lv_event_t* e) |
|||
{ |
|||
onHideKeyboard(); |
|||
} |
|||
|
|||
static void onScrollBeginEvent(lv_event_t *e) |
|||
{ |
|||
UIManager *self = (UIManager*) lv_event_get_user_data(e); |
|||
if(self) self->scroll_begin_event(e); |
|||
} |
|||
|
|||
void UIManager::scroll_begin_event(lv_event_t *e) |
|||
{ |
|||
if (lv_event_get_code(e) == LV_EVENT_SCROLL_BEGIN) { |
|||
lv_anim_t* a = (lv_anim_t*)lv_event_get_param(e); |
|||
if (a) a->time = 0; |
|||
|
|||
} |
|||
} |
|||
|
|||
void UIManager::ui_Screen1_screen_init(void) |
|||
{ |
|||
//lv_disp_set_rotation(disp, LV_DISP_ROT_90);
|
|||
//ui_Screen1 = lv_obj_create(NULL);
|
|||
|
|||
ui_Screen1 = LvObj(NULL) |
|||
.scrollable(false) |
|||
.bgColor(0x000000) |
|||
.bgOpa(255); |
|||
|
|||
LvTabView tabView(ui_Screen1); |
|||
tabView |
|||
.size(480, 480) |
|||
.align(LV_ALIGN_CENTER) |
|||
.bgColor(0x000000) |
|||
.contentNoScroll() |
|||
.tabBtnBg(0x424242) |
|||
.tabBtnText(0xFFFFFF, &lv_font_arial_18); |
|||
|
|||
ui_TabView1 = tabView.raw(); |
|||
|
|||
#if defined(LANG_EN) |
|||
ui_TabPageHome = tabView.addTab("Home"); |
|||
ui_TabPageContacts = tabView.addTab("Contacts"); |
|||
ui_TabPageChannels = tabView.addTab("Channels"); |
|||
ui_TabPageSettings = tabView.addTab("Settings"); |
|||
#elif defined(LANG_GR) |
|||
ui_TabPageHome = tabView.addTab("Αρχική"); |
|||
ui_TabPageContacts = tabView.addTab("Επαφές"); |
|||
ui_TabPageChannels = tabView.addTab("Κανάλια"); |
|||
ui_TabPageSettings = tabView.addTab("Ρυθμίσεις"); |
|||
#endif |
|||
|
|||
LvObj(ui_TabPageHome) |
|||
.scrollable(false) |
|||
.bgOpa(0) |
|||
.bgColor(0x000000); |
|||
LvObj(ui_TabPageContacts) |
|||
.scrollable(false) |
|||
.bgOpa(0) |
|||
.bgColor(0x000000); |
|||
LvObj(ui_TabPageChannels) |
|||
.scrollable(false) |
|||
.bgOpa(0) |
|||
.bgColor(0x000000); |
|||
LvObj(ui_TabPageSettings) |
|||
.scrollable(false) |
|||
.bgOpa(0) |
|||
.bgColor(0x000000); |
|||
|
|||
ui_ValueDate = LvLabel(ui_TabPageHome) |
|||
.text("--- --/--/----") |
|||
.width(LV_SIZE_CONTENT) |
|||
.height(LV_SIZE_CONTENT) |
|||
.font(&lv_font_arial_40) |
|||
.textColor(0xFFFFFF) |
|||
.opa(255) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(0, -165); |
|||
|
|||
|
|||
ui_ValueTime = LvLabel(ui_TabPageHome) |
|||
.text("--:--") |
|||
.width(LV_SIZE_CONTENT) |
|||
.height(LV_SIZE_CONTENT) |
|||
.font(&lv_font_arial_48) |
|||
.textColor(0xFFFFFF) |
|||
.opa(255) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(0, -100); |
|||
|
|||
ui_Contacts = LvList(ui_TabPageContacts) |
|||
.width(250) |
|||
.height(400) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(-274, 0) |
|||
.transparent() |
|||
.raw(); |
|||
|
|||
lv_obj_set_style_bg_opa(ui_Contacts, LV_OPA_TRANSP, 0); |
|||
lv_obj_set_style_border_width(ui_Contacts, 0, 0); |
|||
lv_obj_set_style_outline_width(ui_Contacts, 0, 0); |
|||
lv_obj_set_style_shadow_width(ui_Contacts, 0, 0); |
|||
//lv_obj_set_scrollbar_mode(ui_Contacts, LV_SCROLLBAR_MODE_OFF);
|
|||
lv_obj_set_style_bg_opa(ui_Contacts, LV_OPA_TRANSP, LV_PART_ITEMS); |
|||
lv_obj_set_style_border_width(ui_Contacts, 0, LV_PART_ITEMS); |
|||
|
|||
ui_ContactMessages = LvList(ui_TabPageContacts) |
|||
.width(500) |
|||
.height(400) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(124, 0) |
|||
.transparent() |
|||
.raw(); |
|||
|
|||
lv_obj_set_style_bg_color(ui_ContactMessages, lv_color_hex(0), 0); |
|||
lv_obj_set_style_bg_opa(ui_ContactMessages, LV_OPA_TRANSP, 0); |
|||
lv_obj_set_style_border_width(ui_ContactMessages, 0, 0); |
|||
lv_obj_set_style_outline_width(ui_ContactMessages, 0, 0); |
|||
lv_obj_set_style_shadow_width(ui_ContactMessages, 0, 0); |
|||
//lv_obj_set_scrollbar_mode(ui_ContactMessages, LV_SCROLLBAR_MODE_OFF);
|
|||
lv_obj_set_style_bg_opa(ui_ContactMessages, LV_OPA_TRANSP, LV_PART_ITEMS); |
|||
lv_obj_set_style_border_width(ui_ContactMessages, 0, LV_PART_ITEMS); |
|||
|
|||
// LvObj(ui_TabPageContacts)
|
|||
// .size(2, 400)
|
|||
// .position(222, 0)
|
|||
// .bgColor(0x444444)
|
|||
// .border(0)
|
|||
// .scrollable(false)
|
|||
// .radius(0);
|
|||
|
|||
ui_Channels = LvDropdown(ui_TabPageChannels) |
|||
.options("Public") |
|||
.width(291) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(-243, -182) |
|||
.clickable(true) |
|||
.raw(); |
|||
|
|||
ui_ChannelMessages = LvList(ui_TabPageChannels) |
|||
.width(780) |
|||
.height(280) |
|||
.align(LV_ALIGN_CENTER) |
|||
.transparent() |
|||
.padRow(10) |
|||
.position(0, 0) |
|||
.bgColor(0) |
|||
.bgOpa(0) |
|||
.border(0) |
|||
.noDecor() |
|||
.raw(); |
|||
|
|||
//lv_obj_set_scrollbar_mode(ui_ChannelMessages, LV_SCROLLBAR_MODE_OFF);
|
|||
lv_obj_set_style_bg_opa(ui_ChannelMessages, LV_OPA_TRANSP, LV_PART_ITEMS); |
|||
lv_obj_set_style_border_width(ui_ChannelMessages, 0, LV_PART_ITEMS); |
|||
|
|||
ui_ChannelDivider = LvObj(ui_TabPageChannels) |
|||
.size(780, 1) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(0, 150) |
|||
.bgColor(0x444444) |
|||
.border(0) |
|||
.raw(); |
|||
|
|||
ui_DimOverlay = LvObj(ui_Screen1) |
|||
.size(lv_pct(100), lv_pct(100)) |
|||
.align(LV_ALIGN_CENTER) |
|||
.bgColor(0x000000) |
|||
.bgOpa(0) |
|||
.bringToFront() |
|||
.onClick(s_onDimOverlayClick, this) |
|||
.scrollable(false) |
|||
.clickable(true) |
|||
.raw(); |
|||
lv_obj_remove_style_all(ui_DimOverlay); // no border/padding
|
|||
lv_obj_clear_flag(ui_DimOverlay, LV_OBJ_FLAG_SCROLL_CHAIN_HOR); |
|||
lv_obj_clear_flag(ui_DimOverlay, LV_OBJ_FLAG_SCROLL_CHAIN_VER); |
|||
|
|||
|
|||
ui_ChannelInput = LvTextArea(ui_TabPageChannels) |
|||
.size(670, 40) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(-50, channelInputBaseY) |
|||
.oneLine(true) |
|||
#if defined(LANG_EN) |
|||
.placeholder("Write message...") |
|||
#elif defined(LANG_GR) |
|||
.placeholder("Γράψε μήνυμα...") |
|||
#endif |
|||
.font(&lv_font_arial_20) |
|||
.bgColor(0x111111) |
|||
.textColor(0xFFFFFF) |
|||
.borderColor(0x444444) |
|||
.borderWidth(1) |
|||
.radius(6) |
|||
.onFocus(s_onChannelInputFocus, this) |
|||
.raw(); |
|||
|
|||
ui_SendBtn = LvButton(ui_TabPageChannels) |
|||
.size(90, 42) |
|||
.align(LV_ALIGN_CENTER) |
|||
.position(350, channelInputBaseY) |
|||
.bgColor(0x3A7AFE) |
|||
.onClick(s_onSendClick, this) |
|||
.raw(); |
|||
|
|||
iu_SendLabel = LvLabel(ui_SendBtn) |
|||
#if defined(LANG_EN) |
|||
.text("Send") |
|||
#elif defined(LANG_GR) |
|||
.text("Αποστολή") |
|||
#endif |
|||
.font(&lv_font_arial_18); |
|||
lv_obj_center(iu_SendLabel); |
|||
|
|||
ui_Keyboard = LvKeyboard(lv_layer_top()) |
|||
.size(480, 200) |
|||
.align(LV_ALIGN_BOTTOM_MID) |
|||
.show(false) |
|||
.onEvent(s_onKeyboardEvent, this); |
|||
} |
|||
|
|||
void UIManager::setNightMode(bool night) { |
|||
|
|||
if (!ui_DimOverlay) return; |
|||
if (night) { |
|||
lv_obj_set_style_bg_opa(ui_DimOverlay, 192, 0); // 75% dark
|
|||
} else { |
|||
lv_obj_set_style_bg_opa(ui_DimOverlay, 0, 0); // none
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,50 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiTasks.h" |
|||
#include "uiVars.h" |
|||
|
|||
// Tasks
|
|||
TaskHandle_t t_core0_lvgl; |
|||
TaskHandle_t t_core1_clock; |
|||
TaskHandle_t t_core1_core; |
|||
|
|||
#define TAG "createTasks" |
|||
|
|||
void createTasks() { |
|||
Serial.println("Creating Tasks..."); |
|||
|
|||
xTaskCreatePinnedToCore( |
|||
lvgl_task, // Task function.
|
|||
"LVGL_Manager", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
5, // Priority of the task
|
|||
&t_core0_lvgl, // Task handle to keep track of created task
|
|||
0); // Pin task to core 0
|
|||
|
|||
xTaskCreatePinnedToCore( |
|||
core_task, // Task function.
|
|||
"MeshCore", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
4, // Priority of the task
|
|||
&t_core1_core, // Task handle to keep track of created task
|
|||
1); // Pin task to core 1
|
|||
|
|||
xTaskCreatePinnedToCore( |
|||
clock_task, // Task function.
|
|||
"CLOCK_Manager", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
4, // Priority of the task
|
|||
&t_core1_clock, // Task handle to keep track of created task
|
|||
1); // Pin task to core 1
|
|||
|
|||
ESP_LOGD(TAG, "All tasks created\nStarting tasks..."); |
|||
|
|||
vTaskResume(t_core0_lvgl); |
|||
vTaskResume(t_core1_clock); |
|||
|
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,15 @@ |
|||
#pragma once |
|||
#include <stdint.h> |
|||
|
|||
// Append-only per-contact / public chat persistence on SPIFFS.
|
|||
// Each DM contact gets a file keyed by the first 4 bytes of the public key.
|
|||
// When a file exceeds MSGSTORE_MAX_BYTES it is dropped (oldest history lost).
|
|||
|
|||
#define MSGSTORE_MAX_BYTES 16384 |
|||
|
|||
void msgstore_append_dm(const uint8_t* pub_key, uint32_t ts, bool sent, const char* text); |
|||
void msgstore_append_public(uint32_t ts, const char* sender, bool sent, const char* text); |
|||
|
|||
// Replays stored history into the UI (calls uiManager->addPrivateChatBubble / addChatBubble).
|
|||
void msgstore_load_dm(const uint8_t* pub_key); |
|||
void msgstore_load_public(); |
|||
@ -0,0 +1,25 @@ |
|||
#include <Arduino.h> |
|||
#include <time.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
#define TAG "clock_task" |
|||
|
|||
void clock_task(void *pvParameters) { |
|||
|
|||
ESP_LOGI(TAG, "Clock manager: Task running on core %d", xPortGetCoreID()); |
|||
|
|||
uiManager->clearDateTime(); |
|||
|
|||
while (1) { |
|||
if (g_clock_synced) { |
|||
time_t now = time(nullptr); |
|||
struct tm t; |
|||
localtime_r(&now, &t); |
|||
uiManager->updateDateTime(t); |
|||
} |
|||
vTaskDelay(DELAY_CLOCK_TASK / portTICK_PERIOD_MS); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
#define TAG "lvgl_task" |
|||
|
|||
void lvgl_task(void *pvParameters) { |
|||
|
|||
vTaskSuspend(NULL); |
|||
|
|||
ESP_LOGI(TAG, "UI manager: Task running on core %d", xPortGetCoreID()); |
|||
|
|||
while (1) { |
|||
lv_timer_handler(); |
|||
vTaskDelay(DELAY_LVGL_TASK / portTICK_PERIOD_MS); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,50 @@ |
|||
#include <Arduino.h> |
|||
|
|||
#include "esp_log.h" |
|||
#include "uiDefines.h" |
|||
#include "uiTasks.h" |
|||
#include "uiVars.h" |
|||
|
|||
// Tasks
|
|||
TaskHandle_t t_core0_lvgl; |
|||
TaskHandle_t t_core1_clock; |
|||
TaskHandle_t t_core1_core; |
|||
|
|||
#define TAG "createTasks" |
|||
|
|||
void createTasks() { |
|||
Serial.println("Creating Tasks..."); |
|||
|
|||
xTaskCreatePinnedToCore( |
|||
lvgl_task, // Task function.
|
|||
"LVGL_Manager", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
5, // Priority of the task
|
|||
&t_core0_lvgl, // Task handle to keep track of created task
|
|||
0); // Pin task to core 0
|
|||
|
|||
xTaskCreatePinnedToCore( |
|||
core_task, // Task function.
|
|||
"MeshCore", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
4, // Priority of the task
|
|||
&t_core1_core, // Task handle to keep track of created task
|
|||
1); // Pin task to core 1
|
|||
|
|||
xTaskCreatePinnedToCore( |
|||
clock_task, // Task function.
|
|||
"CLOCK_Manager", // Name of task.
|
|||
10000, // Stack size of task
|
|||
NULL, // Parameter of the task
|
|||
4, // Priority of the task
|
|||
&t_core1_clock, // Task handle to keep track of created task
|
|||
1); // Pin task to core 1
|
|||
|
|||
ESP_LOGD(TAG, "All tasks created\nStarting tasks..."); |
|||
|
|||
vTaskResume(t_core0_lvgl); |
|||
vTaskResume(t_core1_clock); |
|||
|
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
#ifndef LGFX_h |
|||
#define LGFX_h |
|||
|
|||
#include <Arduino.h> |
|||
#include <LovyanGFX.hpp> |
|||
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp> |
|||
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp> |
|||
|
|||
#ifndef SEEED_SENSECAP_INDICATOR |
|||
class LGFX : public lgfx::LGFX_Device |
|||
{ |
|||
public: |
|||
|
|||
lgfx::Bus_RGB _bus_instance; |
|||
lgfx::Panel_RGB _panel_instance; |
|||
#if defined(ELECROW_DISPLAY_35) |
|||
LGFX(void) |
|||
{ |
|||
{ |
|||
auto cfg = _bus_instance.config(); |
|||
|
|||
cfg.port = 0; |
|||
cfg.freq_write = 80000000; |
|||
cfg.pin_wr = GPIO_NUM_18; |
|||
cfg.pin_rd = GPIO_NUM_48; |
|||
cfg.pin_rs = GPIO_NUM_45; |
|||
|
|||
cfg.pin_d0 = GPIO_NUM_47; |
|||
cfg.pin_d1 = GPIO_NUM_21; |
|||
cfg.pin_d2 = GPIO_NUM_14; |
|||
cfg.pin_d3 = GPIO_NUM_13; |
|||
cfg.pin_d4 = GPIO_NUM_12; |
|||
cfg.pin_d5 = GPIO_NUM_11; |
|||
cfg.pin_d6 = GPIO_NUM_10; |
|||
cfg.pin_d7 = GPIO_NUM_9; |
|||
cfg.pin_d8 = GPIO_NUM_3; |
|||
cfg.pin_d9 = GPIO_NUM_8; |
|||
cfg.pin_d10 = GPIO_NUM_16; |
|||
cfg.pin_d11 = GPIO_NUM_15; |
|||
cfg.pin_d12 = GPIO_NUM_7; |
|||
cfg.pin_d13 = GPIO_NUM_6; |
|||
cfg.pin_d14 = GPIO_NUM_5; |
|||
cfg.pin_d15 = GPIO_NUM_4; |
|||
|
|||
_bus_instance.config(cfg); |
|||
_panel_instance.setBus(&_bus_instance); |
|||
} |
|||
{ |
|||
auto cfg = _panel_instance.config(); |
|||
|
|||
cfg.pin_cs = -1; |
|||
cfg.pin_rst = -1; |
|||
cfg.pin_busy = -1; |
|||
|
|||
cfg.memory_width = 320; |
|||
cfg.memory_height = 480; |
|||
cfg.panel_width = 320; |
|||
cfg.panel_height = 480; |
|||
cfg.offset_x = 0; |
|||
cfg.offset_y = 0; |
|||
cfg.offset_rotation = 0; |
|||
cfg.dummy_read_pixel = 8; |
|||
cfg.dummy_read_bits = 1; |
|||
cfg.readable = true; |
|||
cfg.invert = false; |
|||
cfg.rgb_order = false; |
|||
cfg.dlen_16bit = true; |
|||
cfg.bus_shared = true; |
|||
|
|||
_panel_instance.config(cfg); |
|||
} |
|||
setPanel(&_panel_instance); |
|||
} |
|||
|
|||
#elif defined(ELECROW_DISPLAY_50) |
|||
LGFX(void) |
|||
{ |
|||
{ |
|||
auto cfg = _bus_instance.config(); |
|||
cfg.panel = &_panel_instance; |
|||
|
|||
cfg.pin_d0 = GPIO_NUM_8; // B0
|
|||
cfg.pin_d1 = GPIO_NUM_3; // B1
|
|||
cfg.pin_d2 = GPIO_NUM_46; // B2
|
|||
cfg.pin_d3 = GPIO_NUM_9; // B3
|
|||
cfg.pin_d4 = GPIO_NUM_1; // B4
|
|||
|
|||
cfg.pin_d5 = GPIO_NUM_5; // G0
|
|||
cfg.pin_d6 = GPIO_NUM_6; // G1
|
|||
cfg.pin_d7 = GPIO_NUM_7; // G2
|
|||
cfg.pin_d8 = GPIO_NUM_15; // G3
|
|||
cfg.pin_d9 = GPIO_NUM_16; // G4
|
|||
cfg.pin_d10 = GPIO_NUM_4; // G5
|
|||
|
|||
cfg.pin_d11 = GPIO_NUM_45; // R0
|
|||
cfg.pin_d12 = GPIO_NUM_48; // R1
|
|||
cfg.pin_d13 = GPIO_NUM_47; // R2
|
|||
cfg.pin_d14 = GPIO_NUM_21; // R3
|
|||
cfg.pin_d15 = GPIO_NUM_14; // R4
|
|||
|
|||
cfg.pin_henable = GPIO_NUM_40; |
|||
cfg.pin_vsync = GPIO_NUM_41; |
|||
cfg.pin_hsync = GPIO_NUM_39; |
|||
cfg.pin_pclk = GPIO_NUM_0; |
|||
cfg.freq_write = 15000000; |
|||
|
|||
cfg.hsync_polarity = 0; |
|||
cfg.hsync_front_porch = 8; |
|||
cfg.hsync_pulse_width = 4; |
|||
cfg.hsync_back_porch = 43; |
|||
|
|||
cfg.vsync_polarity = 0; |
|||
cfg.vsync_front_porch = 8; |
|||
cfg.vsync_pulse_width = 4; |
|||
cfg.vsync_back_porch = 12; |
|||
|
|||
cfg.pclk_active_neg = 1; |
|||
cfg.de_idle_high = 0; |
|||
cfg.pclk_idle_high = 0; |
|||
|
|||
_bus_instance.config(cfg); |
|||
} |
|||
{ |
|||
auto cfg = _panel_instance.config(); |
|||
cfg.memory_width = 800; |
|||
cfg.memory_height = 480; |
|||
cfg.panel_width = 800; |
|||
cfg.panel_height = 480; |
|||
cfg.offset_x = 0; |
|||
cfg.offset_y = 0; |
|||
_panel_instance.config(cfg); |
|||
} |
|||
_panel_instance.setBus(&_bus_instance); |
|||
setPanel(&_panel_instance); |
|||
} |
|||
#elif defined(ELECROW_DISPLAY_70) |
|||
// Constructor for the LGFX class.
|
|||
LGFX(void) { |
|||
// Configure the RGB bus.
|
|||
{ |
|||
auto cfg = _bus_instance.config(); |
|||
cfg.panel = &_panel_instance; |
|||
|
|||
// Configure data pins.
|
|||
cfg.pin_d0 = GPIO_NUM_15; // B0
|
|||
cfg.pin_d1 = GPIO_NUM_7; // B1
|
|||
cfg.pin_d2 = GPIO_NUM_6; // B2
|
|||
cfg.pin_d3 = GPIO_NUM_5; // B3
|
|||
cfg.pin_d4 = GPIO_NUM_4; // B4
|
|||
|
|||
cfg.pin_d5 = GPIO_NUM_9; // G0
|
|||
cfg.pin_d6 = GPIO_NUM_46; // G1
|
|||
cfg.pin_d7 = GPIO_NUM_3; // G2
|
|||
cfg.pin_d8 = GPIO_NUM_8; // G3
|
|||
cfg.pin_d9 = GPIO_NUM_16; // G4
|
|||
cfg.pin_d10 = GPIO_NUM_1; // G5
|
|||
|
|||
cfg.pin_d11 = GPIO_NUM_14; // R0
|
|||
cfg.pin_d12 = GPIO_NUM_21; // R1
|
|||
cfg.pin_d13 = GPIO_NUM_47; // R2
|
|||
cfg.pin_d14 = GPIO_NUM_48; // R3
|
|||
cfg.pin_d15 = GPIO_NUM_45; // R4
|
|||
|
|||
// Configure sync and clock pins.
|
|||
cfg.pin_henable = GPIO_NUM_41; |
|||
cfg.pin_vsync = GPIO_NUM_40; |
|||
cfg.pin_hsync = GPIO_NUM_39; |
|||
cfg.pin_pclk = GPIO_NUM_0; |
|||
cfg.freq_write = 15000000; |
|||
|
|||
// Configure timing parameters for horizontal and vertical sync.
|
|||
cfg.hsync_polarity = 0; |
|||
cfg.hsync_front_porch = 40; |
|||
cfg.hsync_pulse_width = 48; |
|||
cfg.hsync_back_porch = 40; |
|||
|
|||
cfg.vsync_polarity = 0; |
|||
cfg.vsync_front_porch = 1; |
|||
cfg.vsync_pulse_width = 31; |
|||
cfg.vsync_back_porch = 13; |
|||
|
|||
// Configure polarity for clock and data transmission.
|
|||
cfg.pclk_active_neg = 1; |
|||
cfg.de_idle_high = 0; |
|||
cfg.pclk_idle_high = 0; |
|||
|
|||
// Apply configuration to the RGB bus instance.
|
|||
_bus_instance.config(cfg); |
|||
} |
|||
|
|||
// Configure the panel.
|
|||
{ |
|||
auto cfg = _panel_instance.config(); |
|||
cfg.memory_width = 800; |
|||
cfg.memory_height = 480; |
|||
cfg.panel_width = 800; |
|||
cfg.panel_height = 480; |
|||
cfg.offset_x = 0; |
|||
cfg.offset_y = 0; |
|||
|
|||
// Apply configuration to the panel instance.
|
|||
_panel_instance.config(cfg); |
|||
} |
|||
|
|||
// Set the RGB bus and panel instances.
|
|||
_panel_instance.setBus(&_bus_instance); |
|||
setPanel(&_panel_instance); |
|||
} |
|||
#elif defined(SEEED_SENSECAP_INDICATOR) |
|||
|
|||
#else |
|||
#error "No Display size defined!" |
|||
#endif |
|||
|
|||
}; |
|||
#endif |
|||
#endif |
|||
@ -0,0 +1,107 @@ |
|||
#ifndef LV_BASE_h |
|||
#define LV_BASE_h |
|||
#include "lvgl.h" |
|||
|
|||
template<typename T> |
|||
class LvBase { |
|||
public: |
|||
operator lv_obj_t*() const { return obj; } |
|||
lv_obj_t* raw() const { return obj; } |
|||
|
|||
T& width(int v) { |
|||
lv_obj_set_width(obj, v == 100 ? lv_pct(100) : v); |
|||
return self(); |
|||
} |
|||
|
|||
T& height(int v) { |
|||
lv_obj_set_height(obj, v); |
|||
return self(); |
|||
} |
|||
|
|||
T& padAll(int v) { |
|||
lv_obj_set_style_pad_all(obj, v, 0); |
|||
return self(); |
|||
} |
|||
|
|||
T& bgOpa(int v) { |
|||
lv_obj_set_style_bg_opa(obj, v, 0); |
|||
return self(); |
|||
} |
|||
|
|||
T& border(int w) { |
|||
lv_obj_set_style_border_width(obj, w, 0); |
|||
return self(); |
|||
} |
|||
|
|||
T& scrollable(bool isScrollable) { |
|||
if (isScrollable) { |
|||
lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLLABLE); |
|||
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO); |
|||
} else { |
|||
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); |
|||
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_OFF); |
|||
} |
|||
return self(); |
|||
} |
|||
|
|||
T& position(lv_coord_t x, lv_coord_t y) { |
|||
lv_obj_set_x(obj, x); |
|||
lv_obj_set_y(obj, y); |
|||
return self(); |
|||
} |
|||
|
|||
T& positionY(lv_coord_t y) { |
|||
lv_obj_set_y(obj, y); |
|||
return self(); |
|||
} |
|||
|
|||
T& positionX(lv_coord_t x) { |
|||
lv_obj_set_x(obj, x); |
|||
return self(); |
|||
} |
|||
|
|||
T& size(int w, int h) { |
|||
lv_obj_set_size(obj, w, h); |
|||
return self(); |
|||
} |
|||
|
|||
T& align(lv_align_t align) { |
|||
lv_obj_set_align(obj, align); |
|||
return self(); |
|||
} |
|||
|
|||
T& bgColor(uint32_t hex) { |
|||
lv_obj_set_style_bg_color(obj, lv_color_hex(hex), 0); |
|||
return self(); |
|||
} |
|||
|
|||
T& noDecor() { |
|||
lv_obj_set_style_outline_width(obj, 0, 0); |
|||
lv_obj_set_style_shadow_width(obj, 0, 0); |
|||
return self(); |
|||
} |
|||
|
|||
T& clickable(bool isClickable) { |
|||
if (isClickable) { |
|||
lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE); |
|||
} else { |
|||
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); |
|||
} |
|||
return self(); |
|||
} |
|||
|
|||
T& onClick(lv_event_cb_t cb, void* user = nullptr) { |
|||
lv_obj_add_event_cb(obj, cb, LV_EVENT_CLICKED, user); |
|||
return self(); |
|||
} |
|||
|
|||
protected: |
|||
lv_obj_t* obj = nullptr; |
|||
|
|||
private: |
|||
T& self() { |
|||
return static_cast<T&>(*this); |
|||
} |
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,12 @@ |
|||
#ifndef LV_BUTTON_h |
|||
#define LV_BUTTON_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvButton : public LvBase<LvButton> { |
|||
public: |
|||
explicit LvButton(lv_obj_t* parent) { |
|||
obj = lv_btn_create(parent); |
|||
} |
|||
}; |
|||
#endif |
|||
@ -0,0 +1,23 @@ |
|||
#ifndef LV_DROP_DOWN_h |
|||
#define LV_DROP_DOWN_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvDropdown : public LvBase<LvDropdown> { |
|||
public: |
|||
explicit LvDropdown(lv_obj_t* parent) { |
|||
obj = lv_dropdown_create(parent); |
|||
} |
|||
|
|||
LvDropdown& options(const char* opts) { |
|||
lv_dropdown_set_options(obj, opts); |
|||
return *this; |
|||
} |
|||
|
|||
LvDropdown& selected(uint16_t idx) { |
|||
lv_dropdown_set_selected(obj, idx); |
|||
return *this; |
|||
} |
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,32 @@ |
|||
#ifndef LV_KEYBOARD_h |
|||
#define LV_KEYBOARD_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvKeyboard : public LvBase<LvKeyboard> { |
|||
public: |
|||
explicit LvKeyboard(lv_obj_t* parent) { |
|||
obj = lv_keyboard_create(parent); |
|||
} |
|||
|
|||
explicit LvKeyboard(lv_obj_t* existing, bool) { |
|||
obj = existing; |
|||
} |
|||
|
|||
LvKeyboard& target(lv_obj_t* ta) { |
|||
lv_keyboard_set_textarea(obj, ta); |
|||
return *this; |
|||
} |
|||
|
|||
LvKeyboard& show(bool v = true) { |
|||
v ? lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN) |
|||
: lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); |
|||
return *this; |
|||
} |
|||
|
|||
LvKeyboard& onEvent(lv_event_cb_t cb, void* user = nullptr) { |
|||
lv_obj_add_event_cb(obj, cb, LV_EVENT_ALL, user); |
|||
return *this; |
|||
} |
|||
}; |
|||
#endif |
|||
@ -0,0 +1,41 @@ |
|||
#ifndef LV_LABEL_h |
|||
#define LV_LABEL_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvLabel : public LvBase<LvLabel> { |
|||
public: |
|||
explicit LvLabel(lv_obj_t* parent) { |
|||
obj = lv_label_create(parent); |
|||
} |
|||
|
|||
LvLabel& text(const char* t) { |
|||
lv_label_set_text(obj, t); |
|||
return *this; |
|||
} |
|||
|
|||
LvLabel& font(const lv_font_t* f) { |
|||
lv_obj_set_style_text_font(obj, f, 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvLabel& textColor(uint32_t hex) { |
|||
lv_obj_set_style_text_color(obj, lv_color_hex(hex), 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvLabel& opa(int v) { |
|||
lv_obj_set_style_text_opa(obj, v, 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvLabel& wrap(bool v = true) { |
|||
lv_label_set_long_mode( |
|||
obj, |
|||
v ? LV_LABEL_LONG_WRAP : LV_LABEL_LONG_CLIP |
|||
); |
|||
return *this; |
|||
} |
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,28 @@ |
|||
#ifndef LV_LIST_h |
|||
#define LV_LIST_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvList : public LvBase<LvList> { |
|||
public: |
|||
explicit LvList(lv_obj_t* parent) { |
|||
obj = lv_list_create(parent); |
|||
} |
|||
|
|||
LvList& transparent() { |
|||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0); |
|||
lv_obj_set_style_border_width(obj, 0, 0); |
|||
|
|||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, LV_PART_ITEMS); |
|||
lv_obj_set_style_border_width(obj, 0, LV_PART_ITEMS); |
|||
|
|||
return *this; |
|||
} |
|||
|
|||
LvList& padRow(int v) { |
|||
lv_obj_set_style_pad_row(obj, v, 0); |
|||
return *this; |
|||
} |
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,42 @@ |
|||
#ifndef LV_OBJ_h |
|||
#define LV_OBJ_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvObj : public LvBase<LvObj> { |
|||
public: |
|||
explicit LvObj(lv_obj_t* parent) { |
|||
obj = lv_obj_create(parent); |
|||
} |
|||
|
|||
explicit LvObj(lv_obj_t* existing, bool) { |
|||
obj = existing; |
|||
} |
|||
|
|||
LvObj& flexFlow(lv_flex_flow_t flow) { |
|||
lv_obj_set_flex_flow(obj, flow); |
|||
return *this; |
|||
} |
|||
|
|||
LvObj& flexAlign( |
|||
lv_flex_align_t main, |
|||
lv_flex_align_t cross, |
|||
lv_flex_align_t track = LV_FLEX_ALIGN_START |
|||
) { |
|||
lv_obj_set_flex_align(obj, main, cross, track); |
|||
return *this; |
|||
} |
|||
|
|||
LvObj& bringToFront() { |
|||
lv_obj_move_foreground(obj); |
|||
return *this; |
|||
} |
|||
|
|||
LvObj& radius(int r) { |
|||
lv_obj_set_style_radius(obj, r, 0); |
|||
return *this; |
|||
} |
|||
|
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,51 @@ |
|||
#ifndef LV_TAB_VIEW_h |
|||
#define LV_TAB_VIEW_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvTabView : public LvBase<LvTabView> { |
|||
public: |
|||
explicit LvTabView(lv_obj_t* parent, |
|||
lv_dir_t dir = LV_DIR_TOP, |
|||
int tab_h = 50) { |
|||
obj = lv_tabview_create(parent, dir, tab_h); |
|||
} |
|||
|
|||
lv_obj_t* addTab(const char* name) { |
|||
return lv_tabview_add_tab(obj, name); |
|||
} |
|||
|
|||
LvTabView& contentNoScroll() { |
|||
lv_obj_clear_flag( |
|||
lv_tabview_get_content(obj), |
|||
LV_OBJ_FLAG_SCROLLABLE |
|||
); |
|||
return *this; |
|||
} |
|||
|
|||
LvTabView& tabBtnBg(uint32_t hex) { |
|||
lv_obj_set_style_bg_color( |
|||
lv_tabview_get_tab_btns(obj), |
|||
lv_color_hex(hex), |
|||
0 |
|||
); |
|||
return *this; |
|||
} |
|||
|
|||
LvTabView& tabBtnText(uint32_t hex, const lv_font_t* f = nullptr) { |
|||
lv_obj_set_style_text_color( |
|||
lv_tabview_get_tab_btns(obj), |
|||
lv_color_hex(hex), |
|||
LV_PART_ITEMS |
|||
); |
|||
if (f) { |
|||
lv_obj_set_style_text_font( |
|||
lv_tabview_get_tab_btns(obj), |
|||
f, |
|||
LV_PART_ITEMS |
|||
); |
|||
} |
|||
return *this; |
|||
} |
|||
}; |
|||
#endif |
|||
@ -0,0 +1,52 @@ |
|||
#ifndef LV_TEXT_AREA_h |
|||
#define LV_TEXT_AREA_h |
|||
|
|||
#include "lvBase.h" |
|||
|
|||
class LvTextArea : public LvBase<LvTextArea> { |
|||
public: |
|||
explicit LvTextArea(lv_obj_t* parent) { |
|||
obj = lv_textarea_create(parent); |
|||
} |
|||
|
|||
LvTextArea& oneLine(bool v = true) { |
|||
lv_textarea_set_one_line(obj, v); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& placeholder(const char* txt) { |
|||
lv_textarea_set_placeholder_text(obj, txt); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& font(const lv_font_t* f) { |
|||
lv_obj_set_style_text_font(obj, f, 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& textColor(uint32_t hex) { |
|||
lv_obj_set_style_text_color(obj, lv_color_hex(hex), 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& borderColor(uint32_t hex) { |
|||
lv_obj_set_style_border_color(obj, lv_color_hex(hex), 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& borderWidth(int w) { |
|||
lv_obj_set_style_border_width(obj, w, 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& radius(int r) { |
|||
lv_obj_set_style_radius(obj, r, 0); |
|||
return *this; |
|||
} |
|||
|
|||
LvTextArea& onFocus(lv_event_cb_t cb, void* user = nullptr) { |
|||
lv_obj_add_event_cb(obj, cb, LV_EVENT_CLICKED, user); |
|||
return *this; |
|||
} |
|||
}; |
|||
#endif |
|||
@ -0,0 +1,61 @@ |
|||
#ifndef DEFINES_h |
|||
#define DEFINES_h |
|||
|
|||
|
|||
#define BUTTONS_ON_SCREEN 7 |
|||
|
|||
#define DELAY_CORE_TASK 5 |
|||
#define DELAY_LVGL_TASK 10 |
|||
#define DELAY_MAIN_TASK 500 |
|||
#define DELAY_CLOCK_TASK 1000 |
|||
#define DELAY_NTP_TASK 30000 |
|||
#define DELAY_WIFI_RECONNECT_TASK 5000 |
|||
// every 5 minutes on success
|
|||
#define DELAY_OPEN_WEATHER_TASK 300000 |
|||
// every 1 minute on failure
|
|||
#define DELAY_OPEN_WEATHER_SHORT_TASK 60000 |
|||
|
|||
#define STATE_OFF HIGH |
|||
#define STATE_ON LOW |
|||
|
|||
#define MAX_CHAT_MESSAGES 50 |
|||
|
|||
// Colors
|
|||
#define COLOR_WHITE 0xFFFFFF |
|||
#define COLOR_BLACK 0 |
|||
#define COLOR_RED 0xFF0000 |
|||
#define COLOR_BLUE 0x0077FF |
|||
#define COLOR_BLUE_LIGHT 0x22A8FD |
|||
#define COLOR_GREY 0x8F8F8F |
|||
#define BUTTON_BACKGROUND 0x1B1B1B |
|||
|
|||
// Device Elecrow 5 & 7 inches
|
|||
#define SD_MOSI 11 |
|||
#define SD_MISO 13 |
|||
#define SD_SCK 12 |
|||
#define SD_CS 10 |
|||
|
|||
#define I2S_DOUT 17 |
|||
#define I2S_BCLK 42 |
|||
#define I2S_LRC 18 |
|||
|
|||
#define GPIO_D 38 |
|||
|
|||
#define UART_RX 44 |
|||
#define UART_TX 43 |
|||
#define I2C_SDA 19 |
|||
#define I2C_SCL 20 |
|||
#define LCD_Backlight 2 |
|||
|
|||
// Contact list: show avatar circle with initials (disable on small screens)
|
|||
//#define SHOW_CONTACT_AVATAR
|
|||
|
|||
// Other defines
|
|||
#define DELAY_MAIN_TASK 10 |
|||
#define DELAY_REFRESH_VIEW 10 |
|||
#define OLED_RESET -1 |
|||
|
|||
#define TFT_BL 2 |
|||
#define LGFX_USE_V1 |
|||
|
|||
#endif |
|||
@ -0,0 +1,11 @@ |
|||
#ifndef EXTERNALS_h |
|||
#define EXTERNALS_h |
|||
|
|||
#include <Arduino.h> |
|||
|
|||
extern void createTasks(); |
|||
extern TaskHandle_t t_core0_lvgl; |
|||
extern TaskHandle_t t_core1_clock; |
|||
extern TaskHandle_t t_core1_core; |
|||
|
|||
#endif |
|||
@ -0,0 +1,145 @@ |
|||
#ifndef UI_MANAGER_h |
|||
#define UI_MANAGER_h |
|||
|
|||
#include <Arduino.h> |
|||
#include "uiDefines.h" |
|||
|
|||
#include "lvButton.h" |
|||
#include "lvDropDown.h" |
|||
#include "lvKeyboard.h" |
|||
#include "lvLabel.h" |
|||
#include "lvList.h" |
|||
#include "lvObj.h" |
|||
#include "lvTabView.h" |
|||
#include "lvTextArea.h" |
|||
|
|||
#include <helpers/ContactInfo.h> |
|||
|
|||
class UIManager { |
|||
private: |
|||
// functions
|
|||
void format_datetime(char *buf, size_t size, const struct tm *timeinfo); |
|||
void timestampToTime(time_t timestamp, char *buffer, size_t buffer_size); |
|||
const char* convertDegreesToDirection(int degrees); |
|||
int windSpeedToBeaufort(float speed); |
|||
void getInitials(const char *name, char *out); |
|||
void formatLastSeen(uint32_t ts, char *out, size_t len); |
|||
void format_time(uint32_t ts, char *buf, size_t len); |
|||
|
|||
// Calendar days and months
|
|||
static const char *days[7]; |
|||
static const char *months[12]; |
|||
|
|||
// vars
|
|||
char time_str[9]; |
|||
String lastUpdate = ""; |
|||
char* tmp_buf; |
|||
|
|||
lv_obj_t *chat_items[MAX_CHAT_MESSAGES]; |
|||
int chat_count = 0; |
|||
int channelInputBaseY = 185; |
|||
int channelInputBaseKeybOnY = -15; |
|||
void onShowKeyboard(); |
|||
void onHideKeyboard(); |
|||
void ui_Screen1_screen_init(void); |
|||
|
|||
lv_obj_t* ui_Screen1; |
|||
lv_obj_t* ui_TabView1; |
|||
lv_obj_t* ui_TabPageContacts; |
|||
lv_obj_t* ui_Contacts; |
|||
lv_obj_t* ui_ContactMessages; |
|||
lv_obj_t* ui_TabPageChannels; |
|||
lv_obj_t* ui_Channels; |
|||
lv_obj_t* ui_ChannelMessages; |
|||
lv_obj_t* ui_AutoLight; |
|||
lv_obj_t* ui____initial_actions0; |
|||
|
|||
lv_obj_t* ui_DimOverlay; |
|||
lv_obj_t* ui_TabPageHome; |
|||
lv_obj_t* ui_ValueDate; |
|||
lv_obj_t* ui_ValueTime; |
|||
lv_obj_t* ui_TabPageSettings; |
|||
lv_obj_t* ui_DayLight; |
|||
lv_obj_t* ui_ChannelInput; |
|||
lv_obj_t* ui_SendBtn; |
|||
lv_obj_t* ui_Keyboard; |
|||
lv_obj_t* iu_SendLabel; |
|||
lv_obj_t* ui_ChannelDivider; |
|||
|
|||
lv_obj_t* ui_ContactName = nullptr; |
|||
lv_obj_t* ui_ContactInput = nullptr; |
|||
lv_obj_t* ui_ContactSendBtn = nullptr; |
|||
lv_obj_t* ui_ContactSendLabel = nullptr; |
|||
char currentContactName[32] = {0}; |
|||
uint8_t currentContactPubKey[32] = {0}; |
|||
bool hasCurrentContact = false; |
|||
bool night_mode_active = false; |
|||
char myNodeName[32] = {0}; |
|||
|
|||
lv_obj_t* ui_ContactStatus = nullptr; |
|||
|
|||
lv_obj_t* ui_HomeNodeName = nullptr; |
|||
lv_obj_t* ui_HomePubKey = nullptr; |
|||
lv_obj_t* ui_HomeInfo = nullptr; |
|||
lv_obj_t* ui_AdvertiseBtn = nullptr; |
|||
|
|||
lv_obj_t* ui_SettingsName = nullptr; |
|||
lv_obj_t* ui_SettingsPreset = nullptr; |
|||
lv_obj_t* ui_SettingsFreq = nullptr; |
|||
lv_obj_t* ui_SettingsBw = nullptr; |
|||
lv_obj_t* ui_SettingsSf = nullptr; |
|||
lv_obj_t* ui_SettingsCr = nullptr; |
|||
lv_obj_t* ui_SettingsTx = nullptr; |
|||
lv_obj_t* ui_SettingsFw = nullptr; |
|||
lv_obj_t* ui_SettingsSaveBtn = nullptr; |
|||
lv_obj_t* ui_SettingsStatus = nullptr; |
|||
|
|||
// Whichever input/send button last got focus — used by keyboard show/hide
|
|||
lv_obj_t* activeInput = nullptr; |
|||
lv_obj_t* activeSendBtn = nullptr; |
|||
int activeInputBaseY = 0; |
|||
|
|||
public: |
|||
UIManager(); |
|||
|
|||
void onChannelInputFocus(lv_event_t* e); |
|||
void onContactInputFocus(lv_event_t* e); |
|||
void onContactSendClick(lv_event_t* e); |
|||
void onSettingsInputFocus(lv_event_t* e); |
|||
void onSettingsSaveClick(lv_event_t* e); |
|||
void onAdvertiseClick(lv_event_t* e); |
|||
void onDimOverlayClick(lv_event_t* e); |
|||
void onSendClick(lv_event_t* e); |
|||
void onKeyboardEvent(lv_event_t* e); |
|||
void scroll_begin_event(lv_event_t* e); |
|||
|
|||
void onPresetChange(uint16_t idx); |
|||
void populateSettings(const char* name, float freq, float bw, uint8_t sf, uint8_t cr, |
|||
uint8_t tx_power, const char* fw_ver, const char* build_date); |
|||
void populateHome(const char* name, const char* pub_key_hex, |
|||
int contact_count, float freq); |
|||
void setMyNodeName(const char* name); |
|||
|
|||
// Phase 6
|
|||
void routeIncomingDM(const uint8_t* from_pub_key, const char* from_name, |
|||
const char* time_str, const char* text); |
|||
void setSendStatus(int state); // 0=sending, 1=delivered, 2=failed, -1=clear
|
|||
|
|||
void updateDateTime(const struct tm timeinfo); |
|||
void updateInfo(const char *str, uint32_t color); |
|||
void clearDateTime(); |
|||
void updateValues(); |
|||
void addPrivateChatBubble(const char *time_str, const char *msg, bool is_self, bool do_scroll = true); |
|||
void addChatBubble(const char *time_str, const char *sender, const char *msg, bool is_self, bool do_scroll = true); |
|||
void scrollPrivateChatToBottom(); |
|||
void scrollPublicChatToBottom(); |
|||
void beginPublicHistoryLoad(); // disable flex before bulk load
|
|||
void endPublicHistoryLoad(); // re-enable flex after bulk load
|
|||
void addContactToUI(ContactInfo c); |
|||
void updateContactLastSeen(const uint8_t* pub_key, uint32_t lastmod); |
|||
void refreshLastSeenLabels(); |
|||
void handleContactClick(lv_event_t *e); |
|||
void setNightMode(bool night); |
|||
}; |
|||
|
|||
#endif |
|||
@ -0,0 +1,12 @@ |
|||
#ifndef TASKS_h |
|||
#define TASKS_h |
|||
|
|||
#include <Arduino.h> |
|||
#include "uiDefines.h" |
|||
#include "uiVars.h" |
|||
|
|||
void lvgl_task(void *pvParameters); |
|||
void clock_task(void *pvParameters); |
|||
void core_task(void *pvParameters); |
|||
|
|||
#endif |
|||
@ -0,0 +1,220 @@ |
|||
/*******************************************************************************
|
|||
* Touch libraries: |
|||
* FT6X36: https://github.com/strange-v/FT6X36.git
|
|||
* GT911: https://github.com/TAMCTec/gt911-arduino.git
|
|||
* XPT2046: https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
|
|||
******************************************************************************/ |
|||
|
|||
/* uncomment for FT6X36 */ |
|||
// #define TOUCH_FT6X36
|
|||
// #define TOUCH_FT6X36_SCL 38//19
|
|||
// #define TOUCH_FT6X36_SDA 37//18
|
|||
// #define TOUCH_FT6X36_INT 4//39
|
|||
// #define TOUCH_SWAP_XY
|
|||
// #define TOUCH_MAP_X1 800
|
|||
// #define TOUCH_MAP_X2 0
|
|||
// #define TOUCH_MAP_Y1 0
|
|||
// #define TOUCH_MAP_Y2 480
|
|||
|
|||
/* uncomment for GT911 */ |
|||
// #define TOUCH_GT911
|
|||
// #define TOUCH_GT911_SCL 20//20
|
|||
// #define TOUCH_GT911_SDA 19//19
|
|||
// #define TOUCH_GT911_INT -1//-1
|
|||
// #define TOUCH_GT911_RST -1//38
|
|||
// #define TOUCH_GT911_ROTATION ROTATION_NORMAL
|
|||
// #define TOUCH_MAP_X1 800//480
|
|||
// #define TOUCH_MAP_X2 0
|
|||
// #define TOUCH_MAP_Y1 480//272
|
|||
// #define TOUCH_MAP_Y2 0
|
|||
|
|||
/* uncomment for XPT2046 */ |
|||
// #define TOUCH_XPT2046
|
|||
// #define TOUCH_XPT2046_SCK 12
|
|||
// #define TOUCH_XPT2046_MISO 13
|
|||
// #define TOUCH_XPT2046_MOSI 11
|
|||
// #define TOUCH_XPT2046_CS 38
|
|||
// #define TOUCH_XPT2046_INT 36
|
|||
// #define TOUCH_XPT2046_ROTATION 0
|
|||
// #define TOUCH_MAP_X1 4000
|
|||
// #define TOUCH_MAP_X2 100
|
|||
// #define TOUCH_MAP_Y1 100
|
|||
// #define TOUCH_MAP_Y2 4000
|
|||
|
|||
LGFX lcd; |
|||
|
|||
int touch_last_x = 0, touch_last_y = 0; |
|||
|
|||
#if defined(TOUCH_FT6X36) |
|||
#include <Wire.h> |
|||
#include <FT6X36.h> |
|||
FT6X36 ts(&Wire, TOUCH_FT6X36_INT); |
|||
bool touch_touched_flag = true, touch_released_flag = true; |
|||
|
|||
#elif defined(TOUCH_GT911) |
|||
#include <Wire.h> |
|||
#include <TAMC_GT911.h> |
|||
TAMC_GT911 ts = TAMC_GT911(TOUCH_GT911_SDA, TOUCH_GT911_SCL, TOUCH_GT911_INT, TOUCH_GT911_RST, max(TOUCH_MAP_X1, TOUCH_MAP_X2), max(TOUCH_MAP_Y1, TOUCH_MAP_Y2)); |
|||
|
|||
#elif defined(TOUCH_XPT2046) |
|||
#include <XPT2046_Touchscreen.h> |
|||
#include <SPI.h> |
|||
XPT2046_Touchscreen ts(TOUCH_XPT2046_CS, TOUCH_XPT2046_INT); |
|||
|
|||
#endif |
|||
|
|||
#if defined(TOUCH_FT6X36) |
|||
void touch(TPoint p, TEvent e) |
|||
{ |
|||
if (e != TEvent::Tap && e != TEvent::DragStart && e != TEvent::DragMove && e != TEvent::DragEnd) |
|||
{ |
|||
return; |
|||
} |
|||
// translation logic depends on screen rotation
|
|||
#if defined(TOUCH_SWAP_XY) |
|||
touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width()); |
|||
touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height()); |
|||
#else |
|||
touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width()); |
|||
touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height()); |
|||
#endif |
|||
switch (e) |
|||
{ |
|||
case TEvent::Tap: |
|||
Serial.println("Tap"); |
|||
touch_touched_flag = true; |
|||
touch_released_flag = true; |
|||
break; |
|||
case TEvent::DragStart: |
|||
Serial.println("DragStart"); |
|||
touch_touched_flag = true; |
|||
break; |
|||
case TEvent::DragMove: |
|||
Serial.println("DragMove"); |
|||
touch_touched_flag = true; |
|||
break; |
|||
case TEvent::DragEnd: |
|||
Serial.println("DragEnd"); |
|||
touch_released_flag = true; |
|||
break; |
|||
default: |
|||
Serial.println("UNKNOWN"); |
|||
break; |
|||
} |
|||
} |
|||
#endif |
|||
|
|||
void touch_init() |
|||
{ |
|||
#if defined(TOUCH_FT6X36) |
|||
Wire.begin(TOUCH_FT6X36_SDA, TOUCH_FT6X36_SCL); |
|||
ts.begin(); |
|||
ts.registerTouchHandler(touch); |
|||
|
|||
#elif defined(TOUCH_GT911) |
|||
Wire.begin(TOUCH_GT911_SDA, TOUCH_GT911_SCL); |
|||
ts.begin(); |
|||
ts.setRotation(TOUCH_GT911_ROTATION); |
|||
|
|||
#elif defined(TOUCH_XPT2046) |
|||
SPI.begin(TOUCH_XPT2046_SCK, TOUCH_XPT2046_MISO, TOUCH_XPT2046_MOSI, TOUCH_XPT2046_CS); |
|||
ts.begin(); |
|||
ts.setRotation(TOUCH_XPT2046_ROTATION); |
|||
|
|||
#endif |
|||
} |
|||
|
|||
bool touch_has_signal() |
|||
{ |
|||
#if defined(TOUCH_FT6X36) |
|||
ts.loop(); |
|||
return touch_touched_flag || touch_released_flag; |
|||
|
|||
#elif defined(TOUCH_GT911) |
|||
return true; |
|||
|
|||
#elif defined(TOUCH_XPT2046) |
|||
return ts.tirqTouched(); |
|||
|
|||
#else |
|||
return false; |
|||
#endif |
|||
} |
|||
|
|||
bool touch_touched() |
|||
{ |
|||
#if defined(TOUCH_FT6X36) |
|||
if (touch_touched_flag) |
|||
{ |
|||
touch_touched_flag = false; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#elif defined(TOUCH_GT911) |
|||
ts.read(); |
|||
if (ts.isTouched) |
|||
{ |
|||
#if defined(TOUCH_SWAP_XY) |
|||
touch_last_x = map(ts.points[0].y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width() - 1); |
|||
touch_last_y = map(ts.points[0].x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height() - 1); |
|||
#else |
|||
touch_last_x = map(ts.points[0].x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width() - 1); |
|||
touch_last_y = map(ts.points[0].y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height() - 1); |
|||
#endif |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#elif defined(TOUCH_XPT2046) |
|||
if (ts.touched()) |
|||
{ |
|||
TS_Point p = ts.getPoint(); |
|||
#if defined(TOUCH_SWAP_XY) |
|||
touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width() - 1); |
|||
touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height() - 1); |
|||
#else |
|||
touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, lcd.width() - 1); |
|||
touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, lcd.height() - 1); |
|||
#endif |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#else |
|||
return false; |
|||
#endif |
|||
} |
|||
|
|||
bool touch_released() |
|||
{ |
|||
#if defined(TOUCH_FT6X36) |
|||
if (touch_released_flag) |
|||
{ |
|||
touch_released_flag = false; |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#elif defined(TOUCH_GT911) |
|||
return true; |
|||
|
|||
#elif defined(TOUCH_XPT2046) |
|||
return true; |
|||
|
|||
#else |
|||
return false; |
|||
#endif |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
#ifndef VARS_h |
|||
#define VARS_h |
|||
|
|||
#include <Arduino.h> |
|||
#include "lvgl.h" |
|||
#include "uiManager.h" |
|||
|
|||
extern void createTasks(); |
|||
|
|||
extern TaskHandle_t t_core1_tft; |
|||
extern TaskHandle_t t_core0_lvgl; |
|||
extern TaskHandle_t t_core1_clock; |
|||
extern TaskHandle_t t_core1_core; |
|||
|
|||
extern SemaphoreHandle_t semaphoreData; |
|||
|
|||
extern UIManager *uiManager; |
|||
extern lv_obj_t * ui_MainTabView; |
|||
|
|||
extern volatile bool g_clock_synced; |
|||
|
|||
#endif |
|||
@ -0,0 +1,787 @@ |
|||
/**
|
|||
* @file lv_conf.h |
|||
* Configuration file for v8.3.11 |
|||
*/ |
|||
|
|||
/*
|
|||
* Copy this file as `lv_conf.h` |
|||
* 1. simply next to the `lvgl` folder |
|||
* 2. or any other places and |
|||
* - define `LV_CONF_INCLUDE_SIMPLE` |
|||
* - add the path as include path |
|||
*/ |
|||
|
|||
/* clang-format off */ |
|||
#if 1 /*Set it to "1" to enable content*/ |
|||
|
|||
#ifndef LV_CONF_H |
|||
#define LV_CONF_H |
|||
|
|||
#include <stdint.h> |
|||
|
|||
/*====================
|
|||
COLOR SETTINGS |
|||
*====================*/ |
|||
|
|||
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/ |
|||
#define LV_COLOR_DEPTH 16 |
|||
|
|||
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/ |
|||
#define LV_COLOR_16_SWAP 0 |
|||
|
|||
/*Enable features to draw on transparent background.
|
|||
*It's required if opa, and transform_* style properties are used. |
|||
*Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*/ |
|||
#define LV_COLOR_SCREEN_TRANSP 0 |
|||
|
|||
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
|
|||
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */ |
|||
#define LV_COLOR_MIX_ROUND_OFS 0 |
|||
|
|||
/*Images pixels with this color will not be drawn if they are chroma keyed)*/ |
|||
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/ |
|||
|
|||
/*=========================
|
|||
MEMORY SETTINGS |
|||
*=========================*/ |
|||
|
|||
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/ |
|||
/* ESP32-S3 has 8 MB OPI PSRAM. Switching to LV_MEM_CUSTOM=1 lets LVGL use the
|
|||
system heap (which includes PSRAM on this board) instead of a fixed 48 KB |
|||
static pool. This prevents OOM crashes when loading many chat bubbles. */ |
|||
#define LV_MEM_CUSTOM 1 |
|||
#if LV_MEM_CUSTOM == 0 |
|||
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/ |
|||
#define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/ |
|||
|
|||
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/ |
|||
#define LV_MEM_ADR 0 /*0: unused*/ |
|||
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/ |
|||
#if LV_MEM_ADR == 0 |
|||
#undef LV_MEM_POOL_INCLUDE |
|||
#undef LV_MEM_POOL_ALLOC |
|||
#endif |
|||
|
|||
#else /*LV_MEM_CUSTOM*/ |
|||
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/ |
|||
#define LV_MEM_CUSTOM_ALLOC malloc |
|||
#define LV_MEM_CUSTOM_FREE free |
|||
#define LV_MEM_CUSTOM_REALLOC realloc |
|||
#endif /*LV_MEM_CUSTOM*/ |
|||
|
|||
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
|
|||
*You will see an error log message if there wasn't enough buffers. */ |
|||
#define LV_MEM_BUF_MAX_NUM 16 |
|||
|
|||
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/ |
|||
#define LV_MEMCPY_MEMSET_STD 0 |
|||
|
|||
/*====================
|
|||
HAL SETTINGS |
|||
*====================*/ |
|||
|
|||
/*Default display refresh period. LVG will redraw changed areas with this period time*/ |
|||
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/ |
|||
|
|||
/*Input device read period in milliseconds*/ |
|||
#define LV_INDEV_DEF_READ_PERIOD 30 /*[ms]*/ |
|||
|
|||
/*Use a custom tick source that tells the elapsed time in milliseconds.
|
|||
*It removes the need to manually update the tick with `lv_tick_inc()`)*/ |
|||
#define LV_TICK_CUSTOM 1 |
|||
#if LV_TICK_CUSTOM |
|||
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/ |
|||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/ |
|||
/*If using lvgl as ESP32 component*/ |
|||
// #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
|
|||
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
|
|||
#endif /*LV_TICK_CUSTOM*/ |
|||
|
|||
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
|
|||
*(Not so important, you can adjust it to modify default sizes and spaces)*/ |
|||
#define LV_DPI_DEF 130 /*[px/inch]*/ |
|||
|
|||
/*=======================
|
|||
* FEATURE CONFIGURATION |
|||
*=======================*/ |
|||
|
|||
/*-------------
|
|||
* Drawing |
|||
*-----------*/ |
|||
|
|||
/*Enable complex draw engine.
|
|||
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/ |
|||
#define LV_DRAW_COMPLEX 1 |
|||
#if LV_DRAW_COMPLEX != 0 |
|||
|
|||
/*Allow buffering some shadow calculation.
|
|||
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius` |
|||
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/ |
|||
#define LV_SHADOW_CACHE_SIZE 0 |
|||
|
|||
/* Set number of maximally cached circle data.
|
|||
* The circumference of 1/4 circle are saved for anti-aliasing |
|||
* radius * 4 bytes are used per circle (the most often used radiuses are saved) |
|||
* 0: to disable caching */ |
|||
#define LV_CIRCLE_CACHE_SIZE 4 |
|||
#endif /*LV_DRAW_COMPLEX*/ |
|||
|
|||
/**
|
|||
* "Simple layers" are used when a widget has `style_opa < 255` to buffer the widget into a layer |
|||
* and blend it as an image with the given opacity. |
|||
* Note that `bg_opa`, `text_opa` etc don't require buffering into layer) |
|||
* The widget can be buffered in smaller chunks to avoid using large buffers. |
|||
* |
|||
* - LV_LAYER_SIMPLE_BUF_SIZE: [bytes] the optimal target buffer size. LVGL will try to allocate it |
|||
* - LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [bytes] used if `LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated. |
|||
* |
|||
* Both buffer sizes are in bytes. |
|||
* "Transformed layers" (where transform_angle/zoom properties are used) use larger buffers |
|||
* and can't be drawn in chunks. So these settings affects only widgets with opacity. |
|||
*/ |
|||
#define LV_LAYER_SIMPLE_BUF_SIZE (24 * 1024) |
|||
#define LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE (3 * 1024) |
|||
|
|||
/*Default image cache size. Image caching keeps the images opened.
|
|||
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added) |
|||
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. |
|||
*However the opened images might consume additional RAM. |
|||
*0: to disable caching*/ |
|||
#define LV_IMG_CACHE_DEF_SIZE 0 |
|||
|
|||
/*Number of stops allowed per gradient. Increase this to allow more stops.
|
|||
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/ |
|||
#define LV_GRADIENT_MAX_STOPS 2 |
|||
|
|||
/*Default gradient buffer size.
|
|||
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again. |
|||
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes. |
|||
*If the cache is too small the map will be allocated only while it's required for the drawing. |
|||
*0 mean no caching.*/ |
|||
#define LV_GRAD_CACHE_DEF_SIZE 0 |
|||
|
|||
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
|
|||
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface |
|||
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */ |
|||
#define LV_DITHER_GRADIENT 0 |
|||
#if LV_DITHER_GRADIENT |
|||
/*Add support for error diffusion dithering.
|
|||
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing. |
|||
*The increase in memory consumption is (24 bits * object's width)*/ |
|||
#define LV_DITHER_ERROR_DIFFUSION 0 |
|||
#endif |
|||
|
|||
/*Maximum buffer size to allocate for rotation.
|
|||
*Only used if software rotation is enabled in the display driver.*/ |
|||
#define LV_DISP_ROT_MAX_BUF (10*1024) |
|||
|
|||
/*-------------
|
|||
* GPU |
|||
*-----------*/ |
|||
|
|||
/*Use Arm's 2D acceleration library Arm-2D */ |
|||
#define LV_USE_GPU_ARM2D 0 |
|||
|
|||
/*Use STM32's DMA2D (aka Chrom Art) GPU*/ |
|||
#define LV_USE_GPU_STM32_DMA2D 0 |
|||
#if LV_USE_GPU_STM32_DMA2D |
|||
/*Must be defined to include path of CMSIS header of target processor
|
|||
e.g. "stm32f7xx.h" or "stm32f4xx.h"*/ |
|||
#define LV_GPU_DMA2D_CMSIS_INCLUDE |
|||
#endif |
|||
|
|||
/*Enable RA6M3 G2D GPU*/ |
|||
#define LV_USE_GPU_RA6M3_G2D 0 |
|||
#if LV_USE_GPU_RA6M3_G2D |
|||
/*include path of target processor
|
|||
e.g. "hal_data.h"*/ |
|||
#define LV_GPU_RA6M3_G2D_INCLUDE "hal_data.h" |
|||
#endif |
|||
|
|||
/*Use SWM341's DMA2D GPU*/ |
|||
#define LV_USE_GPU_SWM341_DMA2D 0 |
|||
#if LV_USE_GPU_SWM341_DMA2D |
|||
#define LV_GPU_SWM341_DMA2D_INCLUDE "SWM341.h" |
|||
#endif |
|||
|
|||
/*Use NXP's PXP GPU iMX RTxxx platforms*/ |
|||
#define LV_USE_GPU_NXP_PXP 0 |
|||
#if LV_USE_GPU_NXP_PXP |
|||
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
|
|||
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS |
|||
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected. |
|||
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init() |
|||
*/ |
|||
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0 |
|||
#endif |
|||
|
|||
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/ |
|||
#define LV_USE_GPU_NXP_VG_LITE 0 |
|||
|
|||
/*Use SDL renderer API*/ |
|||
#define LV_USE_GPU_SDL 0 |
|||
#if LV_USE_GPU_SDL |
|||
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h> |
|||
/*Texture cache size, 8MB by default*/ |
|||
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8) |
|||
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/ |
|||
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6)) |
|||
#endif |
|||
|
|||
/*-------------
|
|||
* Logging |
|||
*-----------*/ |
|||
|
|||
/*Enable the log module*/ |
|||
#define LV_USE_LOG 0 |
|||
#if LV_USE_LOG |
|||
|
|||
/*How important log should be added:
|
|||
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information |
|||
*LV_LOG_LEVEL_INFO Log important events |
|||
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem |
|||
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail |
|||
*LV_LOG_LEVEL_USER Only logs added by the user |
|||
*LV_LOG_LEVEL_NONE Do not log anything*/ |
|||
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN |
|||
|
|||
/*1: Print the log with 'printf';
|
|||
*0: User need to register a callback with `lv_log_register_print_cb()`*/ |
|||
#define LV_LOG_PRINTF 0 |
|||
|
|||
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/ |
|||
#define LV_LOG_TRACE_MEM 1 |
|||
#define LV_LOG_TRACE_TIMER 1 |
|||
#define LV_LOG_TRACE_INDEV 1 |
|||
#define LV_LOG_TRACE_DISP_REFR 1 |
|||
#define LV_LOG_TRACE_EVENT 1 |
|||
#define LV_LOG_TRACE_OBJ_CREATE 1 |
|||
#define LV_LOG_TRACE_LAYOUT 1 |
|||
#define LV_LOG_TRACE_ANIM 1 |
|||
|
|||
#endif /*LV_USE_LOG*/ |
|||
|
|||
/*-------------
|
|||
* Asserts |
|||
*-----------*/ |
|||
|
|||
/*Enable asserts if an operation is failed or an invalid data is found.
|
|||
*If LV_USE_LOG is enabled an error message will be printed on failure*/ |
|||
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/ |
|||
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/ |
|||
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/ |
|||
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/ |
|||
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/ |
|||
|
|||
/*Add a custom handler when assert happens e.g. to restart the MCU*/ |
|||
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h> |
|||
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/ |
|||
|
|||
/*-------------
|
|||
* Others |
|||
*-----------*/ |
|||
|
|||
/*1: Show CPU usage and FPS count*/ |
|||
#define LV_USE_PERF_MONITOR 0 |
|||
#if LV_USE_PERF_MONITOR |
|||
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT |
|||
#endif |
|||
|
|||
/*1: Show the used memory and the memory fragmentation
|
|||
* Requires LV_MEM_CUSTOM = 0*/ |
|||
#define LV_USE_MEM_MONITOR 0 |
|||
#if LV_USE_MEM_MONITOR |
|||
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT |
|||
#endif |
|||
|
|||
/*1: Draw random colored rectangles over the redrawn areas*/ |
|||
#define LV_USE_REFR_DEBUG 0 |
|||
|
|||
/*Change the built in (v)snprintf functions*/ |
|||
#define LV_SPRINTF_CUSTOM 0 |
|||
#if LV_SPRINTF_CUSTOM |
|||
#define LV_SPRINTF_INCLUDE <stdio.h> |
|||
#define lv_snprintf snprintf |
|||
#define lv_vsnprintf vsnprintf |
|||
#else /*LV_SPRINTF_CUSTOM*/ |
|||
#define LV_SPRINTF_USE_FLOAT 0 |
|||
#endif /*LV_SPRINTF_CUSTOM*/ |
|||
|
|||
#define LV_USE_USER_DATA 1 |
|||
|
|||
/*Garbage Collector settings
|
|||
*Used if lvgl is bound to higher level language and the memory is managed by that language*/ |
|||
#define LV_ENABLE_GC 0 |
|||
#if LV_ENABLE_GC != 0 |
|||
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/ |
|||
#endif /*LV_ENABLE_GC*/ |
|||
|
|||
/*=====================
|
|||
* COMPILER SETTINGS |
|||
*====================*/ |
|||
|
|||
/*For big endian systems set to 1*/ |
|||
#define LV_BIG_ENDIAN_SYSTEM 0 |
|||
|
|||
/*Define a custom attribute to `lv_tick_inc` function*/ |
|||
#define LV_ATTRIBUTE_TICK_INC |
|||
|
|||
/*Define a custom attribute to `lv_timer_handler` function*/ |
|||
#define LV_ATTRIBUTE_TIMER_HANDLER |
|||
|
|||
/*Define a custom attribute to `lv_disp_flush_ready` function*/ |
|||
#define LV_ATTRIBUTE_FLUSH_READY |
|||
|
|||
/*Required alignment size for buffers*/ |
|||
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1 |
|||
|
|||
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
|
|||
* E.g. __attribute__((aligned(4)))*/ |
|||
#define LV_ATTRIBUTE_MEM_ALIGN |
|||
|
|||
/*Attribute to mark large constant arrays for example font's bitmaps*/ |
|||
#define LV_ATTRIBUTE_LARGE_CONST |
|||
|
|||
/*Compiler prefix for a big array declaration in RAM*/ |
|||
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY |
|||
|
|||
/*Place performance critical functions into a faster memory (e.g RAM)*/ |
|||
#define LV_ATTRIBUTE_FAST_MEM |
|||
|
|||
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/ |
|||
#define LV_ATTRIBUTE_DMA |
|||
|
|||
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
|
|||
*should also appear on LVGL binding API such as Micropython.*/ |
|||
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/ |
|||
|
|||
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/ |
|||
#define LV_USE_LARGE_COORD 0 |
|||
|
|||
/*==================
|
|||
* FONT USAGE |
|||
*===================*/ |
|||
|
|||
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
|
|||
*https://fonts.google.com/specimen/Montserrat*/
|
|||
#define LV_FONT_MONTSERRAT_8 0 |
|||
#define LV_FONT_MONTSERRAT_10 0 |
|||
#define LV_FONT_MONTSERRAT_12 1 |
|||
#define LV_FONT_MONTSERRAT_14 1 |
|||
#define LV_FONT_MONTSERRAT_16 1 |
|||
#define LV_FONT_MONTSERRAT_18 1 |
|||
#define LV_FONT_MONTSERRAT_20 1 |
|||
#define LV_FONT_MONTSERRAT_22 1 |
|||
#define LV_FONT_MONTSERRAT_24 1 |
|||
#define LV_FONT_MONTSERRAT_26 1 |
|||
#define LV_FONT_MONTSERRAT_28 0 |
|||
#define LV_FONT_MONTSERRAT_30 0 |
|||
#define LV_FONT_MONTSERRAT_32 1 |
|||
#define LV_FONT_MONTSERRAT_34 0 |
|||
#define LV_FONT_MONTSERRAT_36 0 |
|||
#define LV_FONT_MONTSERRAT_38 0 |
|||
#define LV_FONT_MONTSERRAT_40 1 |
|||
#define LV_FONT_MONTSERRAT_42 0 |
|||
#define LV_FONT_MONTSERRAT_44 1 |
|||
#define LV_FONT_MONTSERRAT_46 0 |
|||
#define LV_FONT_MONTSERRAT_48 1 |
|||
|
|||
/*Demonstrate special features*/ |
|||
#define LV_FONT_MONTSERRAT_12_SUBPX 0 |
|||
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/ |
|||
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/ |
|||
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/ |
|||
|
|||
/*Pixel perfect monospace fonts*/ |
|||
#define LV_FONT_UNSCII_8 0 |
|||
#define LV_FONT_UNSCII_16 0 |
|||
|
|||
/*Optionally declare custom fonts here.
|
|||
*You can use these fonts as default font too and they will be available globally. |
|||
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/ |
|||
#define LV_FONT_CUSTOM_DECLARE |
|||
|
|||
/*Always set a default font*/ |
|||
#define LV_FONT_DEFAULT &lv_font_montserrat_14 |
|||
|
|||
/*Enable handling large font and/or fonts with a lot of characters.
|
|||
*The limit depends on the font size, font face and bpp. |
|||
*Compiler error will be triggered if a font needs it.*/ |
|||
#define LV_FONT_FMT_TXT_LARGE 0 |
|||
|
|||
/*Enables/disables support for compressed fonts.*/ |
|||
#define LV_USE_FONT_COMPRESSED 0 |
|||
|
|||
/*Enable subpixel rendering*/ |
|||
#define LV_USE_FONT_SUBPX 0 |
|||
#if LV_USE_FONT_SUBPX |
|||
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/ |
|||
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/ |
|||
#endif |
|||
|
|||
/*Enable drawing placeholders when glyph dsc is not found*/ |
|||
#define LV_USE_FONT_PLACEHOLDER 1 |
|||
|
|||
/*=================
|
|||
* TEXT SETTINGS |
|||
*=================*/ |
|||
|
|||
/**
|
|||
* Select a character encoding for strings. |
|||
* Your IDE or editor should have the same character encoding |
|||
* - LV_TXT_ENC_UTF8 |
|||
* - LV_TXT_ENC_ASCII |
|||
*/ |
|||
#define LV_TXT_ENC LV_TXT_ENC_UTF8 |
|||
|
|||
/*Can break (wrap) texts on these chars*/ |
|||
#define LV_TXT_BREAK_CHARS " ,.;:-_" |
|||
|
|||
/*If a word is at least this long, will break wherever "prettiest"
|
|||
*To disable, set to a value <= 0*/ |
|||
#define LV_TXT_LINE_BREAK_LONG_LEN 0 |
|||
|
|||
/*Minimum number of characters in a long word to put on a line before a break.
|
|||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ |
|||
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 |
|||
|
|||
/*Minimum number of characters in a long word to put on a line after a break.
|
|||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ |
|||
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 |
|||
|
|||
/*The control character to use for signalling text recoloring.*/ |
|||
#define LV_TXT_COLOR_CMD "#" |
|||
|
|||
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
|
|||
*The direction will be processed according to the Unicode Bidirectional Algorithm: |
|||
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
|
|||
#define LV_USE_BIDI 0 |
|||
#if LV_USE_BIDI |
|||
/*Set the default direction. Supported values:
|
|||
*`LV_BASE_DIR_LTR` Left-to-Right |
|||
*`LV_BASE_DIR_RTL` Right-to-Left |
|||
*`LV_BASE_DIR_AUTO` detect texts base direction*/ |
|||
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO |
|||
#endif |
|||
|
|||
/*Enable Arabic/Persian processing
|
|||
*In these languages characters should be replaced with an other form based on their position in the text*/ |
|||
#define LV_USE_ARABIC_PERSIAN_CHARS 0 |
|||
|
|||
/*==================
|
|||
* WIDGET USAGE |
|||
*================*/ |
|||
|
|||
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/ |
|||
|
|||
#define LV_USE_ARC 0 |
|||
|
|||
#define LV_USE_BAR 0 |
|||
|
|||
#define LV_USE_BTN 1 |
|||
|
|||
#define LV_USE_BTNMATRIX 1 |
|||
|
|||
#define LV_USE_CANVAS 1 |
|||
|
|||
#define LV_USE_CHECKBOX 1 |
|||
|
|||
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/ |
|||
|
|||
#define LV_USE_IMG 1 /*Requires: lv_label*/ |
|||
|
|||
#define LV_USE_LABEL 1 |
|||
#if LV_USE_LABEL |
|||
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/ |
|||
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/ |
|||
#endif |
|||
|
|||
#define LV_USE_LINE 0 |
|||
|
|||
#define LV_USE_ROLLER 1 /*Requires: lv_label*/ |
|||
#if LV_USE_ROLLER |
|||
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/ |
|||
#endif |
|||
|
|||
#define LV_USE_SLIDER 0 /*Requires: lv_bar*/ |
|||
|
|||
#define LV_USE_SWITCH 0 |
|||
|
|||
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/ |
|||
#if LV_USE_TEXTAREA != 0 |
|||
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/ |
|||
#endif |
|||
|
|||
#define LV_USE_TABLE 0 |
|||
|
|||
/*==================
|
|||
* EXTRA COMPONENTS |
|||
*==================*/ |
|||
|
|||
/*-----------
|
|||
* Widgets |
|||
*----------*/ |
|||
#define LV_USE_ANIMIMG 0 |
|||
|
|||
#define LV_USE_CALENDAR 0 |
|||
#if LV_USE_CALENDAR |
|||
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0 |
|||
#if LV_CALENDAR_WEEK_STARTS_MONDAY |
|||
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} |
|||
#else |
|||
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} |
|||
#endif |
|||
|
|||
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} |
|||
#define LV_USE_CALENDAR_HEADER_ARROW 1 |
|||
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1 |
|||
#endif /*LV_USE_CALENDAR*/ |
|||
|
|||
#define LV_USE_CHART 0 |
|||
|
|||
#define LV_USE_COLORWHEEL 0 |
|||
|
|||
#define LV_USE_IMGBTN 1 |
|||
|
|||
#define LV_USE_KEYBOARD 1 |
|||
|
|||
#define LV_USE_LED 0 |
|||
|
|||
#define LV_USE_LIST 1 |
|||
|
|||
#define LV_USE_MENU 1 |
|||
|
|||
#define LV_USE_METER 0 |
|||
|
|||
#define LV_USE_MSGBOX 1 |
|||
|
|||
#define LV_USE_SPAN 1 |
|||
#if LV_USE_SPAN |
|||
/*A line text can contain maximum num of span descriptor */ |
|||
#define LV_SPAN_SNIPPET_STACK_SIZE 64 |
|||
#endif |
|||
|
|||
#define LV_USE_SPINBOX 0 |
|||
|
|||
#define LV_USE_SPINNER 0 |
|||
|
|||
#define LV_USE_TABVIEW 1 |
|||
|
|||
#define LV_USE_TILEVIEW 0 |
|||
|
|||
#define LV_USE_WIN 0 |
|||
|
|||
/*-----------
|
|||
* Themes |
|||
*----------*/ |
|||
|
|||
/*A simple, impressive and very complete theme*/ |
|||
#define LV_USE_THEME_DEFAULT 1 |
|||
#if LV_USE_THEME_DEFAULT |
|||
|
|||
/*0: Light mode; 1: Dark mode*/ |
|||
#define LV_THEME_DEFAULT_DARK 0 |
|||
|
|||
/*1: Enable grow on press*/ |
|||
#define LV_THEME_DEFAULT_GROW 1 |
|||
|
|||
/*Default transition time in [ms]*/ |
|||
#define LV_THEME_DEFAULT_TRANSITION_TIME 80 |
|||
#endif /*LV_USE_THEME_DEFAULT*/ |
|||
|
|||
/*A very simple theme that is a good starting point for a custom theme*/ |
|||
#define LV_USE_THEME_BASIC 1 |
|||
|
|||
/*A theme designed for monochrome displays*/ |
|||
#define LV_USE_THEME_MONO 1 |
|||
|
|||
/*-----------
|
|||
* Layouts |
|||
*----------*/ |
|||
|
|||
/*A layout similar to Flexbox in CSS.*/ |
|||
#define LV_USE_FLEX 1 |
|||
|
|||
/*A layout similar to Grid in CSS.*/ |
|||
#define LV_USE_GRID 1 |
|||
|
|||
/*---------------------
|
|||
* 3rd party libraries |
|||
*--------------------*/ |
|||
|
|||
/*File system interfaces for common APIs */ |
|||
|
|||
/*API for fopen, fread, etc*/ |
|||
#define LV_USE_FS_STDIO 0 |
|||
#if LV_USE_FS_STDIO |
|||
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ |
|||
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ |
|||
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ |
|||
#endif |
|||
|
|||
/*API for open, read, etc*/ |
|||
#define LV_USE_FS_POSIX 0 |
|||
#if LV_USE_FS_POSIX |
|||
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ |
|||
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ |
|||
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ |
|||
#endif |
|||
|
|||
/*API for CreateFile, ReadFile, etc*/ |
|||
#define LV_USE_FS_WIN32 0 |
|||
#if LV_USE_FS_WIN32 |
|||
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ |
|||
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ |
|||
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ |
|||
#endif |
|||
|
|||
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/ |
|||
#define LV_USE_FS_FATFS 0 |
|||
#if LV_USE_FS_FATFS |
|||
#define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ |
|||
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ |
|||
#endif |
|||
|
|||
/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/ |
|||
#define LV_USE_FS_LITTLEFS 0 |
|||
#if LV_USE_FS_LITTLEFS |
|||
#define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ |
|||
#define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ |
|||
#endif |
|||
|
|||
/*PNG decoder library*/ |
|||
#define LV_USE_PNG 0 |
|||
|
|||
/*BMP decoder library*/ |
|||
#define LV_USE_BMP 0 |
|||
|
|||
/* JPG + split JPG decoder library.
|
|||
* Split JPG is a custom format optimized for embedded systems. */ |
|||
#define LV_USE_SJPG 0 |
|||
|
|||
/*GIF decoder library*/ |
|||
#define LV_USE_GIF 0 |
|||
|
|||
/*QR code library*/ |
|||
#define LV_USE_QRCODE 0 |
|||
|
|||
/*FreeType library*/ |
|||
#define LV_USE_FREETYPE 0 |
|||
#if LV_USE_FREETYPE |
|||
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/ |
|||
#define LV_FREETYPE_CACHE_SIZE (16 * 1024) |
|||
#if LV_FREETYPE_CACHE_SIZE >= 0 |
|||
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */ |
|||
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */ |
|||
/* if font size >= 256, must be configured as image cache */ |
|||
#define LV_FREETYPE_SBIT_CACHE 0 |
|||
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */ |
|||
/* (0:use system defaults) */ |
|||
#define LV_FREETYPE_CACHE_FT_FACES 0 |
|||
#define LV_FREETYPE_CACHE_FT_SIZES 0 |
|||
#endif |
|||
#endif |
|||
|
|||
/*Tiny TTF library*/ |
|||
#define LV_USE_TINY_TTF 0 |
|||
#if LV_USE_TINY_TTF |
|||
/*Load TTF data from files*/ |
|||
#define LV_TINY_TTF_FILE_SUPPORT 0 |
|||
#endif |
|||
|
|||
/*Rlottie library*/ |
|||
#define LV_USE_RLOTTIE 0 |
|||
|
|||
/*FFmpeg library for image decoding and playing videos
|
|||
*Supports all major image formats so do not enable other image decoder with it*/ |
|||
#define LV_USE_FFMPEG 0 |
|||
#if LV_USE_FFMPEG |
|||
/*Dump input information to stderr*/ |
|||
#define LV_FFMPEG_DUMP_FORMAT 0 |
|||
#endif |
|||
|
|||
/*-----------
|
|||
* Others |
|||
*----------*/ |
|||
|
|||
/*1: Enable API to take snapshot for object*/ |
|||
#define LV_USE_SNAPSHOT 0 |
|||
|
|||
/*1: Enable Monkey test*/ |
|||
#define LV_USE_MONKEY 0 |
|||
|
|||
/*1: Enable grid navigation*/ |
|||
#define LV_USE_GRIDNAV 0 |
|||
|
|||
/*1: Enable lv_obj fragment*/ |
|||
#define LV_USE_FRAGMENT 0 |
|||
|
|||
/*1: Support using images as font in label or span widgets */ |
|||
#define LV_USE_IMGFONT 0 |
|||
|
|||
/*1: Enable a published subscriber based messaging system */ |
|||
#define LV_USE_MSG 0 |
|||
|
|||
/*1: Enable Pinyin input method*/ |
|||
/*Requires: lv_keyboard*/ |
|||
#define LV_USE_IME_PINYIN 0 |
|||
#if LV_USE_IME_PINYIN |
|||
/*1: Use default thesaurus*/ |
|||
/*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/ |
|||
#define LV_IME_PINYIN_USE_DEFAULT_DICT 1 |
|||
/*Set the maximum number of candidate panels that can be displayed*/ |
|||
/*This needs to be adjusted according to the size of the screen*/ |
|||
#define LV_IME_PINYIN_CAND_TEXT_NUM 6 |
|||
|
|||
/*Use 9 key input(k9)*/ |
|||
#define LV_IME_PINYIN_USE_K9_MODE 1 |
|||
#if LV_IME_PINYIN_USE_K9_MODE == 1 |
|||
#define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3 |
|||
#endif // LV_IME_PINYIN_USE_K9_MODE
|
|||
#endif |
|||
|
|||
/*==================
|
|||
* EXAMPLES |
|||
*==================*/ |
|||
|
|||
/*Enable the examples to be built with the library*/ |
|||
#define LV_BUILD_EXAMPLES 0 |
|||
|
|||
/*===================
|
|||
* DEMO USAGE |
|||
====================*/ |
|||
|
|||
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */ |
|||
#define LV_USE_DEMO_WIDGETS 0 |
|||
#if LV_USE_DEMO_WIDGETS |
|||
#define LV_DEMO_WIDGETS_SLIDESHOW 0 |
|||
#endif |
|||
|
|||
/*Demonstrate the usage of encoder and keyboard*/ |
|||
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 |
|||
|
|||
/*Benchmark your system*/ |
|||
#define LV_USE_DEMO_BENCHMARK 0 |
|||
#if LV_USE_DEMO_BENCHMARK |
|||
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/ |
|||
#define LV_DEMO_BENCHMARK_RGB565A8 0 |
|||
#endif |
|||
|
|||
/*Stress test for LVGL*/ |
|||
#define LV_USE_DEMO_STRESS 0 |
|||
|
|||
/*Music player demo*/ |
|||
#define LV_USE_DEMO_MUSIC 0 |
|||
#if LV_USE_DEMO_MUSIC |
|||
#define LV_DEMO_MUSIC_SQUARE 0 |
|||
#define LV_DEMO_MUSIC_LANDSCAPE 0 |
|||
#define LV_DEMO_MUSIC_ROUND 0 |
|||
#define LV_DEMO_MUSIC_LARGE 0 |
|||
#define LV_DEMO_MUSIC_AUTO_PLAY 0 |
|||
#endif |
|||
|
|||
/*--END OF LV_CONF_H--*/ |
|||
|
|||
#endif /*LV_CONF_H*/ |
|||
|
|||
#endif /*End of "Content enable"*/ |
|||
|
Can't render this file because it has a wrong number of fields in line 2.
|
@ -0,0 +1,25 @@ |
|||
extern const lv_font_t lv_font_arial_8; |
|||
extern const lv_font_t lv_font_arial_10; |
|||
extern const lv_font_t lv_font_arial_12; |
|||
extern const lv_font_t lv_font_arial_14; |
|||
extern const lv_font_t lv_font_arial_16; |
|||
extern const lv_font_t lv_font_arial_18; |
|||
extern const lv_font_t lv_font_arial_20; |
|||
extern const lv_font_t lv_font_arial_22; |
|||
extern const lv_font_t lv_font_arial_24; |
|||
extern const lv_font_t lv_font_arial_26; |
|||
extern const lv_font_t lv_font_arial_28; |
|||
extern const lv_font_t lv_font_arial_30; |
|||
extern const lv_font_t lv_font_arial_32; |
|||
extern const lv_font_t lv_font_arial_34; |
|||
extern const lv_font_t lv_font_arial_36; |
|||
extern const lv_font_t lv_font_arial_38; |
|||
extern const lv_font_t lv_font_arial_40; |
|||
extern const lv_font_t lv_font_arial_42; |
|||
extern const lv_font_t lv_font_arial_44; |
|||
extern const lv_font_t lv_font_arial_46; |
|||
extern const lv_font_t lv_font_arial_48; |
|||
extern const lv_font_t lv_font_arial_50; |
|||
extern const lv_font_t lv_font_arial_58; |
|||
extern const lv_font_t lv_font_arial_66; |
|||
extern const lv_font_t lv_font_arial_74; |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,307 @@ |
|||
#pragma once |
|||
|
|||
// SenseCapHAL - Custom RadioLib 7.x HAL for the Seeed SenseCAP Indicator
|
|||
//
|
|||
// The SenseCAP Indicator routes SX1262 control pins through a TCA9535
|
|||
// 16-bit I2C IO expander (address 0x20) because GPIOs 0-15 are used by
|
|||
// the 16-bit RGB display bus.
|
|||
//
|
|||
// Pin encoding matches LovyanGFX convention: (pin_index | IO_EXPANDER)
|
|||
// IO_EXPANDER = 0x40 (defined as build flag in platformio.ini)
|
|||
//
|
|||
// TCA9535 Port 0 pin assignments:
|
|||
// pin 0 = NSS (EXPANDER_IO_RADIO_NSS) — SX1262 chip-select
|
|||
// pin 1 = RESET (EXPANDER_IO_RADIO_RST) — SX1262 hardware reset
|
|||
// pin 2 = BUSY (EXPANDER_IO_RADIO_BUSY) — SX1262 busy signal
|
|||
// pin 3 = DIO1 (EXPANDER_IO_RADIO_DIO_1) — SX1262 IRQ
|
|||
// pin 4 = display SPI CS — ST7701 chip-select (must stay OUTPUT HIGH)
|
|||
// pin 5 = display RESX — ST7701 hardware reset (LovyanGFX cfg.pin_rst)
|
|||
// pin 6 = touch INT — FT5x06 interrupt (input, not used in SW)
|
|||
// pin 7 = touch RST — FT5x06 reset (not used in SW)
|
|||
//
|
|||
// DIO1 interrupt: TCA9535 /INT output → GPIO 42 (IO_EXPANDER_IRQ)
|
|||
// The /INT pin fires FALLING when any input changes.
|
|||
//
|
|||
// TCA9535 register map (Port 0 only):
|
|||
// 0x00 Input Port 0 (read)
|
|||
// 0x02 Output Port 0 (write; 1=HIGH, 0=LOW)
|
|||
// 0x06 Config Port 0 (0=output, 1=input)
|
|||
|
|||
#include <RadioLib.h> |
|||
#include <Wire.h> |
|||
#include <SPI.h> |
|||
#include <freertos/FreeRTOS.h> |
|||
#include <freertos/semphr.h> |
|||
#include <driver/gpio.h> |
|||
|
|||
class SenseCapHAL : public ArduinoHal { |
|||
TwoWire* _wire; |
|||
uint8_t _addr; // 7-bit I2C address of TCA9535 (0x20)
|
|||
uint8_t _out0; // cached Output Port 0 latch (all HIGH = de-asserted)
|
|||
uint8_t _cfg0; // cached Config Port 0 (all 1 = input initially)
|
|||
int _sclk, _miso, _mosi; // SPI data pins
|
|||
SemaphoreHandle_t _mutex; // shared Wire mutex (set via setMutex() after creation)
|
|||
|
|||
// ── Deferred IRQ dispatch ──────────────────────────────────────────────
|
|||
// The TCA9535 /INT fires for ANY input change on Port 0, not just DIO1.
|
|||
// BUSY transitions, touch-INT (pin 6), and other inputs all trigger GPIO 42.
|
|||
// We cannot do I2C inside an ISR, so the raw ISR just sets a flag.
|
|||
// dispatchPendingIrq() (called from the main loop via recvRaw) reads Port 0
|
|||
// to verify DIO1 is actually HIGH before forwarding the event to RadioLib.
|
|||
inline static volatile bool _s_pending = false; |
|||
inline static void (*_s_cb)(void) = nullptr; |
|||
uint8_t _irqBit = 0; // Port 0 bit-mask for DIO1, set in attachInterrupt()
|
|||
|
|||
// NOTE: no IRAM_ATTR — class-static IRAM functions cause Xtensa literal-pool
|
|||
// linker errors when defined inline in a header. Running from flash is fine
|
|||
// here: the SenseCAP uses ESP32-S3 with no mid-operation flash cache disables,
|
|||
// and LoRa packet timescales (tens of ms) dwarf any flash-cache miss latency.
|
|||
static void _rawIsr() { _s_pending = true; } |
|||
|
|||
// A pin is on the expander when its upper nibble equals IO_EXPANDER
|
|||
static bool isExp(uint32_t pin) { |
|||
return (pin & ~0x0Fu) == (uint32_t)IO_EXPANDER; |
|||
} |
|||
// Bit mask within TCA9535 port register
|
|||
static uint8_t expBit(uint32_t pin) { |
|||
return (uint8_t)(1u << (pin & 0x07u)); |
|||
} |
|||
// Pins 0-7 are Port 0; pins 8-15 are Port 1
|
|||
static bool isPort0(uint32_t pin) { |
|||
return (pin & 0x08u) == 0; |
|||
} |
|||
|
|||
void writeReg(uint8_t reg, uint8_t val) { |
|||
if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); |
|||
_wire->beginTransmission(_addr); |
|||
_wire->write(reg); |
|||
_wire->write(val); |
|||
_wire->endTransmission(); |
|||
if (_mutex) xSemaphoreGive(_mutex); |
|||
} |
|||
uint8_t readReg(uint8_t reg) { |
|||
if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); |
|||
_wire->beginTransmission(_addr); |
|||
_wire->write(reg); |
|||
_wire->endTransmission(false); |
|||
_wire->requestFrom(_addr, (uint8_t)1); |
|||
uint8_t val = _wire->available() ? _wire->read() : 0xFF; |
|||
if (_mutex) xSemaphoreGive(_mutex); |
|||
return val; |
|||
} |
|||
|
|||
public: |
|||
SenseCapHAL(SPIClass& spi, int sclk, int miso, int mosi, |
|||
uint8_t i2c_addr = 0x20, TwoWire* wire = &Wire) |
|||
: ArduinoHal(spi, SPISettings(2000000, MSBFIRST, SPI_MODE0)) |
|||
, _wire(wire), _addr(i2c_addr) |
|||
, _out0(0xFF) // all HIGH (NSS, RESET de-asserted)
|
|||
, _cfg0(0xFF) // all inputs initially
|
|||
, _sclk(sclk), _miso(miso), _mosi(mosi) |
|||
, _mutex(nullptr) |
|||
{} |
|||
|
|||
// Set the shared I2C mutex. Must be called before any Wire operations.
|
|||
// Both this HAL and the touch-read callback must use the same handle.
|
|||
void setMutex(SemaphoreHandle_t m) { _mutex = m; } |
|||
|
|||
// Call once Wire is running, AFTER lcd.begin() has completed.
|
|||
// Configures TCA9535 Port 0 radio pins (bits 0-3) and locks bit 4 (display CS).
|
|||
// Bits 5-7 are preserved exactly as LovyanGFX left them.
|
|||
void initExpander() { |
|||
// Read the current register values set by LovyanGFX
|
|||
uint8_t cur_out = readReg(0x02); |
|||
uint8_t cur_cfg = readReg(0x06); |
|||
|
|||
// Bits 0-3 (radio): inputs initially; RadioLib reconfigures as needed.
|
|||
// Output latch HIGH = de-asserted.
|
|||
// Bit 4 (display CS): OUTPUT HIGH — permanently de-asserted.
|
|||
// LovyanGFX leaves this as INPUT after lcd.begin();
|
|||
// a floating CS allows radio SPI to corrupt the ST7701.
|
|||
// Bit 5 (display RESX): preserved as OUTPUT HIGH — lcd.begin() already pulsed
|
|||
// it LOW→HIGH to reset the ST7701 before init commands.
|
|||
// Bits 6-7 (touch INT/RST): preserved as LovyanGFX left them (inputs).
|
|||
_out0 = (cur_out & 0xE0) | 0x1F; // bits 0-4 = HIGH, bits 5-7 = preserved
|
|||
_cfg0 = (cur_cfg & 0xE0) | 0x0F; // bits 0-3 = input, bit 4 = OUTPUT, bits 5-7 = preserved
|
|||
|
|||
writeReg(0x02, _out0); // Output Port 0
|
|||
writeReg(0x06, _cfg0); // Config Port 0
|
|||
|
|||
// Port 1 (pins 8-15) — appears unused on the SenseCAP Indicator.
|
|||
// TCA9535 /INT fires on ANY input change on either port and only clears
|
|||
// when the triggering port register is read. Floating Port 1 inputs
|
|||
// continuously re-assert /INT, preventing DIO1 edges from ever firing.
|
|||
// Fix: make all Port 1 pins OUTPUT HIGH so they can never float.
|
|||
writeReg(0x03, 0xFF); // Output Port 1: all HIGH
|
|||
writeReg(0x07, 0x00); // Config Port 1: all OUTPUTS
|
|||
|
|||
// Read both input ports now to establish a clean /INT baseline.
|
|||
readReg(0x00); |
|||
readReg(0x01); |
|||
|
|||
Serial.printf("[SenseCapHAL] TCA9535@0x%02X init: out0=0x%02X cfg0=0x%02X\n", |
|||
_addr, _out0, _cfg0); |
|||
} |
|||
|
|||
// ── Overrides ──────────────────────────────────────────────────────────
|
|||
//
|
|||
// SOFTWARE (bit-bang) SPI — intentionally avoids the hardware FSPI peripheral.
|
|||
//
|
|||
// Calling SPI.begin() (hardware FSPI) AFTER lcd.begin() disrupts the
|
|||
// LCD_CAM peripheral that is already running the RGB parallel bus,
|
|||
// causing the display to go blank. Since the LCD_CAM uses GPIOs 0–21
|
|||
// and the SX1262 SPI uses GPIOs 41/47/48, there is no pin conflict —
|
|||
// the problem is at the peripheral / DMA level when FSPI is initialised
|
|||
// while LCD_CAM is active.
|
|||
//
|
|||
// Bit-bang SPI on the same GPIOs avoids hardware SPI entirely.
|
|||
// The SX1262 requires at most ~16 MHz SPI; bit-bang on ESP32-S3 gives
|
|||
// ~1–2 MHz which is more than adequate for LoRa configuration and DIO
|
|||
// interrupt latency is unaffected.
|
|||
|
|||
// ArduinoHal::init() only calls spiBegin() when initInterface==true,
|
|||
// which is false when an explicit SPIClass& is supplied. Override to
|
|||
// always call our spiBegin().
|
|||
void init() override { spiBegin(); } |
|||
|
|||
void spiBegin() override { |
|||
// Configure GPIO pins for direct CPU control (disconnects any
|
|||
// peripheral / GPIO-matrix function, including leftover FSPI routing).
|
|||
::pinMode(_sclk, OUTPUT); |
|||
::pinMode(_mosi, OUTPUT); |
|||
::pinMode(_miso, INPUT); |
|||
::digitalWrite(_sclk, LOW); |
|||
Serial.printf("[SenseCapHAL] SPI (bit-bang) SCLK=%d MOSI=%d MISO=%d\n", |
|||
_sclk, _mosi, _miso); |
|||
} |
|||
|
|||
void spiEnd() override { |
|||
// nothing to tear down for bit-bang
|
|||
} |
|||
|
|||
void spiBeginTransaction() override { |
|||
// nothing needed — CS is managed via the expander in digitalWrite()
|
|||
} |
|||
|
|||
void spiEndTransaction() override { |
|||
// nothing needed
|
|||
} |
|||
|
|||
// Bit-bang SPI Mode 0 (CPOL=0, CPHA=0): data sampled on rising edge.
|
|||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override { |
|||
for (size_t i = 0; i < len; i++) { |
|||
uint8_t tx = out ? out[i] : 0x00; |
|||
uint8_t rx = 0; |
|||
for (int b = 7; b >= 0; b--) { |
|||
::digitalWrite(_mosi, (tx >> b) & 1); |
|||
::digitalWrite(_sclk, HIGH); |
|||
rx = (rx << 1) | (::digitalRead(_miso) ? 1 : 0); |
|||
::digitalWrite(_sclk, LOW); |
|||
} |
|||
if (in) in[i] = rx; |
|||
} |
|||
} |
|||
|
|||
void pinMode(uint32_t pin, uint32_t mode) override { |
|||
if (isExp(pin) && isPort0(pin)) { |
|||
if (mode == OUTPUT) _cfg0 &= ~expBit(pin); |
|||
else _cfg0 |= expBit(pin); |
|||
writeReg(0x06, _cfg0); |
|||
} else { |
|||
ArduinoHal::pinMode(pin, mode); |
|||
} |
|||
} |
|||
|
|||
void digitalWrite(uint32_t pin, uint32_t value) override { |
|||
if (isExp(pin) && isPort0(pin)) { |
|||
if (value) _out0 |= expBit(pin); |
|||
else _out0 &= ~expBit(pin); |
|||
writeReg(0x02, _out0); |
|||
} else { |
|||
ArduinoHal::digitalWrite(pin, value); |
|||
} |
|||
} |
|||
|
|||
uint32_t digitalRead(uint32_t pin) override { |
|||
if (isExp(pin) && isPort0(pin)) { |
|||
return (readReg(0x00) & expBit(pin)) ? 1 : 0; |
|||
} |
|||
return ArduinoHal::digitalRead(pin); |
|||
} |
|||
|
|||
// DIO1 is expander pin 3; redirect the interrupt to IO_EXPANDER_IRQ (GPIO 42).
|
|||
// TCA9535 /INT fires FALLING when any input changes (DIO1 going HIGH = packet ready).
|
|||
// RadioLib 7.x calls pinToInterrupt(irq_pin) BEFORE attachInterrupt().
|
|||
// Without this override, pinToInterrupt(0x43) → digitalPinToInterrupt(67)
|
|||
// which is invalid on ESP32-S3, so attachInterrupt() silently fails.
|
|||
// Return IO_EXPANDER_IRQ (GPIO 42) directly for any expander pin.
|
|||
uint32_t pinToInterrupt(uint32_t pin) override { |
|||
if (isExp(pin)) { |
|||
return (uint32_t)IO_EXPANDER_IRQ; |
|||
} |
|||
return ArduinoHal::pinToInterrupt(pin); |
|||
} |
|||
|
|||
// Called with either:
|
|||
// a) raw expander pin (e.g. 0x43) — direct call path
|
|||
// b) IO_EXPANDER_IRQ (42) — via pinToInterrupt() path (RadioLib 7.x)
|
|||
//
|
|||
// DEFERRED DISPATCH: instead of calling `cb` (= RadioLib's setFlag) directly
|
|||
// from the ISR, we attach a lightweight raw ISR that only sets _s_pending.
|
|||
// The real dispatch (reading Port 0 to verify DIO1 is HIGH) happens later
|
|||
// in dispatchPendingIrq(), called from the main loop via recvRaw().
|
|||
// This prevents touch-INT (pin 6), BUSY, and other Port 0 input changes
|
|||
// from triggering false radio-interrupt events.
|
|||
void attachInterrupt(uint32_t pin, void (*cb)(void), uint32_t mode) override { |
|||
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) { |
|||
_s_cb = cb; // save RadioLib's setFlag callback
|
|||
_irqBit = isExp(pin) ? expBit(pin) // direct expander pin → derive bit
|
|||
: (uint8_t)(1u << 3u); // pin=42 path → DIO1 is bit 3
|
|||
// Clear any pending TCA9535 /INT by reading BOTH port registers.
|
|||
readReg(0x00); |
|||
readReg(0x01); |
|||
::pinMode(IO_EXPANDER_IRQ, INPUT_PULLUP); |
|||
int gpio42 = ::digitalRead(IO_EXPANDER_IRQ); |
|||
::attachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ), _rawIsr, FALLING); |
|||
Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d state=%d (FALLING, deferred)\n", |
|||
IO_EXPANDER_IRQ, gpio42); |
|||
} else { |
|||
ArduinoHal::attachInterrupt(pin, cb, mode); |
|||
} |
|||
} |
|||
|
|||
// ── dispatchPendingIrq ────────────────────────────────────────────────
|
|||
// Call from the main loop (NOT from ISR). Reads Port 0 to check whether
|
|||
// DIO1 is actually HIGH. If so, forwards the event to RadioLib (setFlag).
|
|||
// Ignores spurious triggers from BUSY, touch-INT, or any other Port 0 input.
|
|||
void dispatchPendingIrq() { |
|||
if (!_s_pending) return; |
|||
_s_pending = false; |
|||
|
|||
// Reading Port 0 also acknowledges the TCA9535 /INT for ALL inputs,
|
|||
// including touch INT and BUSY — preventing /INT from staying stuck LOW.
|
|||
uint8_t port0 = readReg(0x00); |
|||
if (_irqBit && (port0 & _irqBit)) { |
|||
// DIO1 is HIGH → this is a real radio interrupt (RX_DONE or TX_DONE)
|
|||
if (_s_cb) _s_cb(); |
|||
} |
|||
// else: spurious — BUSY transition, touch INT, etc. Do nothing.
|
|||
} |
|||
|
|||
void detachInterrupt(uint32_t pin) override { |
|||
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) { |
|||
::detachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ)); |
|||
} else { |
|||
ArduinoHal::detachInterrupt(pin); |
|||
} |
|||
} |
|||
|
|||
// Scan for the TCA9535 on I2C and log the result
|
|||
bool scanExpander() { |
|||
if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); |
|||
_wire->beginTransmission(_addr); |
|||
bool found = (_wire->endTransmission() == 0); |
|||
if (_mutex) xSemaphoreGive(_mutex); |
|||
return found; |
|||
} |
|||
}; |
|||
@ -0,0 +1,27 @@ |
|||
#pragma once |
|||
|
|||
// SenseCapSX1262Wrapper — extends CustomSX1262Wrapper with a DIO1-verified
|
|||
// dispatchPendingIrq() override.
|
|||
//
|
|||
// On SenseCAP Indicator the SX1262 DIO1, BUSY, and touch-INT all share the
|
|||
// same TCA9535 /INT line (GPIO 42). The raw ISR in SenseCapHAL only sets a
|
|||
// pending flag; this override reads Port 0 via I2C and calls setFlag() only
|
|||
// when DIO1 is actually HIGH, silencing spurious triggers from BUSY
|
|||
// transitions, touch events, or any other Port 0 input change.
|
|||
|
|||
#include <helpers/radiolib/CustomSX1262Wrapper.h> |
|||
#include "SenseCapHAL.h" |
|||
|
|||
class SenseCapSX1262Wrapper : public CustomSX1262Wrapper { |
|||
public: |
|||
SenseCapSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) |
|||
: CustomSX1262Wrapper(radio, board) {} |
|||
|
|||
protected: |
|||
void dispatchPendingIrq() override { |
|||
// Access HAL via Module (Module::hal is public in RadioLib 7.x)
|
|||
auto* hal = static_cast<SenseCapHAL*>( |
|||
static_cast<CustomSX1262*>(_radio)->getMod()->hal); |
|||
hal->dispatchPendingIrq(); |
|||
} |
|||
}; |
|||
|
After Width: | Height: | Size: 808 KiB |
|
After Width: | Height: | Size: 789 KiB |
|
After Width: | Height: | Size: 816 KiB |
|
After Width: | Height: | Size: 786 KiB |
@ -6,7 +6,7 @@ board_build.flash_mode = qio |
|||
board_build.psram_type = opi |
|||
board_upload.flash_size = 8MB |
|||
board_upload.maximum_size = 8388608 |
|||
board_build.partitions = default.csv |
|||
board_build.partitions = partition-table-8MB.csv |
|||
build_flags = |
|||
${esp32_base.build_flags} |
|||
-D PIN_BOARD_SDA=39 |
|||
@ -36,15 +36,40 @@ lib_deps=${esp32_base.lib_deps} |
|||
extends =SenseCapIndicator-ESPNow |
|||
build_flags = |
|||
${SenseCapIndicator-ESPNow.build_flags} |
|||
-I examples/companion_radio/ui-new |
|||
; -I examples/companion_radio/ui-new |
|||
-D MAX_CONTACTS=350 |
|||
-D MAX_GROUP_CHANNELS=40 |
|||
-D CORE_DEBUG_LEVEL=4 ; 0: None, 1: Error, 2: Warn, 3: Info, 4: Debug, 5: Verbose |
|||
-D LV_CONF_PATH=lv_conf.h |
|||
-D SEEED_SENSECAP_INDICATOR |
|||
-D RADIO_CLASS=CustomSX1262 |
|||
-D WRAPPER_CLASS=SenseCapSX1262Wrapper |
|||
-D PIN_USER_BTN=38 |
|||
-D LANG_EN |
|||
-D LORA_FREQ=869.525 |
|||
-D LORA_BW=250.0 |
|||
-D LORA_SF=11 |
|||
-D LORA_CR=5 |
|||
-D LORA_TX_POWER=20 |
|||
-D SX126X_DIO3_TCXO_VOLTAGE=2.4 |
|||
-D SX126X_DIO2_AS_RF_SWITCH=true |
|||
-D ADVERT_NAME='"SenseCap Client"' |
|||
-D MAX_CONTACTS=350 |
|||
-D MAX_GROUP_CHANNELS=8 |
|||
-D MESH_DEBUG=1 |
|||
-D RADIOLIB_SPI_PARANOID=0 |
|||
|
|||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 |
|||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 |
|||
; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 |
|||
build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} |
|||
+<../examples/companion_radio/ui-new/*.cpp> |
|||
+<../examples/companion_radio/*.cpp> |
|||
+<../examples/simple_secure_chat_ui/*.cpp> |
|||
+<fonts/*.c> |
|||
+<UI/*.c> |
|||
lib_deps = |
|||
${SenseCapIndicator-ESPNow.lib_deps} |
|||
densaugeo/base64 @ ~1.4.0 |
|||
fbiego/ESP32Time@^2.0.6 |
|||
lvgl/[email protected] |
|||
lovyan03/LovyanGFX@^1.2.7 |
|||
bitbank2/PNGdec@^1.1.6 |
|||
densaugeo/base64 @ ~1.4.0 |
|||
|
|||
@ -1,44 +1,169 @@ |
|||
#include <Arduino.h> |
|||
#include "target.h" |
|||
#include <helpers/ArduinoHelpers.h> |
|||
#include <driver/gpio.h> |
|||
|
|||
// Recover a stuck I2C bus by manually clocking SCL up to 9 times until
|
|||
// SDA is released, then issuing a STOP condition.
|
|||
static void i2c_bus_recovery() { |
|||
Serial.println("[i2c_recover] Attempting I2C bus recovery..."); |
|||
pinMode(PIN_BOARD_SCL, OUTPUT_OPEN_DRAIN); |
|||
pinMode(PIN_BOARD_SDA, INPUT_PULLUP); |
|||
digitalWrite(PIN_BOARD_SCL, HIGH); |
|||
delayMicroseconds(10); |
|||
|
|||
bool released = false; |
|||
for (int i = 0; i < 9; i++) { |
|||
digitalWrite(PIN_BOARD_SCL, LOW); |
|||
delayMicroseconds(10); |
|||
digitalWrite(PIN_BOARD_SCL, HIGH); |
|||
delayMicroseconds(10); |
|||
if (digitalRead(PIN_BOARD_SDA) == HIGH) { |
|||
released = true; |
|||
Serial.printf("[i2c_recover] SDA released after %d clocks\n", i + 1); |
|||
break; |
|||
} |
|||
} |
|||
if (!released) { |
|||
Serial.println("[i2c_recover] SDA still LOW after 9 clocks!"); |
|||
} |
|||
|
|||
// Generate STOP condition: SDA LOW→HIGH while SCL HIGH
|
|||
pinMode(PIN_BOARD_SDA, OUTPUT_OPEN_DRAIN); |
|||
digitalWrite(PIN_BOARD_SDA, LOW); |
|||
delayMicroseconds(10); |
|||
digitalWrite(PIN_BOARD_SCL, HIGH); |
|||
delayMicroseconds(10); |
|||
digitalWrite(PIN_BOARD_SDA, HIGH); |
|||
delayMicroseconds(10); |
|||
|
|||
// Restore pins to input so Wire can take over
|
|||
pinMode(PIN_BOARD_SCL, INPUT_PULLUP); |
|||
pinMode(PIN_BOARD_SDA, INPUT_PULLUP); |
|||
delay(5); |
|||
Serial.println("[i2c_recover] Done."); |
|||
} |
|||
|
|||
ESP32Board board; |
|||
|
|||
ESPNOWRadio radio_driver; |
|||
// ── SX1262 SPI data pins (hardware FSPI / SPI2) ────────────────────────
|
|||
// Shared with the ST7701 display config SPI (bit-banged by LovyanGFX only
|
|||
// during lcd.begin(), so no runtime conflict).
|
|||
#define LORA_SCLK 41 |
|||
#define LORA_MISO 47 |
|||
#define LORA_MOSI 48 |
|||
|
|||
// ── SX1262 control pins via TCA9535 I2C IO Expander (7-bit addr 0x20) ──
|
|||
// Encoded as (pin_index | IO_EXPANDER) per LovyanGFX / Seeed convention.
|
|||
// IO_EXPANDER = 0x40 and IO_EXPANDER_IRQ = 42 defined in platformio.ini
|
|||
#define LORA_NSS (0 | IO_EXPANDER) // TCA9535 port-0 pin 0 (0x40)
|
|||
#define LORA_RESET (1 | IO_EXPANDER) // TCA9535 port-0 pin 1 (0x41)
|
|||
#define LORA_BUSY (2 | IO_EXPANDER) // TCA9535 port-0 pin 2 (0x42)
|
|||
#define LORA_DIO1 (3 | IO_EXPANDER) // TCA9535 port-0 pin 3 (0x43)
|
|||
// → interrupt fires on GPIO 42
|
|||
|
|||
ESP32RTCClock rtc_clock; |
|||
#if defined(ENV_INCLUDE_GPS) |
|||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, (mesh::RTCClock*)&rtc_clock); |
|||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); |
|||
#else |
|||
EnvironmentSensorManager sensors = EnvironmentSensorManager(); |
|||
#endif |
|||
// Custom RadioLib HAL: routes expander pins through TCA9535 (I2C 0x20).
|
|||
// Uses software (bit-bang) SPI on SCLK/MOSI/MISO to avoid taking the
|
|||
// hardware FSPI peripheral, which would disrupt LovyanGFX's LCD_CAM output.
|
|||
// SPI is passed as a placeholder only — all SPI methods are overridden.
|
|||
static SenseCapHAL radio_hal(SPI, LORA_SCLK, LORA_MISO, LORA_MOSI, 0x20, &Wire); |
|||
|
|||
#ifdef DISPLAY_CLASS |
|||
DISPLAY_CLASS display; |
|||
#ifdef PIN_USER_BTN |
|||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); |
|||
#endif |
|||
#endif |
|||
// SX1262 module using the custom HAL
|
|||
RADIO_CLASS radio = new Module(&radio_hal, LORA_NSS, LORA_DIO1, LORA_RESET, LORA_BUSY); |
|||
|
|||
WRAPPER_CLASS radio_driver(radio, board); |
|||
|
|||
ESP32RTCClock fallback_clock; |
|||
AutoDiscoverRTCClock rtc_clock(fallback_clock); |
|||
|
|||
EnvironmentSensorManager sensors; |
|||
|
|||
// Shared Wire mutex — created in radio_init(), exported via target.h
|
|||
SemaphoreHandle_t g_i2c_mutex = nullptr; |
|||
|
|||
// ── radio_init ─────────────────────────────────────────────────────────
|
|||
|
|||
bool radio_init() { |
|||
rtc_clock.begin(); |
|||
// Create the shared Wire mutex FIRST so both the radio HAL and the
|
|||
// LVGL touch callback (my_touchpad_read) serialise their Wire access.
|
|||
g_i2c_mutex = xSemaphoreCreateMutex(); |
|||
radio_hal.setMutex(g_i2c_mutex); |
|||
|
|||
radio_driver.init(); |
|||
Serial.println("[radio_init] Starting..."); |
|||
Serial.printf("[radio_init] SPI : SCLK=%d MISO=%d MOSI=%d\n", |
|||
LORA_SCLK, LORA_MISO, LORA_MOSI); |
|||
Serial.printf("[radio_init] Expander (0x20): NSS=pin%d RST=pin%d BUSY=pin%d DIO1=pin%d\n", |
|||
LORA_NSS & 0x0F, LORA_RESET & 0x0F, LORA_BUSY & 0x0F, LORA_DIO1 & 0x0F); |
|||
Serial.printf("[radio_init] DIO1 interrupt → GPIO %d\n", IO_EXPANDER_IRQ); |
|||
|
|||
return true; // success
|
|||
} |
|||
// Take mutex for bus recovery + Wire re-init + scan
|
|||
// (prevents LVGL touch task from accessing Wire concurrently)
|
|||
xSemaphoreTake(g_i2c_mutex, portMAX_DELAY); |
|||
|
|||
// Recover stuck I2C bus (may be left in bad state after display init)
|
|||
i2c_bus_recovery(); |
|||
|
|||
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
|
|||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
|
|||
class ESP_RNG : public mesh::RNG { |
|||
public: |
|||
void random(uint8_t* dest, size_t sz) override { |
|||
esp_fill_random(dest, sz); |
|||
// Wire must be up before TCA9535 access — force full re-init after recovery
|
|||
Wire.end(); |
|||
delay(5); |
|||
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL, 400000); |
|||
Wire.setTimeOut(15); // 15 ms per address for fast scan
|
|||
delay(10); |
|||
|
|||
// ── Full I2C bus scan ───────────────────────────────────────────────────
|
|||
Serial.println("[radio_init] I2C scan:"); |
|||
uint8_t found_addr = 0; |
|||
for (uint8_t addr = 0x08; addr <= 0x77; addr++) { |
|||
Wire.beginTransmission(addr); |
|||
uint8_t err = Wire.endTransmission(); |
|||
if (err == 0) { |
|||
Serial.printf("[radio_init] Device found at 0x%02X\n", addr); |
|||
found_addr = addr; |
|||
} |
|||
} |
|||
if (found_addr == 0) { |
|||
Serial.println("[radio_init] No I2C devices found!"); |
|||
} |
|||
Serial.println("[radio_init] I2C scan done."); |
|||
|
|||
xSemaphoreGive(g_i2c_mutex); |
|||
|
|||
// Verify TCA9535 is present on the bus
|
|||
if (radio_hal.scanExpander()) { |
|||
Serial.println("[radio_init] TCA9535 FOUND at 0x20"); |
|||
} else { |
|||
Serial.println("[radio_init] WARNING: TCA9535 NOT FOUND at 0x20 – check I2C wiring"); |
|||
} |
|||
}; |
|||
|
|||
// Set TCA9535 Port 0 defaults: all outputs HIGH (NSS/RESET de-asserted)
|
|||
radio_hal.initExpander(); |
|||
|
|||
fallback_clock.begin(); |
|||
Serial.println("[radio_init] RTC initialized"); |
|||
|
|||
// std_init() calls radio.begin() internally, which calls our spiBegin()
|
|||
// to initialise the FSPI bus with pins 41/47/48, then communicates with
|
|||
// the SX1262 using our expander-aware digitalWrite/digitalRead.
|
|||
bool ok = radio.std_init(); |
|||
Serial.printf("[radio_init] std_init result: %s\n", ok ? "OK" : "FAILED"); |
|||
return ok; |
|||
} |
|||
|
|||
uint32_t radio_get_rng_seed() { |
|||
return radio.random(0x7FFFFFFF); |
|||
} |
|||
|
|||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { |
|||
radio.setFrequency(freq); |
|||
radio.setSpreadingFactor(sf); |
|||
radio.setBandwidth(bw); |
|||
radio.setCodingRate(cr); |
|||
} |
|||
|
|||
void radio_set_tx_power(uint8_t dbm) { |
|||
radio.setOutputPower(dbm); |
|||
} |
|||
|
|||
mesh::LocalIdentity radio_new_identity() { |
|||
ESP_RNG rng; |
|||
return mesh::LocalIdentity(&rng); // create new random identity
|
|||
RadioNoiseListener rng(radio); |
|||
return mesh::LocalIdentity(&rng); |
|||
} |
|||
|
|||
@ -1,26 +1,78 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <RadioLib.h> |
|||
#include <Wire.h> |
|||
#include <freertos/FreeRTOS.h> |
|||
#include <freertos/semphr.h> |
|||
|
|||
#include <helpers/ESP32Board.h> |
|||
#include <helpers/esp32/ESPNOWRadio.h> |
|||
#include <helpers/radiolib/RadioLibWrappers.h> |
|||
#include <helpers/radiolib/CustomSX1262Wrapper.h> |
|||
#include <helpers/AutoDiscoverRTCClock.h> |
|||
#include <helpers/esp32/SenseCapHAL.h> // TCA9535 IO expander HAL for RadioLib |
|||
#include <helpers/esp32/SenseCapSX1262Wrapper.h> // DIO1-verified IRQ dispatch |
|||
|
|||
#include <helpers/SensorManager.h> |
|||
#include <helpers/sensors/EnvironmentSensorManager.h> |
|||
|
|||
#ifdef ENV_INCLUDE_GPS |
|||
#include <helpers/sensors/MicroNMEALocationProvider.h> |
|||
#endif |
|||
|
|||
#ifdef DISPLAY_CLASS |
|||
#include "SCIndicatorDisplay.h" |
|||
#include <helpers/ui/MomentaryButton.h> |
|||
#endif |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Board
|
|||
// -------------------------------------------------
|
|||
extern ESP32Board board; |
|||
extern ESPNOWRadio radio_driver; |
|||
extern ESP32RTCClock rtc_clock; |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Radio (SX1262 - SenseCAP uses SX1262)
|
|||
// -------------------------------------------------
|
|||
extern SenseCapSX1262Wrapper radio_driver; |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// RTC
|
|||
// -------------------------------------------------
|
|||
extern AutoDiscoverRTCClock rtc_clock; |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Sensors
|
|||
// -------------------------------------------------
|
|||
extern EnvironmentSensorManager sensors; |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Display + Button
|
|||
// -------------------------------------------------
|
|||
#ifdef DISPLAY_CLASS |
|||
extern DISPLAY_CLASS display; |
|||
extern MomentaryButton user_btn; |
|||
#endif |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Shared I2C mutex
|
|||
// Protects Wire access shared between SenseCapHAL (TCA9535 @ 0x20)
|
|||
// and the LVGL touch callback (FT5x06 @ 0x48).
|
|||
// Created in radio_init() before std_init(); use via g_i2c_mutex.
|
|||
// -------------------------------------------------
|
|||
extern SemaphoreHandle_t g_i2c_mutex; |
|||
|
|||
|
|||
// -------------------------------------------------
|
|||
// Functions
|
|||
// -------------------------------------------------
|
|||
bool radio_init(); |
|||
mesh::LocalIdentity radio_new_identity(); |
|||
uint32_t radio_get_rng_seed(); |
|||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); |
|||
void radio_set_tx_power(uint8_t dbm); |
|||
mesh::LocalIdentity radio_new_identity(); |
|||
Loading…
Reference in new issue