diff --git a/.gitignore b/.gitignore index 350197cf0..3c3dec0d1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ compile_commands.json .venv/ venv/ examples/simple_secure_chat_ui/UI_PLAN.md +clock_sync_phase1_summary.md diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 519271a0f..cd5cee73d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -357,7 +357,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(p->path, path, p->path_len); } - if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[] + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // contact is in contacts[], schedule lazy write } static int sort_by_recent(const void *a, const void *b) { diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 018ec2a20..d793bedfd 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -158,13 +158,13 @@ class MyMesh : public BaseChatMesh, ContactVisitor { } void setClock(uint32_t timestamp) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (timestamp > curr) { + //uint32_t curr = getRTCClock()->getCurrentTime(); + //if (timestamp > curr) { getRTCClock()->setCurrentTime(timestamp); Serial.println(" (OK - clock set!)"); - } else { - Serial.println(" (ERR: clock cannot go backwards)"); - } + //} else { + // Serial.println(" (ERR: clock cannot go backwards)"); + //} } void importCard(const char* command) { diff --git a/examples/simple_secure_chat_ui/main.cpp b/examples/simple_secure_chat_ui/main.cpp index 14ed83374..7d9963760 100644 --- a/examples/simple_secure_chat_ui/main.cpp +++ b/examples/simple_secure_chat_ui/main.cpp @@ -1,5 +1,7 @@ #include // needed for PlatformIO #include +#include +#include #if defined(NRF52_PLATFORM) #include @@ -81,6 +83,8 @@ UIManager *uiManager; SemaphoreHandle_t semaphoreData; +volatile bool g_clock_synced = false; + TwoWire I2Cone = TwoWire(0); #ifndef SEEED_SENSECAP_INDICATOR Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &I2Cone, OLED_RESET); @@ -374,14 +378,13 @@ class MyMesh : public BaseChatMesh, ContactVisitor { ContactInfo c; uint8_t pub_key[32]; uint8_t unused; - uint32_t reserved; bool success = (file.read(pub_key, 32) == 32); success = success && (file.read((uint8_t *) &c.name, 32) == 32); success = success && (file.read(&c.type, 1) == 1); success = success && (file.read(&c.flags, 1) == 1); success = success && (file.read(&unused, 1) == 1); - success = success && (file.read((uint8_t *) &reserved, 4) == 4); + success = success && (file.read((uint8_t *) &c.lastmod, 4) == 4); success = success && (file.read((uint8_t *) &c.out_path_len, 1) == 1); success = success && (file.read((uint8_t *) &c.last_advert_timestamp, 4) == 4); success = success && (file.read(c.out_path, 64) == 64); @@ -390,7 +393,6 @@ class MyMesh : public BaseChatMesh, ContactVisitor { if (!success) break; // EOF c.id = mesh::Identity(pub_key); - c.lastmod = 0; uiManager->addContactToUI(c); if (!addContact(c)) full = true; } @@ -412,7 +414,6 @@ class MyMesh : public BaseChatMesh, ContactVisitor { ContactsIterator iter; ContactInfo c; uint8_t unused = 0; - uint32_t reserved = 0; while (iter.hasNext(this, c)) { bool success = (file.write(c.id.pub_key, 32) == 32); @@ -420,7 +421,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { success = success && (file.write(&c.type, 1) == 1); success = success && (file.write(&c.flags, 1) == 1); success = success && (file.write(&unused, 1) == 1); - success = success && (file.write((uint8_t *) &reserved, 4) == 4); + success = success && (file.write((uint8_t *) &c.lastmod, 4) == 4); success = success && (file.write((uint8_t *) &c.out_path_len, 1) == 1); success = success && (file.write((uint8_t *) &c.last_advert_timestamp, 4) == 4); success = success && (file.write(c.out_path, 64) == 64); @@ -428,17 +429,20 @@ class MyMesh : public BaseChatMesh, ContactVisitor { if (!success) break; // write failed } file.close(); - uiManager->addContactToUI(c); } } void setClock(uint32_t timestamp) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (timestamp > curr) { - getRTCClock()->setCurrentTime(timestamp); - Serial.println(" (OK - clock set!)"); - } else { - Serial.println(" (ERR: clock cannot go backwards)"); + getRTCClock()->setCurrentTime(timestamp); + struct timeval tv = { .tv_sec = (time_t)timestamp, .tv_usec = 0 }; + settimeofday(&tv, nullptr); + g_clock_synced = true; + Serial.println(" (OK - clock set!)"); + if (uiManager) { + time_t ts = (time_t)timestamp; + struct tm t; + localtime_r(&ts, &t); + uiManager->updateDateTime(t); } } @@ -478,12 +482,15 @@ protected: } void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) override { - // TODO: if not in favs, prompt to add as fav(?) - Serial.printf("ADVERT from -> %s\n", contact.name); Serial.printf(" type: %s\n", getTypeName(contact.type)); Serial.print(" public key: "); mesh::Utils::printHex(Serial, contact.id.pub_key, PUB_KEY_SIZE); Serial.println(); + if (is_new) { + uiManager->addContactToUI(contact); + } else { + uiManager->updateContactLastSeen(contact.id.pub_key, contact.lastmod); + } saveContacts(); } @@ -511,6 +518,8 @@ protected: Serial.printf("(%s) MSG -> from %s\n", pkt->isRouteDirect() ? "DIRECT" : "FLOOD", from.name); Serial.printf(" %s\n", text); + setClock(sender_timestamp); + if (strcmp(text, "clock sync") == 0) { // special text command setClock(sender_timestamp + 1); } @@ -537,6 +546,8 @@ protected: } Serial.printf(" %s\n", text); + setClock(timestamp); + // Only public? // if (strcmp(channel.secret, PUBLIC_GROUP_PSK) != 0) // return; @@ -1055,6 +1066,11 @@ void initializeMesh() { void setup() { Serial.begin(115200); + // Greece: UTC+2 standard (EET), UTC+3 summer (EEST) + // Change this string to match your timezone if needed. + setenv("TZ", "EET-2EEST,M3.5.0/3,M10.5.0/4", 1); + tzset(); + #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif diff --git a/examples/simple_secure_chat_ui/task_clock.cpp b/examples/simple_secure_chat_ui/task_clock.cpp index 752a5147c..5a3cbbe47 100644 --- a/examples/simple_secure_chat_ui/task_clock.cpp +++ b/examples/simple_secure_chat_ui/task_clock.cpp @@ -1,4 +1,5 @@ #include +#include #include "esp_log.h" #include "uiDefines.h" @@ -8,18 +9,17 @@ 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(); + 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); } } \ No newline at end of file diff --git a/examples/simple_secure_chat_ui/uiManager.cpp b/examples/simple_secure_chat_ui/uiManager.cpp index 7c2df803e..6b67a532f 100644 --- a/examples/simple_secure_chat_ui/uiManager.cpp +++ b/examples/simple_secure_chat_ui/uiManager.cpp @@ -31,6 +31,7 @@ struct UIContactInfo { ContactInfo info; lv_obj_t* badge = nullptr; lv_obj_t* badge_label = nullptr; + lv_obj_t* label_lastseen = nullptr; uint32_t unread = 0; }; @@ -201,11 +202,11 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch lv_obj_set_style_bg_color(bubble, is_self ? lv_color_hex(0x1E88E5) : lv_color_hex(0x2C2C2C), 0); - // IMPORTANT: vertical layout inside bubble + // Vertical layout inside bubble; cross-align follows bubble side lv_obj_set_flex_flow(bubble, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(bubble, LV_FLEX_ALIGN_START, - LV_FLEX_ALIGN_START, + is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); // Header row (sender + time) @@ -219,8 +220,8 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch 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, // Ευθυγράμμιση ονόματος/ώρας στον κάθετο άξονα + is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); lv_obj_t *lbl_sender = lv_label_create(hdr); @@ -239,6 +240,7 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch 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_align(lbl_msg, is_self ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0); 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); @@ -253,11 +255,11 @@ void UIManager::addChatBubble(const char *time_str, const char *sender, const ch } 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) + // 1. Row container – pushes bubble to the correct side + lv_obj_t* row = LvObj(ui_ContactMessages) .width(lv_pct(100)) .height(LV_SIZE_CONTENT) .bgOpa(0) @@ -266,59 +268,54 @@ void UIManager::addPrivateChatBubble(const char *time_str, const char *msg, bool .scrollable(false) .flexFlow(LV_FLEX_FLOW_ROW) .flexAlign( - is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, + is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, // sent=right, received=left 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); + // 2. Bubble column: message text + timestamp below + 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_COLUMN) + .flexAlign( + LV_FLEX_ALIGN_START, + is_self ? LV_FLEX_ALIGN_END : LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_START + ); + lv_obj_set_style_pad_row(aligner, 2, 0); - // Long mode για wrap + // 3. Message bubble label + lv_obj_t *lbl_msg = lv_label_create(aligner); 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, 270); // max πλάτος - lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT); // αυτόματο ύψος - - // Bubble style + lv_obj_set_width(lbl_msg, 175); // max width – fits in 310px panel with room to shift + lv_obj_set_height(lbl_msg, LV_SIZE_CONTENT); + lv_obj_set_style_text_align(lbl_msg, is_self ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0); 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); + lv_obj_set_style_pad_all(lbl_msg, 10, 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); + 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); + lv_obj_set_style_bg_color(lbl_msg, lv_color_hex(0x2C2C2C), 0); + lv_obj_set_style_text_color(lbl_msg, lv_color_hex(0xFFFFFF), 0); } - // 4. Η Ώρα + // 4. Timestamp below the bubble 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); + lv_obj_scroll_to_view(row, LV_ANIM_OFF); } void UIManager::getInitials(const char *name, char *out) { @@ -511,13 +508,15 @@ void UIManager::addContactToUI(ContactInfo c) char lastSeen[32]; formatLastSeen(c.lastmod, lastSeen, sizeof(lastSeen)); - LvLabel(text_col) + lv_obj_t* lbl_ls = LvLabel(text_col) .text(lastSeen) .position(0, PAD + 32) .width(text_w) .font(&lv_font_arial_16) .textColor(0x888888) - .wrap(false); + .wrap(false) + .raw(); + store->label_lastseen = lbl_ls; // ============================ // Unread badge (top-right corner of row) @@ -551,6 +550,16 @@ void UIManager::addContactToUI(ContactInfo c) LvObj(text_col, true).clickable(false); } +void UIManager::updateContactLastSeen(const uint8_t* pub_key, uint32_t lastmod) +{ + UIContactInfo* uic = findContactByPubKey(ui_Contacts, pub_key); + if (!uic || !uic->label_lastseen) return; + uic->info.lastmod = lastmod; + char buf[32]; + formatLastSeen(lastmod, buf, sizeof(buf)); + lv_label_set_text(uic->label_lastseen, buf); +} + void UIManager::onShowKeyboard() { LvKeyboard(ui_Keyboard, true).show(true); diff --git a/include/uiManager.h b/include/uiManager.h index a0368066f..26a8c2b87 100644 --- a/include/uiManager.h +++ b/include/uiManager.h @@ -126,6 +126,7 @@ class UIManager { void addPrivateChatBubble(const char *time_str, const char *msg, bool is_self); void addChatBubble(const char *time_str, const char *sender, const char *msg,bool is_self); void addContactToUI(ContactInfo c); + void updateContactLastSeen(const uint8_t* pub_key, uint32_t lastmod); void handleContactClick(lv_event_t *e); void setNightMode(bool night); }; diff --git a/include/uiVars.h b/include/uiVars.h index 84069e152..a04ff6ac1 100644 --- a/include/uiVars.h +++ b/include/uiVars.h @@ -17,4 +17,6 @@ extern SemaphoreHandle_t semaphoreData; extern UIManager *uiManager; extern lv_obj_t * ui_MainTabView; +extern volatile bool g_clock_synced; + #endif \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index aebfc1b64..e447c2cda 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -133,7 +133,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - bool is_new = false; // true = not in contacts[], false = exists in contacts[] + bool is_new = false; // true = newly discovered (not previously in contacts[]), false = existing contact if (from == NULL) { if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; @@ -151,10 +151,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - + populateContactFromAdvert(*from, id, parser, timestamp); from->sync_since = 0; from->shared_secret_valid = false; + is_new = true; // slot was just allocated for a brand-new contact } // update StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 42198b498..219c8d81d 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -205,15 +205,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first strcpy(reply, "OK - Advert sent"); } else if (memcmp(command, "clock sync", 10) == 0) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (sender_timestamp > curr) { + //uint32_t curr = getRTCClock()->getCurrentTime(); + //if (sender_timestamp > curr) { getRTCClock()->setCurrentTime(sender_timestamp + 1); uint32_t now = getRTCClock()->getCurrentTime(); DateTime dt = DateTime(now); sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else { - strcpy(reply, "ERR: clock cannot go backwards"); - } + //} else { + // strcpy(reply, "ERR: clock cannot go backwards"); + //} } else if (memcmp(command, "start ota", 9) == 0) { if (!_board->startOTAUpdate(_prefs->node_name, reply)) { strcpy(reply, "Error"); @@ -224,15 +224,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) uint32_t secs = _atoi(&command[5]); - uint32_t curr = getRTCClock()->getCurrentTime(); - if (secs > curr) { + //int32_t curr = getRTCClock()->getCurrentTime(); + //if (secs > curr) { getRTCClock()->setCurrentTime(secs); uint32_t now = getRTCClock()->getCurrentTime(); DateTime dt = DateTime(now); sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else { - strcpy(reply, "(ERR: clock cannot go backwards)"); - } + //} else { + // strcpy(reply, "(ERR: clock cannot go backwards)"); + //} } else if (memcmp(command, "neighbors", 9) == 0) { _callbacks->formatNeighborsReply(reply); } else if (memcmp(command, "neighbor.remove ", 16) == 0) {