Browse Source

wip

pull/2568/head
Christos Themelis 4 months ago
parent
commit
f862dd1aa8
  1. 20
      examples/companion_radio/MyMesh.cpp
  2. 3
      examples/companion_radio/MyMesh.h
  3. 249
      examples/companion_radio/main.cpp
  4. 25
      examples/companion_radio/task_clock.cpp
  5. 19
      examples/companion_radio/task_lvgl.cpp
  6. 797
      examples/companion_radio/uiManager.cpp
  7. 50
      examples/companion_radio/uiTasks.cpp
  8. 4
      examples/simple_secure_chat_ui/uiManager.cpp
  9. 3
      variants/sensecap_indicator-espnow/platformio.ini

20
examples/companion_radio/MyMesh.cpp

@ -872,6 +872,10 @@ void MyMesh::begin(bool has_display) {
const char *MyMesh::getNodeName() { const char *MyMesh::getNodeName() {
return _prefs.node_name; return _prefs.node_name;
} }
const char* MyMesh::getFirmwareVer() { return FIRMWARE_VERSION; }
const char* MyMesh::getBuildDate() { return FIRMWARE_BUILD_DATE; }
NodePrefs *MyMesh::getNodePrefs() { NodePrefs *MyMesh::getNodePrefs() {
return &_prefs; return &_prefs;
} }
@ -1747,6 +1751,22 @@ void MyMesh::enterCLIRescue() {
Serial.println("========= CLI Rescue ========="); Serial.println("========= CLI Rescue =========");
} }
void MyMesh::handleCommand(const char* command) {
while (*command == ' ') command++; // skip leading space
if (strcmp(command, "erase") == 0) {
bool success = _store->formatFileSystem();
if (success) {
Serial.println(" > erase done");
} else {
Serial.println(" Error: erase failed");
}
} else if (strcmp(cli_command, "reboot") == 0) {
board.reboot(); // doesn't return
} else {
Serial.println(" Error: unknown command");
}
}
void MyMesh::checkCLIRescueCmd() { void MyMesh::checkCLIRescueCmd() {
int len = strlen(cli_command); int len = strlen(cli_command);
while (Serial.available() && len < sizeof(cli_command)-1) { while (Serial.available() && len < sizeof(cli_command)-1) {

3
examples/companion_radio/MyMesh.h

@ -92,6 +92,8 @@ public:
void startInterface(BaseSerialInterface &serial); void startInterface(BaseSerialInterface &serial);
const char *getNodeName(); const char *getNodeName();
const char *getFirmwareVer();
const char *getBuildDate();
NodePrefs *getNodePrefs(); NodePrefs *getNodePrefs();
uint32_t getBLEPin(); uint32_t getBLEPin();
@ -158,6 +160,7 @@ protected:
public: public:
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
void handleCommand(const char* command);
private: private:
void writeOKFrame(); void writeOKFrame();

249
examples/companion_radio/main.cpp

@ -2,6 +2,77 @@
#include <Mesh.h> #include <Mesh.h>
#include "MyMesh.h" #include "MyMesh.h"
#include "esp_log.h"
#include <lvgl.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include "../src/fonts/fonts.h"
#include "../lvgl/lvgl.h"
#include "../include/uiExternals.h"
#include "../include/lgfx.h"
#include "../include/uiDefines.h"
#include "../include/uiManager.h"
#include "../include/uiTasks.h"
#include "uiTouch.h"
#define TAG "main"
static uint32_t screenWidth;
static uint32_t screenHeight;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t disp_draw_buf[800 * 480 / 10];
//static lv_color_t disp_draw_buf;
static lv_disp_drv_t disp_drv;
UIManager *uiManager;
SemaphoreHandle_t semaphoreData;
TwoWire I2Cone = TwoWire(0);
#ifndef SEEED_SENSECAP_INDICATOR
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &I2Cone, OLED_RESET);
#endif
SPIClass& spi = SPI;
uint16_t touchCalibration_x0 = 300, touchCalibration_x1 = 3600, touchCalibration_y0 = 300, touchCalibration_y1 = 3600;
uint8_t touchCalibration_rotate = 1, touchCalibration_invert_x = 2, touchCalibration_invert_y = 0;
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
//lcd.fillScreen(TFT_WHITE);
#if (LV_COLOR_16_SWAP != 0)
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);
#else
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);//
#endif
lv_disp_flush_ready(disp);
}
void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
{
uint16_t x, y;
if (lcd.getTouch(&x, &y))
{
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}
// Believe it or not, this std C function is busted on some platforms! // Believe it or not, this std C function is busted on some platforms!
static uint32_t _atoi(const char* sp) { static uint32_t _atoi(const char* sp) {
uint32_t n = 0; uint32_t n = 0;
@ -86,17 +157,17 @@ static uint32_t _atoi(const char* sp) {
#endif #endif
/* GLOBAL OBJECTS */ /* GLOBAL OBJECTS */
#ifdef DISPLAY_CLASS // #ifdef DISPLAY_CLASS
#include "UITask.h" // #include "UITask.h"
UITask ui_task(&board, &serial_interface); // UITask ui_task(&board, &serial_interface);
#endif // #endif
StdRNG fast_rng; StdRNG fast_rng;
SimpleMeshTables tables; SimpleMeshTables tables;
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
#ifdef DISPLAY_CLASS // #ifdef DISPLAY_CLASS
, &ui_task // , &ui_task
#endif // #endif
); );
/* END GLOBAL OBJECTS */ /* END GLOBAL OBJECTS */
@ -105,23 +176,106 @@ void halt() {
while (1) ; while (1) ;
} }
void configureDisplay() {
ESP_LOGI(TAG, "Configuring display...");
screenWidth = lcd.width();
screenHeight = lcd.height();
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, screenWidth * screenHeight / 10);
/* Initialize the display */
lv_disp_drv_init(&disp_drv);
/* Change the following line to your display resolution */
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
/* Initialize the (dummy) input device driver */
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
#ifdef TFT_BL
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
#endif
lcd.fillScreen(0x000000u);
}
void initializeDisplay() {
ESP_LOGI(TAG, "Initializing display...");
lcd.begin();
lcd.fillScreen(0x000000u);
lcd.setTextSize(2);
lcd.setRotation(1);
//lcd.setBrightness(127);
}
void initializeTouchScreen() {
ESP_LOGI(TAG, "Initializing touch screen...");
touch_init();
}
void initializeLVGL() {
ESP_LOGI(TAG, "Initializing LVGL...");
lv_init();
}
void initializeUI() {
Serial.println("initialize UI...");
/*
#ifdef ENABLE_STARTUP_LOGO
lv_disp_load_scr(ui_ScreenLogo);
for( int i=0; i < 100; i++ ){
lv_task_handler();
delay(10);
}
lv_scr_load_anim(ui_Screen1, LV_SCR_LOAD_ANIM_MOVE_TOP, 500, 0, true);
#else
ui_init();
#endif
*/
uiManager = new UIManager();
uiManager->setNightMode(false);
}
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
board.begin(); board.begin();
#ifdef DISPLAY_CLASS #ifdef SEEED_SENSECAP_INDICATOR
DisplayDriver* disp = NULL; initializeDisplay();
if (display.begin()) { delay(200);
disp = &display;
disp->startFrame(); initializeLVGL();
#ifdef ST7789 initializeTouchScreen();
disp->setTextSize(2); configureDisplay();
#endif
disp->drawTextCentered(disp->width() / 2, 28, "Loading..."); initializeUI();
disp->endFrame();
} createTasks();
lv_timer_handler();
#endif #endif
// #ifdef DISPLAY_CLASS
// DisplayDriver* disp = NULL;
// if (display.begin()) {
// disp = &display;
// disp->startFrame();
// #ifdef ST7789
// disp->setTextSize(2);
// #endif
// disp->drawTextCentered(disp->width() / 2, 28, "Loading...");
// disp->endFrame();
// }
// #endif
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
@ -186,11 +340,11 @@ void setup() {
SPIFFS.begin(true); SPIFFS.begin(true);
store.begin(); store.begin();
the_mesh.begin( the_mesh.begin(
#ifdef DISPLAY_CLASS // #ifdef DISPLAY_CLASS
disp != NULL // disp != NULL
#else // #else
false false
#endif //#endif
); );
#ifdef WIFI_SSID #ifdef WIFI_SSID
@ -212,16 +366,49 @@ void setup() {
sensors.begin(); sensors.begin();
#ifdef DISPLAY_CLASS // #ifdef DISPLAY_CLASS
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved // ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
#endif // #endif
vTaskResume(t_core1_core);
Serial.println("Setup completed");
the_mesh.advert();
Serial.print("MeshCore ");
Serial.println(the_mesh.getFirmwareVer());
Serial.print("Build date: ");
Serial.println(the_mesh.getBuildDate());
}
// void loop() {
// the_mesh.loop();
// sensors.loop();
// // #ifdef DISPLAY_CLASS
// // ui_task.loop();
// // #endif
// rtc_clock.tick();
// }
void core_task(void *pvParameters) {
vTaskSuspend(NULL);
ESP_LOGI(TAG, "MeshCore: Task running on core %d", xPortGetCoreID());
while (1) {
the_mesh.loop();
sensors.loop();
rtc_clock.tick();
vTaskDelay(DELAY_CORE_TASK / portTICK_PERIOD_MS);
}
} }
void loop() { void loop() {
the_mesh.loop(); vTaskDelete(NULL);
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
rtc_clock.tick();
} }
void handleCommand(const char* command) {
the_mesh.handleCommand(command);
}

25
examples/companion_radio/task_clock.cpp

@ -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);
}
}

19
examples/companion_radio/task_lvgl.cpp

@ -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);
}
}

797
examples/companion_radio/uiManager.cpp

@ -0,0 +1,797 @@
#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(&timestamp);
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
// ============================
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);
// ============================
// Text column
// ============================
int text_x = PAD + AVATAR + PAD;
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
// ============================
LvObj(avatar, true).clickable(false);
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 = ui_TabView1.addTab("Home");
ui_TabPageContacts = ui_TabView1.addTab("Contacts");
ui_TabPageChannels = ui_TabView1.addTab("Channels");
ui_TabPageSettings = ui_TabView1.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
}
}

50
examples/companion_radio/uiTasks.cpp

@ -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);
}

4
examples/simple_secure_chat_ui/uiManager.cpp

@ -23,7 +23,7 @@ const char *UIManager::months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
#define TAG "UIManager" #define TAG "UIManager"
extern void handleCommand(char *msg); //extern void handleCommand(char *msg);
UIManager::UIManager() { UIManager::UIManager() {
@ -517,7 +517,7 @@ void UIManager::onSendClick(lv_event_t* e)
lv_textarea_set_text(ui_ChannelInput, ""); lv_textarea_set_text(ui_ChannelInput, "");
snprintf(fullMessage, sizeof(fullMessage), "public %s", msgCopy); snprintf(fullMessage, sizeof(fullMessage), "public %s", msgCopy);
handleCommand(fullMessage); //handleCommand(fullMessage);
char time_buf[16]; char time_buf[16];
time_t now = time(NULL); time_t now = time(NULL);

3
variants/sensecap_indicator-espnow/platformio.ini

@ -51,7 +51,8 @@ build_flags =
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1
build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter}
+<../examples/simple_secure_chat_ui/*.cpp> +<../examples/companion_radio/*.cpp>
;+<../examples/simple_secure_chat_ui/*.cpp>
+<fonts/*.c> +<fonts/*.c>
+<UI/*.c> +<UI/*.c>
lib_deps = lib_deps =

Loading…
Cancel
Save