From bb6b0e4d73a05da34a6faf3dc099fc86003054dc Mon Sep 17 00:00:00 2001 From: my Date: Wed, 23 Sep 2026 15:18:28 +0200 Subject: [PATCH] code cleanup. Moved the original mesh code to own file. --- src/idf_component.yml | 2 + src/idf_component.yml.orig | 2 + src/meshberry/EvilChatMesh.cpp | 375 ++++++++++++++++++++ src/meshberry/EvilChatMesh.h | 62 ++++ src/meshberry/chat.cpp | 617 ++------------------------------- src/meshberry/chat.h | 10 +- src/meshberry/notes.cpp | 183 ++++++++++ src/meshberry/notes.h | 12 + src/meshberry/settings.cpp | 2 +- 9 files changed, 676 insertions(+), 589 deletions(-) create mode 100644 src/idf_component.yml create mode 100644 src/idf_component.yml.orig create mode 100644 src/meshberry/EvilChatMesh.cpp create mode 100644 src/meshberry/EvilChatMesh.h create mode 100644 src/meshberry/notes.cpp create mode 100644 src/meshberry/notes.h diff --git a/src/idf_component.yml b/src/idf_component.yml new file mode 100644 index 0000000..d752765 --- /dev/null +++ b/src/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + idf: '>=5.1' diff --git a/src/idf_component.yml.orig b/src/idf_component.yml.orig new file mode 100644 index 0000000..d752765 --- /dev/null +++ b/src/idf_component.yml.orig @@ -0,0 +1,2 @@ +dependencies: + idf: '>=5.1' diff --git a/src/meshberry/EvilChatMesh.cpp b/src/meshberry/EvilChatMesh.cpp new file mode 100644 index 0000000..174d664 --- /dev/null +++ b/src/meshberry/EvilChatMesh.cpp @@ -0,0 +1,375 @@ +#include "EvilChatMesh.h" +#include "chat.h" // currentNick, appMode, chat_add_message() +#include "ui.h" +#include "sound.h" +#include +#include +#include + +/* +============================================================================================================================ +EvilChatMesh +============================================================================================================================ +*/ + +/* ---------- VARIABLES GLOBALES ---------- */ +std::vector nodesConnected; +int nodesCount = 0; + +static std::vector> seenMessageIds; +int seenIndex = 0; + +uint8_t broadcastAddressEspNow[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + +// Replaced by the LVGL UI +//struct Message { +// char content[MAX_MSG_LEN]; +//}; +// static std::vector messages; +int messageCount = 0; +int messageHead = 0; + +/* Timings */ +unsigned long lastPing = 0; + +/* ---------- BUFFER SÉCURISÉ POUR LE CALLBACK ---------- */ +volatile bool newPacketPending = false; +volatile uint8_t packetBuffer[sizeof(MeshMessage)+sizeof(uint16_t)]; +volatile int packetLen = 0; + +// Sets up ESP-NOW, WiFi, and the presence/dedup state. Was inlined in +// chat_init(); pulled out here so seenMessageIds/nodesConnected stay +// private to this file. +void mesh_init() { + nodesConnected.clear(); nodesConnected.resize(MAX_NODES); + seenMessageIds.resize(MAX_HISTORY); + for (auto& e : seenMessageIds) e.fill(0); + + WiFi.persistent(false); + WiFi.mode(WIFI_STA); + WiFi.setSleep(false); + + if (esp_now_init() != ESP_OK) { + Serial.println("[mesh] ESP-NOW init failed"); + ui_push_message("", "ESP-NOW init failed!", false, true); + return; + } + esp_now_register_recv_cb(OnDataRecvChat); + + esp_now_peer_info_t peer = {}; + memcpy(peer.peer_addr, broadcastAddressEspNow, 6); + peer.channel = 1; // relay uses channel 1; original was 0 + peer.encrypt = false; + if (!esp_now_is_peer_exist(peer.peer_addr)) + esp_now_add_peer(&peer); +} + +/* ---------- CALLBACK ESP‑NOW ULTRA‑COURT ---------- */ + +void IRAM_ATTR OnDataRecvChat( + const esp_now_recv_info_t* recv_info, + const uint8_t* incomingData, int len +//#else // IDF4 signature, [removed] +// const uint8_t* mac, +// const uint8_t* incomingData, int len +//#endif +) +{ + const uint8_t* mac = recv_info ? recv_info->src_addr : nullptr; + (void)mac; + if (len > (int)sizeof(packetBuffer)) return; // paquet trop gros + memcpy((void*)packetBuffer, incomingData, len); + packetLen = len; + newPacketPending = true; // flag pour la loop() +} + +// ----- Hash pour dé‑duplication ----- +void simpleHash(const char* s, char* outHash) { + uint32_t h = 5381; + while (*s) h = ((h << 5) + h) + (uint8_t)(*s++); + snprintf(outHash, 9, "%08X", h); // 8 hex, zéro‑terminé +} + +// ----- CRC16 (CRC‑CCITT) ----- +uint16_t crc16(const uint8_t* data, size_t len) { + uint16_t crc = 0xFFFF; + for (size_t i = 0; i < len; ++i) { + crc ^= (uint16_t)data[i] << 8; + for (int j = 0; j < 8; ++j) + crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1); + } + return crc; +} + +// ----- Historique d'ID reçus ----- +bool hasSeenId(const char* id) { + if ((int)seenMessageIds.size() < MAX_HISTORY) return false; + for (int i = 0; i < MAX_HISTORY; ++i) + if (strncmp(seenMessageIds[i].data(), id, 8) == 0) return true; + return false; +} +void rememberId(const char* id) { + if ((int)seenMessageIds.size() < MAX_HISTORY) seenMessageIds.resize(MAX_HISTORY); + strncpy(seenMessageIds[seenIndex].data(), id, 8); + seenMessageIds[seenIndex].data()[8] = '\0'; + seenIndex = (seenIndex + 1) % MAX_HISTORY; +} + +// ----- Buffer circulaire de messages pour l'écran ----- +//void addMessage(const char* m) { +// if (messages.size() < 20) messages.assign(20, Message()); +// strncpy(messages[messageHead].content, m, MAX_MSG_LEN - 1); +// messages[messageHead].content[MAX_MSG_LEN - 1] = '\0'; +// messageHead = (messageHead + 1) % 20; +// if (messageCount < 20) messageCount++; +//} + +// rafraîchit l'écran +//void drawChatWindow() { +// M5.Display.fillScreen(TFT_BLACK); +// M5.Display.setTextColor(TFT_GREEN); +// M5.Display.setCursor(5, 5); +// M5.Display.printf("Connected: %d", nodesCount); +// +// M5.Display.setTextColor(TFT_WHITE); +// M5.Display.setCursor(5, 10); +// +// const int lines = 13; +// int start = (messageCount < lines) ? 0 : (messageHead + 20 - lines) % 20; +// for (int i = 0; i < min(lines, messageCount); ++i) { +// int idx = (start + i) % 20; +// M5.Display.println(messages[idx].content); +// } +// M5.Display.display(); +//} + +/* ---------- Traitement d'un paquet en attente ---------- */ +void handleIncomingPacket(const uint8_t* data, int len) { + if (len != (int)(sizeof(MeshMessage) + sizeof(uint16_t))) return; + + uint16_t rxCrc; + memcpy(&rxCrc, data + sizeof(MeshMessage), sizeof(uint16_t)); + if (rxCrc != crc16(data, sizeof(MeshMessage))) return; + + MeshMessage msg; + memcpy(&msg, data, sizeof(MeshMessage)); + + if (hasSeenId(msg.id)) return; + rememberId(msg.id); + + // --- Gestion du PING (mise à jour de présence) --- + if (strcmp(msg.body, "PING") == 0) { + updatePresence(msg.from); + esp_now_send(broadcastAddressEspNow, data, len); + return; // pas d'affichage + } + // --- Filtre messages "System" visant ce client --- + if (!strcmp(msg.from, "System") && strstr(msg.body, currentNick)) + return; + + // addMessage(line) + drawChatWindow() replaced: + //char line[MAX_MSG_LEN + 16]; + //snprintf(line, sizeof(line), "%s: %s", msg.from, msg.body); + //addMessage(line); + //drawChatWindow(); + bool is_system = !strcmp(msg.from, "System"); + chat_add_message(is_system ? "" : msg.from, msg.body, false, is_system); + if (appMode == AppMode::CHAT) + ui_push_message(is_system ? "" : msg.from, msg.body, false, is_system); + is_system ? sound_system() : sound_msg_in(); + + esp_now_send(broadcastAddressEspNow, data, len); +} + +/* ----- Envoi d'un message Chat (hors callback) ----- */ +void broadcastChatMessage(const char* from, const char* body) { + MeshMessage msg; + char hash[9], tmp[MAX_MSG_LEN+32]; + snprintf(tmp, sizeof(tmp), "%s%s%lu", from, body, millis()); + simpleHash(tmp, hash); + + strncpy(msg.id, hash, 8); msg.id[8] = '\0'; + strncpy(msg.from, from, 15); msg.from[15] = '\0'; + strncpy(msg.body, body, MAX_MSG_LEN-1); msg.body[MAX_MSG_LEN-1]='\0'; + + uint8_t pkt[sizeof(MeshMessage)+2]; + memcpy(pkt, &msg, sizeof(MeshMessage)); + uint16_t crc = crc16((uint8_t*)&msg, sizeof(MeshMessage)); + memcpy(pkt + sizeof(MeshMessage), &crc, 2); + + esp_now_send(broadcastAddressEspNow, pkt, sizeof(pkt)); + rememberId(msg.id); + + bool is_system = !strcmp(from, "System"); + bool is_own = !is_system && !strcmp(from, currentNick); + chat_add_message(is_system ? "" : from, body, is_own, is_system); + if (appMode == AppMode::CHAT) + ui_push_message(is_system ? "" : from, body, is_own, is_system); +} + +/* ----- Ping périodique ----- */ +void broadcastPing() { + MeshMessage msg; + char hash[9], tmp[32]; + + // hash = from + "PING" + horodatage + snprintf(tmp, sizeof(tmp), "%sPING%lu", currentNick, millis()); + simpleHash(tmp, hash); + + strncpy(msg.id, hash, 8); msg.id[8] = '\0'; + strncpy(msg.from, currentNick, 15); msg.from[15] = '\0'; + strncpy(msg.body, "PING", MAX_MSG_LEN - 1); + msg.body[MAX_MSG_LEN - 1] = '\0'; + + uint8_t pkt[sizeof(MeshMessage) + 2]; + memcpy(pkt, &msg, sizeof(MeshMessage)); + uint16_t crc = crc16((uint8_t*)&msg, sizeof(MeshMessage)); + memcpy(pkt + sizeof(MeshMessage), &crc, 2); + + esp_now_send(broadcastAddressEspNow, pkt, sizeof(pkt)); + rememberId(msg.id); // pour ignorer notre propre écho +} + + +/* ----- Gestion de présence ----- */ +void updatePresence(const char* nick) { + for (int i = 0; i < nodesCount; ++i) + if (!strcmp(nodesConnected[i].nick, nick)) { + nodesConnected[i].lastSeen = millis(); return; + } + + if (nodesCount < MAX_NODES) { + if ((int)nodesConnected.size() < MAX_NODES) nodesConnected.resize(MAX_NODES); + strncpy(nodesConnected[nodesCount].nick, nick, 15); + nodesConnected[nodesCount].nick[15] = '\0'; + nodesConnected[nodesCount].lastSeen = millis(); + ++nodesCount; + } +} +void checkPresenceTimeouts() { + unsigned long now = millis(); + for (int i = 0; i < nodesCount; ++i) + if ((now - nodesConnected[i].lastSeen) > TIMEOUT_PRESENCE) { + char m[MAX_MSG_LEN]; + snprintf(m, sizeof(m), "%s left the chat!", nodesConnected[i].nick); + // broadcastChatMessage("System", m); // lets not broadcast this, but: + ui_push_message("", m, false, true); + + + for (int j = i; j < nodesCount-1; ++j) + nodesConnected[j] = nodesConnected[j+1]; + --nodesCount; --i; + } +} + + +// ----- Gestion clavier & saisie utilisateur ----- +// handleKeyboard() removed, M5Cardputer-specific; logic moved into chat_tick(char key) +//void handleKeyboard() { +// if (!kpAny()) return; +// +// Keyboard_Class::KeysState st = M5Cardputer.Keyboard.keysState(); +// +// /* Ajout des caractères tapés */ +// for (char c : st.word) { +// int n = strlen(inputBuffer); +// if (n < MAX_MSG_LEN - 2) { // -2 : place pour '\0' +// inputBuffer[n] = c; +// inputBuffer[n+1] = '\0'; +// } +// } +// /* Backspace */ +// if (st.del && *inputBuffer) +// inputBuffer[strlen(inputBuffer) - 1] = '\0'; +// +// /* Entrée : envoi */ +// if (st.enter && *inputBuffer) { +// if (inputBuffer[0] == '/') +// handleCommand(inputBuffer); +// else +// broadcastChatMessage(currentNick, inputBuffer); +// +// inputBuffer[0] = '\0'; +// } +// +// /* Ligne d'édition */ +// M5.Display.fillRect(0, 120, 240, 16, TFT_NAVY); +// M5.Display.setCursor(5, 120); +// M5.Display.setTextColor(TFT_YELLOW); +// M5.Display.print("> "); +// M5.Display.print(inputBuffer); +//} + +// ----- Fonction principale ----- +// EvilChatMesh() split into chat_init() + chat_tick(), no blocking while(true) +//void EvilChatMesh() { +// auto cfg = M5.config(); +// M5.begin(cfg); +// M5.Display.setTextSize(1); +// M5.Display.setTextColor(TFT_WHITE); +// M5.Display.fillScreen(TFT_BLACK); +// +// WiFi.disconnect(true); delay(100); +// WiFi.mode(WIFI_STA); +// +// if (esp_now_init() != ESP_OK) { +// M5.Display.println("ESP‑NOW init failed"); return; +// } +// esp_now_register_recv_cb(OnDataRecvChat); +// +// esp_now_peer_info_t peer = {}; +// memcpy(peer.peer_addr, broadcastAddressEspNow, 6); +// peer.channel = 0; // changed to 1, EvilChatMesh-Relay.ino expects channel 1 +// peer.encrypt = false; +// if (!esp_now_is_peer_exist(peer.peer_addr)) +// esp_now_add_peer(&peer); +// +// nodesConnected.clear(); nodesConnected.resize(MAX_NODES); +// messages.assign(20, Message()); +// drawChatWindow(); +// +// char msgArr[MAX_MSG_LEN]; +// snprintf(msgArr, sizeof(msgArr), "%s enter the chat!", currentNick); +// broadcastChatMessage("System", msgArr); +// +// /* ---------- Boucle ---------- */ +// while (true) { +// M5.update(); cardUpdate(); +// +// /* 1. paquet reçu ? */ +// if (newPacketPending) { +// noInterrupts(); +// uint8_t localBuf[sizeof(MeshMessage)+2]; +// int len = packetLen; +// memcpy(localBuf, (const void*)packetBuffer, len); +// newPacketPending = false; +// interrupts(); +// +// handleIncomingPacket(localBuf, len); +// } +// +// /* 2. ping & timeouts */ +// unsigned long now = millis(); +// if (now - lastPing > PING_INTERVAL) { +// lastPing = now; broadcastPing(); +// } +// checkPresenceTimeouts(); +// +// /* 3. clavier */ +// if (M5Cardputer.Keyboard.isChange()) +// handleKeyboard(); +// +// /* 4. touche ` pour quitter */ +// if (kp('`')) { +// char m[MAX_MSG_LEN]; +// snprintf(m, sizeof(m), "%s left the chat!", currentNick); +// broadcastChatMessage("System", m); +// +// esp_now_unregister_recv_cb(); +// esp_now_deinit(); +// waitAndReturnToMenu("Left the chat..."); +// return; +// } +// delay(10); +// } +//} diff --git a/src/meshberry/EvilChatMesh.h b/src/meshberry/EvilChatMesh.h new file mode 100644 index 0000000..90ba683 --- /dev/null +++ b/src/meshberry/EvilChatMesh.h @@ -0,0 +1,62 @@ +#pragma once + +// EvilChatMesh.cpp ported to ESP32 CYD + M5Stack CardKB +// +// Copied from Evil-Cardputer-v1-5-2.ino +// Original author : 7h30th3r0n3, https://github.com/7h30th3r0n3/Evil-M5Project +// I've tried to keep the original code as it was as much as possible, +// but –some- a lot ui/logic snuck in when adding the BT functonality. +// the mesh logic is unchanged. + +#include +#include +#include +#include + +#define MAX_MSG_LEN 100 +#define MAX_HISTORY 32 +#define MAX_NODES 30 + +struct NodePresence { + char nick[16]; + unsigned long lastSeen; +}; + +struct MeshMessage { + char id[9]; + char from[16]; + char body[MAX_MSG_LEN]; +}; + +/* ---------- VARIABLES GLOBALES ---------- */ +extern std::vector nodesConnected; +extern int nodesCount; + +extern uint8_t broadcastAddressEspNow[6]; + +/* Timings */ +#define TIMEOUT_PRESENCE 2000 +#define PING_INTERVAL 500 +extern unsigned long lastPing; + +/* ---------- BUFFER SÉCURISÉ POUR LE CALLBACK ---------- */ +extern volatile bool newPacketPending; +extern volatile uint8_t packetBuffer[sizeof(MeshMessage) + sizeof(uint16_t)]; +extern volatile int packetLen; + +// Sets up ESP-NOW, WiFi, and the presence/dedup state. Call once from chat_init(). +void mesh_init(); + +void IRAM_ATTR OnDataRecvChat(const esp_now_recv_info_t* recv_info, + const uint8_t* incomingData, int len); + +void simpleHash(const char* s, char* outHash); +uint16_t crc16(const uint8_t* data, size_t len); +bool hasSeenId(const char* id); +void rememberId(const char* id); + +void handleIncomingPacket(const uint8_t* data, int len); +void broadcastChatMessage(const char* from, const char* body); +void broadcastPing(); +void updatePresence(const char* nick); +void checkPresenceTimeouts(); diff --git a/src/meshberry/chat.cpp b/src/meshberry/chat.cpp index 3cac5ed..e7768fa 100644 --- a/src/meshberry/chat.cpp +++ b/src/meshberry/chat.cpp @@ -1,95 +1,43 @@ -// chat.cpp, EvilChatMesh ported to ESP32 CYD + M5Stack CardKB -// -// Original source : Evil-Cardputer-v1-5-2.ino, lines 20480-20871 +// Heavily based on Evil-Cardputer-v1-5-2.ino // Original author : 7h30th3r0n3, https://github.com/7h30th3r0n3/Evil-M5Project -// -// I've tried to keep the original code as much as possible as it was -// but –some- a lot ui/logic snuck in when adding the BT functonality. #include "chat.h" #include "config.h" #include "settings.h" #include "ui.h" #include "sound.h" +#include "EvilChatMesh.h" +#include "notes.h" #include -#include -#include #include // deep sleep #include // gpio_hold_en keeps backlight off during sleep -#include -#include #include -#include -#include - -/* -============================================================================================================================ -EvilChatMesh -============================================================================================================================ -*/ - - -#define MAX_MSG_LEN 100 -#define MAX_HISTORY 32 -#define MAX_NODES 30 - - -struct NodePresence { - char nick[16]; - unsigned long lastSeen; -}; - -struct MeshMessage { - char id[9]; - char from[16]; - char body[MAX_MSG_LEN]; -}; - -// Replaced by the LVGL UI -//struct Message { -// char content[MAX_MSG_LEN]; -//}; - -/* ---------- VARIABLES GLOBALES ---------- */ -static std::vector nodesConnected; -int nodesCount = 0; - -static std::vector> seenMessageIds; -int seenIndex = 0; +/* GLOBALS */ char inputBuffer[MAX_MSG_LEN] = ""; -uint8_t broadcastAddressEspNow[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; - -// static std::vector messages; -int messageCount = 0; -int messageHead = 0; - // init from g_settings.nick -static char currentNick[16]; +char currentNick[16]; -/* ---------- Notes mode / BT dump ---------- */ -enum class AppMode { CHAT, NOTES, BT }; -static AppMode appMode = AppMode::CHAT; -static AppMode modeBeforeBt = AppMode::CHAT; -static BleKeyboard* g_ble = nullptr; +/* Notes mode / BT dump */ +AppMode appMode = AppMode::CHAT; // for redrawing #define MODE_HISTORY_LINES 12 // buffer for chat messages -struct ChatHistEntry { +struct ChatMessage { char nick[CHAT_MAX_NICK_LEN]; char text[MAX_MSG_LEN]; bool is_own; bool is_system; }; -static ChatHistEntry chatHistory[MODE_HISTORY_LINES]; +static ChatMessage chatHistory[MODE_HISTORY_LINES]; static int chatHistCount = 0; static int chatHistNext = 0; // next write slot -static void chat_history_record(const char* nick, const char* text, bool is_own, bool is_system) { - ChatHistEntry& e = chatHistory[chatHistNext]; +void chat_add_message(const char* nick, const char* text, bool is_own, bool is_system) { + ChatMessage& e = chatHistory[chatHistNext]; strncpy(e.nick, nick, sizeof(e.nick) - 1); e.nick[sizeof(e.nick) - 1] = '\0'; strncpy(e.text, text, sizeof(e.text) - 1); @@ -100,303 +48,36 @@ static void chat_history_record(const char* nick, const char* text, bool is_own, if (chatHistCount < MODE_HISTORY_LINES) chatHistCount++; } -static void chat_show_recent() { +static void chat_draw() { int start = (chatHistCount < MODE_HISTORY_LINES) ? 0 : chatHistNext; for (int i = 0; i < chatHistCount; i++) { - ChatHistEntry& e = chatHistory[(start + i) % MODE_HISTORY_LINES]; + ChatMessage& e = chatHistory[(start + i) % MODE_HISTORY_LINES]; ui_push_message(e.is_system ? "" : e.nick, e.text, e.is_own, e.is_system); } } -static char notesBuf[NOTES_HISTORY_LINES][MAX_MSG_LEN]; -static int currNote = 0;// next pos -static int notesCount = 0; -static int notesScrollPos = 0; - -static void notes_to_buffer(const char* line) { - strncpy(notesBuf[currNote], line, MAX_MSG_LEN - 1); - notesBuf[currNote][MAX_MSG_LEN - 1] = '\0'; - currNote = (currNote + 1) % NOTES_HISTORY_LINES; - if (notesCount < NOTES_HISTORY_LINES) notesCount++; -} - -static void notes_load() { - notesCount = 0; currNote = 0; notesScrollPos = 0; - - File f = LittleFS.open(NOTES_FILE, "r"); - if (!f) return; - - char line[MAX_MSG_LEN]; - while (f.available()) { - int n = f.readBytesUntil('\n', line, sizeof(line) - 1); - if (n > 0 && line[n - 1] == '\r') - n--; // remove trailing /r from .println - line[n] = '\0'; - if (n == 0) continue; - notes_to_buffer(line); - } - f.close(); -} - -static void notes_render() { - int visible = UI_VISIBLE_LINES; - int maxScroll = (notesCount > visible) ? (notesCount - visible) : 0; - if (notesScrollPos > maxScroll) notesScrollPos = maxScroll; - if (notesScrollPos < 0) notesScrollPos = 0; - - int windowEnd = notesCount - notesScrollPos; - int windowStart = windowEnd - visible; - if (windowStart < 0) windowStart = 0; - - int start = (notesCount < NOTES_HISTORY_LINES) ? 0 : currNote; - - ui_clear_messages(); - for (int i = windowStart; i < windowEnd; i++) - ui_push_message("", notesBuf[(start + i) % NOTES_HISTORY_LINES], true, false); -} - /* Switch between chat mode (default) adn notes mode. also redraw the UI somewhat */ -static void switch_mode(AppMode m) { +void switch_mode(AppMode m) { appMode = m; ui_clear_messages(); if (m == AppMode::NOTES) { ui_set_header_mode("NOTE MODE"); - notes_load(); - notes_render(); + notes_begin(); } else { ui_set_header(nodesCount, currentNick); - chat_show_recent(); + chat_draw(); } } -/* Timings */ -#define TIMEOUT_PRESENCE 2000 -#define PING_INTERVAL 500 -unsigned long lastPing = 0; - static int lastNodeCount = -1; -/* ---------- BUFFER SÉCURISÉ POUR LE CALLBACK ---------- */ -volatile bool newPacketPending = false; -volatile uint8_t packetBuffer[sizeof(MeshMessage)+sizeof(uint16_t)]; -volatile int packetLen = 0; - /* ---------- PROTOTYPES (déclarés ici pour la loop) ---------- */ -void handleIncomingPacket(const uint8_t* data, int len); -//void drawChatWindow(); // now ui_push_message / ui_set_header -void broadcastPing(); -void updatePresence(const char* nick); -void broadcastChatMessage(const char* from, const char* body); static void go_to_sleep(); -/* ---------- CALLBACK ESP‑NOW ULTRA‑COURT ---------- */ - -void IRAM_ATTR OnDataRecvChat( - const esp_now_recv_info_t* recv_info, - const uint8_t* incomingData, int len -//#else // IDF4 signature, [removed] -// const uint8_t* mac, -// const uint8_t* incomingData, int len -//#endif -) -{ - const uint8_t* mac = recv_info ? recv_info->src_addr : nullptr; - (void)mac; - if (len > (int)sizeof(packetBuffer)) return; // paquet trop gros - memcpy((void*)packetBuffer, incomingData, len); - packetLen = len; - newPacketPending = true; // flag pour la loop() -} - -// ----- Hash pour dé‑duplication ----- -void simpleHash(const char* s, char* outHash) { - uint32_t h = 5381; - while (*s) h = ((h << 5) + h) + (uint8_t)(*s++); - snprintf(outHash, 9, "%08X", h); // 8 hex, zéro‑terminé -} - -// ----- CRC16 (CRC‑CCITT) ----- -uint16_t crc16(const uint8_t* data, size_t len) { - uint16_t crc = 0xFFFF; - for (size_t i = 0; i < len; ++i) { - crc ^= (uint16_t)data[i] << 8; - for (int j = 0; j < 8; ++j) - crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1); - } - return crc; -} - -// ----- Historique d'ID reçus ----- -bool hasSeenId(const char* id) { - if ((int)seenMessageIds.size() < MAX_HISTORY) return false; - for (int i = 0; i < MAX_HISTORY; ++i) - if (strncmp(seenMessageIds[i].data(), id, 8) == 0) return true; - return false; -} -void rememberId(const char* id) { - if ((int)seenMessageIds.size() < MAX_HISTORY) seenMessageIds.resize(MAX_HISTORY); - strncpy(seenMessageIds[seenIndex].data(), id, 8); - seenMessageIds[seenIndex].data()[8] = '\0'; - seenIndex = (seenIndex + 1) % MAX_HISTORY; -} - -// ----- Buffer circulaire de messages pour l'écran ----- -//void addMessage(const char* m) { -// if (messages.size() < 20) messages.assign(20, Message()); -// strncpy(messages[messageHead].content, m, MAX_MSG_LEN - 1); -// messages[messageHead].content[MAX_MSG_LEN - 1] = '\0'; -// messageHead = (messageHead + 1) % 20; -// if (messageCount < 20) messageCount++; -//} - -// rafraîchit l'écran -//void drawChatWindow() { -// M5.Display.fillScreen(TFT_BLACK); -// M5.Display.setTextColor(TFT_GREEN); -// M5.Display.setCursor(5, 5); -// M5.Display.printf("Connected: %d", nodesCount); -// -// M5.Display.setTextColor(TFT_WHITE); -// M5.Display.setCursor(5, 10); -// -// const int lines = 13; -// int start = (messageCount < lines) ? 0 : (messageHead + 20 - lines) % 20; -// for (int i = 0; i < min(lines, messageCount); ++i) { -// int idx = (start + i) % 20; -// M5.Display.println(messages[idx].content); -// } -// M5.Display.display(); -//} - -/* ---------- Traitement d'un paquet en attente ---------- */ -void handleIncomingPacket(const uint8_t* data, int len) { - if (len != (int)(sizeof(MeshMessage) + sizeof(uint16_t))) return; - - uint16_t rxCrc; - memcpy(&rxCrc, data + sizeof(MeshMessage), sizeof(uint16_t)); - if (rxCrc != crc16(data, sizeof(MeshMessage))) return; - - MeshMessage msg; - memcpy(&msg, data, sizeof(MeshMessage)); - - if (hasSeenId(msg.id)) return; - rememberId(msg.id); - - // --- Gestion du PING (mise à jour de présence) --- - if (strcmp(msg.body, "PING") == 0) { - updatePresence(msg.from); - esp_now_send(broadcastAddressEspNow, data, len); - return; // pas d'affichage - } - // --- Filtre messages "System" visant ce client --- - if (!strcmp(msg.from, "System") && strstr(msg.body, currentNick)) - return; - - // addMessage(line) + drawChatWindow() replaced: - //char line[MAX_MSG_LEN + 16]; - //snprintf(line, sizeof(line), "%s: %s", msg.from, msg.body); - //addMessage(line); - //drawChatWindow(); - bool is_system = !strcmp(msg.from, "System"); - chat_history_record(is_system ? "" : msg.from, msg.body, false, is_system); - if (appMode == AppMode::CHAT) - ui_push_message(is_system ? "" : msg.from, msg.body, false, is_system); - is_system ? sound_system() : sound_msg_in(); - - esp_now_send(broadcastAddressEspNow, data, len); -} - -/* ----- Notes mode / BT dump ----- */ - -void chat_set_ble(BleKeyboard* kb) { g_ble = kb; } - -bool chat_is_bt_mode() { return appMode == AppMode::BT; } - -// Append one line to the notes file (created on first write). -static void notes_append(const char* line) { - File f = LittleFS.open(NOTES_FILE, "a"); - if (!f) return; - f.println(line); - f.close(); -} - -static bool btConnectedLast = false; - -static void set_bt_status_message() { - if (!g_ble) { ui_push_message("", "BT unavailable.", false, true); return; } - if (g_ble->isConnected()) { - ui_push_message("", "BT connected. Press FN + ESC to send notes. TAB to exit.", false, true); - } else { - char buf[64]; - snprintf(buf, sizeof(buf), "Pair Bluetooth keyboard: \"%s %s\"", currentNick, "Keyboard"); - ui_push_message("", buf, false, true); - } -} - -// Switch into/out of BT dump mode. /bt command -static void toggle_bt_mode() { - inputBuffer[0] = '\0'; - ui_set_input(inputBuffer); - if (appMode != AppMode::BT) { - modeBeforeBt = appMode; - appMode = AppMode::BT; - ui_set_header_mode("BT MODE"); - btConnectedLast = g_ble && g_ble->isConnected(); - String bleName = String(g_settings.nick) + " Keyboard"; - g_ble->setName(bleName.c_str()); // make sure to use latest nick - if (!btConnectedLast) NimBLEDevice::startAdvertising(); // TODO: not sure if this is still necessary? - set_bt_status_message(); - } else { - switch_mode(modeBeforeBt); - } -} - -/* -BT dump mode: countdown, then replay the notes file as keystrokes. -Blocking while it dump. -*/ -static void bt_dump_start() { - if (!g_ble || !g_ble->isConnected()) { // dump not started. - ui_push_message("", "Not connected yet.", false, true); - return; - } - for (int s = BT_DUMP_COUNTDOWN_S; s > 0; s--) { - char buf[24]; - snprintf(buf, sizeof(buf), "Sending in %d...", s); - ui_push_message("", buf, false, true); - ui_tick(); - delay(1000); - } - ui_push_message("", "Sending...", false, true); - ui_tick(); - - File f = LittleFS.open(NOTES_FILE, "r"); - if (f) { - char line[MAX_MSG_LEN]; - while (f.available()) { - int n = f.readBytesUntil('\n', line, sizeof(line) - 1); - if (n > 0 && line[n - 1] == '\r') n--; // remove \r - line[n] = '\0'; - g_ble->setDelay(30); // look what i found! - g_ble->print(line); // is too fast, drops keypresses on my machine - // for (int i = 0; line[i] != '\0'; i++) - // { - // g_ble->write(line[i]); - // //delay(30); // TODO: maybe make configurable? - // } - // g_ble->print(line); // is too fast, drops keypresses on my machine - g_ble->write(KEY_RETURN); - delay(30); - } - f.close(); - } - ui_push_message("", "Notes sent.", false, true); - toggle_bt_mode(); // done, leave BT mode, restore the previous screen -} - +/* ----- Commandes ----- */ bool apply_setting(String key, String value) { if (key.startsWith("color_")) { @@ -414,7 +95,6 @@ bool apply_setting(String key, String value) { return false; } -/* ----- Commandes ----- */ void handleCommand(const char* in) { if (!strncmp(in, "/nick ", 6)) { strncpy(currentNick, in + 6, 15); currentNick[15] = '\0'; @@ -437,7 +117,7 @@ void handleCommand(const char* in) { } else if (!strncmp(in, "/set ", 5)) { String args = String(in + 5); args.trim(); - + int spaceIdx = args.indexOf(' '); if (spaceIdx == -1) { ui_push_message("", "Usage: /set ", false, true); @@ -466,11 +146,11 @@ void handleCommand(const char* in) { } } else if (!strcmp(in, "/help")) { - //addMessage("/nick : change pseudo"); + //addMessage("/nick : change pseudo"); //addMessage("/me : action roleplay"); //addMessage("/clear : clear the chat"); //addMessage("/people : list connected"); - ui_push_message("", "/nick : change pseudo", false, true); + ui_push_message("", "/nick : change pseudo", false, true); ui_push_message("", "/me : action roleplay", false, true); ui_push_message("", "/clear : clear the chat", false, true); ui_push_message("", "/clearnotes : erase saved notes", false, true); @@ -481,7 +161,7 @@ void handleCommand(const char* in) { ui_push_message("", "TAB : notes mode", false, true); //drawChatWindow(); - } else if (!strcmp(in, "/sleep") || !strcmp(in, "/s")) { + } else if (!strcmp(in, "/sleep") || !strcmp(in, "/s")) { go_to_sleep(); // sleep device. maybe add a button? } else { @@ -490,204 +170,8 @@ void handleCommand(const char* in) { } - -/* ----- Envoi d'un message Chat (hors callback) ----- */ -void broadcastChatMessage(const char* from, const char* body) { - MeshMessage msg; - char hash[9], tmp[MAX_MSG_LEN+32]; - snprintf(tmp, sizeof(tmp), "%s%s%lu", from, body, millis()); - simpleHash(tmp, hash); - - strncpy(msg.id, hash, 8); msg.id[8] = '\0'; - strncpy(msg.from, from, 15); msg.from[15] = '\0'; - strncpy(msg.body, body, MAX_MSG_LEN-1); msg.body[MAX_MSG_LEN-1]='\0'; - - uint8_t pkt[sizeof(MeshMessage)+2]; - memcpy(pkt, &msg, sizeof(MeshMessage)); - uint16_t crc = crc16((uint8_t*)&msg, sizeof(MeshMessage)); - memcpy(pkt + sizeof(MeshMessage), &crc, 2); - - esp_now_send(broadcastAddressEspNow, pkt, sizeof(pkt)); - rememberId(msg.id); - - bool is_system = !strcmp(from, "System"); - bool is_own = !is_system && !strcmp(from, currentNick); - chat_history_record(is_system ? "" : from, body, is_own, is_system); - if (appMode == AppMode::CHAT) - ui_push_message(is_system ? "" : from, body, is_own, is_system); -} - -/* ----- Ping périodique ----- */ -void broadcastPing() { - MeshMessage msg; - char hash[9], tmp[32]; - - // hash = from + "PING" + horodatage - snprintf(tmp, sizeof(tmp), "%sPING%lu", currentNick, millis()); - simpleHash(tmp, hash); - - strncpy(msg.id, hash, 8); msg.id[8] = '\0'; - strncpy(msg.from, currentNick, 15); msg.from[15] = '\0'; - strncpy(msg.body, "PING", MAX_MSG_LEN - 1); - msg.body[MAX_MSG_LEN - 1] = '\0'; - - uint8_t pkt[sizeof(MeshMessage) + 2]; - memcpy(pkt, &msg, sizeof(MeshMessage)); - uint16_t crc = crc16((uint8_t*)&msg, sizeof(MeshMessage)); - memcpy(pkt + sizeof(MeshMessage), &crc, 2); - - esp_now_send(broadcastAddressEspNow, pkt, sizeof(pkt)); - rememberId(msg.id); // pour ignorer notre propre écho -} - - -/* ----- Gestion de présence ----- */ -void updatePresence(const char* nick) { - for (int i = 0; i < nodesCount; ++i) - if (!strcmp(nodesConnected[i].nick, nick)) { - nodesConnected[i].lastSeen = millis(); return; - } - - if (nodesCount < MAX_NODES) { - if ((int)nodesConnected.size() < MAX_NODES) nodesConnected.resize(MAX_NODES); - strncpy(nodesConnected[nodesCount].nick, nick, 15); - nodesConnected[nodesCount].nick[15] = '\0'; - nodesConnected[nodesCount].lastSeen = millis(); - ++nodesCount; - } -} -void checkPresenceTimeouts() { - unsigned long now = millis(); - for (int i = 0; i < nodesCount; ++i) - if ((now - nodesConnected[i].lastSeen) > TIMEOUT_PRESENCE) { - char m[MAX_MSG_LEN]; - snprintf(m, sizeof(m), "%s left the chat!", nodesConnected[i].nick); - // broadcastChatMessage("System", m); // lets not broadcast this, but: - ui_push_message("", m, false, true); - - - for (int j = i; j < nodesCount-1; ++j) - nodesConnected[j] = nodesConnected[j+1]; - --nodesCount; --i; - } -} - -// ----- Gestion clavier & saisie utilisateur ----- -// handleKeyboard() removed, M5Cardputer-specific; logic moved into chat_tick(char key) -//void handleKeyboard() { -// if (!kpAny()) return; -// -// Keyboard_Class::KeysState st = M5Cardputer.Keyboard.keysState(); -// -// /* Ajout des caractères tapés */ -// for (char c : st.word) { -// int n = strlen(inputBuffer); -// if (n < MAX_MSG_LEN - 2) { // -2 : place pour '\0' -// inputBuffer[n] = c; -// inputBuffer[n+1] = '\0'; -// } -// } -// /* Backspace */ -// if (st.del && *inputBuffer) -// inputBuffer[strlen(inputBuffer) - 1] = '\0'; -// -// /* Entrée : envoi */ -// if (st.enter && *inputBuffer) { -// if (inputBuffer[0] == '/') -// handleCommand(inputBuffer); -// else -// broadcastChatMessage(currentNick, inputBuffer); -// -// inputBuffer[0] = '\0'; -// } -// -// /* Ligne d'édition */ -// M5.Display.fillRect(0, 120, 240, 16, TFT_NAVY); -// M5.Display.setCursor(5, 120); -// M5.Display.setTextColor(TFT_YELLOW); -// M5.Display.print("> "); -// M5.Display.print(inputBuffer); -//} - -// ----- Fonction principale ----- -// EvilChatMesh() split into chat_init() + chat_tick(), no blocking while(true) -//void EvilChatMesh() { -// auto cfg = M5.config(); -// M5.begin(cfg); -// M5.Display.setTextSize(1); -// M5.Display.setTextColor(TFT_WHITE); -// M5.Display.fillScreen(TFT_BLACK); -// -// WiFi.disconnect(true); delay(100); -// WiFi.mode(WIFI_STA); -// -// if (esp_now_init() != ESP_OK) { -// M5.Display.println("ESP‑NOW init failed"); return; -// } -// esp_now_register_recv_cb(OnDataRecvChat); -// -// esp_now_peer_info_t peer = {}; -// memcpy(peer.peer_addr, broadcastAddressEspNow, 6); -// peer.channel = 0; // changed to 1, EvilChatMesh-Relay.ino expects channel 1 -// peer.encrypt = false; -// if (!esp_now_is_peer_exist(peer.peer_addr)) -// esp_now_add_peer(&peer); -// -// nodesConnected.clear(); nodesConnected.resize(MAX_NODES); -// messages.assign(20, Message()); -// drawChatWindow(); -// -// char msgArr[MAX_MSG_LEN]; -// snprintf(msgArr, sizeof(msgArr), "%s enter the chat!", currentNick); -// broadcastChatMessage("System", msgArr); -// -// /* ---------- Boucle ---------- */ -// while (true) { -// M5.update(); cardUpdate(); -// -// /* 1. paquet reçu ? */ -// if (newPacketPending) { -// noInterrupts(); -// uint8_t localBuf[sizeof(MeshMessage)+2]; -// int len = packetLen; -// memcpy(localBuf, (const void*)packetBuffer, len); -// newPacketPending = false; -// interrupts(); -// -// handleIncomingPacket(localBuf, len); -// } -// -// /* 2. ping & timeouts */ -// unsigned long now = millis(); -// if (now - lastPing > PING_INTERVAL) { -// lastPing = now; broadcastPing(); -// } -// checkPresenceTimeouts(); -// -// /* 3. clavier */ -// if (M5Cardputer.Keyboard.isChange()) -// handleKeyboard(); -// -// /* 4. touche ` pour quitter */ -// if (kp('`')) { -// char m[MAX_MSG_LEN]; -// snprintf(m, sizeof(m), "%s left the chat!", currentNick); -// broadcastChatMessage("System", m); -// -// esp_now_unregister_recv_cb(); -// esp_now_deinit(); -// waitAndReturnToMenu("Left the chat..."); -// return; -// } -// delay(10); -// } -//} - - /* -Triggered by /sleep command. -Broadcasts a leave message, turns off the backlight. -two wake sources: touch IRQ (GPIO36) and BOOT button (GPIO0). +Triggered by /sleep command. Touch is quirky and not recommended. */ static void go_to_sleep() { @@ -696,10 +180,8 @@ static void go_to_sleep() { // broadcastChatMessage("System", m); // same here, only show don't broadcasst ui_push_message("", m, false, true); - delay(200); // let the packet fly - // Hold the backlight OFF pin through deep sleep so the screen stays dark. - pinMode(PIN_TFT_BL, OUTPUT); + pinMode(PIN_TFT_BL, OUTPUT); digitalWrite(PIN_TFT_BL, LOW); gpio_hold_en((gpio_num_t)PIN_TFT_BL); @@ -715,27 +197,7 @@ void chat_init() { strncpy(currentNick, g_settings.nick, sizeof(currentNick) - 1); currentNick[sizeof(currentNick) - 1] = '\0'; - nodesConnected.clear(); nodesConnected.resize(MAX_NODES); - seenMessageIds.resize(MAX_HISTORY); - for (auto& e : seenMessageIds) e.fill(0); - - WiFi.persistent(false); - WiFi.mode(WIFI_STA); - WiFi.setSleep(false); - - if (esp_now_init() != ESP_OK) { - Serial.println("[chat] ESP-NOW init failed"); - ui_push_message("", "ESP-NOW init failed!", false, true); - return; - } - esp_now_register_recv_cb(OnDataRecvChat); - - esp_now_peer_info_t peer = {}; - memcpy(peer.peer_addr, broadcastAddressEspNow, 6); - peer.channel = 1; // relay uses channel 1; original was 0 - peer.encrypt = false; - if (!esp_now_is_peer_exist(peer.peer_addr)) - esp_now_add_peer(&peer); + mesh_init(); char msgArr[MAX_MSG_LEN]; snprintf(msgArr, sizeof(msgArr), "%s entered the chat!", currentNick); @@ -769,14 +231,7 @@ void chat_tick(char key) { } // update bt connection status (here instead of waiting for keypress) - if (appMode == AppMode::BT) { - bool nowConnected = g_ble && g_ble->isConnected(); - if (nowConnected != btConnectedLast) { - btConnectedLast = nowConnected; - if (!nowConnected) NimBLEDevice::startAdvertising(); - set_bt_status_message(); - } - } + if (appMode == AppMode::BT) bt_tick(); /* 3. clavier */ if (key == 0) return; @@ -792,8 +247,7 @@ void chat_tick(char key) { /* Scroll through notes */ if (appMode == AppMode::NOTES && ((uint8_t)key == KB_KEY_UP || (uint8_t)key == KB_KEY_DOWN)) { - notesScrollPos += ((uint8_t)key == KB_KEY_UP) ? 1 : -1; - notes_render(); + notes_scroll(((uint8_t)key == KB_KEY_UP) ? 1 : -1); return; } @@ -801,11 +255,7 @@ void chat_tick(char key) { if((uint8_t)key == 0x80) { // FN + ESC bt_dump_start(); } else { - if(KB_KEY_ENTER == key) { - g_ble->write(KEY_RETURN); - } else { - g_ble->write(key); - } + bt_send_key(key); } } else { // Serial.print("Received key: "); @@ -822,15 +272,12 @@ void chat_tick(char key) { } else if (key == KB_KEY_ENTER || key == '\n') { // '\n' for Serial keypresses // if (appMode == AppMode::BT && inputBuffer[0] != '/') { // let /command pass... // bt_dump_start(); - // } else + // } else if (*inputBuffer) { // if (st.enter && *inputBuffer) { if (inputBuffer[0] == '/') handleCommand(inputBuffer); else if (appMode == AppMode::NOTES) { - notes_append(inputBuffer); - notes_to_buffer(inputBuffer); - notesScrollPos = 0; // snap back to the latest note - notes_render(); + notes_add(inputBuffer); } else if (appMode == AppMode::CHAT) { broadcastChatMessage(currentNick, inputBuffer); } @@ -841,14 +288,14 @@ void chat_tick(char key) { } else if ((uint8_t)key >= 0x20 && (uint8_t)key < 0x7F) { - + int n = strlen(inputBuffer); if (n < MAX_MSG_LEN - 2) { // -2 : place pour '\0' inputBuffer[n] = key; inputBuffer[n+1] = '\0'; } - - } + + } // else if (appMode == AppMode::BT && (uint8_t)key == 0x80) { // FN + ESC // bt_dump_start(); // } diff --git a/src/meshberry/chat.h b/src/meshberry/chat.h index 331789f..730b504 100644 --- a/src/meshberry/chat.h +++ b/src/meshberry/chat.h @@ -1,12 +1,16 @@ #pragma once +#include "EvilChatMesh.h" class BleKeyboard; -// Lifted from EvilChatMesh in Evil-Cardputer-v1-5-2.ino and adapted for -// the CYD hardware. +enum class AppMode { CHAT, NOTES, BT }; +extern AppMode appMode; +extern char currentNick[16]; +extern char inputBuffer[MAX_MSG_LEN]; +void switch_mode(AppMode m); +void chat_add_message(const char* nick, const char* text, bool is_own, bool is_system); void chat_init(); void chat_tick(char key); void chat_set_ble(BleKeyboard* kb); bool chat_is_bt_mode(); -static void toggle_bt_mode(); \ No newline at end of file diff --git a/src/meshberry/notes.cpp b/src/meshberry/notes.cpp new file mode 100644 index 0000000..5869f24 --- /dev/null +++ b/src/meshberry/notes.cpp @@ -0,0 +1,183 @@ +// notes.cpp - Notes mode / BT dump. + +#include "notes.h" +#include "chat.h" +#include "EvilChatMesh.h" +#include "config.h" +#include "settings.h" +#include "ui.h" +#include +#include +#include +#include + +static char notesBuf[NOTES_HISTORY_LINES][MAX_MSG_LEN]; +static int currNote = 0;// next pos +static int notesCount = 0; +static int notesScrollPos = 0; + +static void notes_to_buffer(const char* line) { + strncpy(notesBuf[currNote], line, MAX_MSG_LEN - 1); + notesBuf[currNote][MAX_MSG_LEN - 1] = '\0'; + currNote = (currNote + 1) % NOTES_HISTORY_LINES; + if (notesCount < NOTES_HISTORY_LINES) notesCount++; +} + +static void notes_load() { + notesCount = 0; currNote = 0; notesScrollPos = 0; + + File f = LittleFS.open(NOTES_FILE, "r"); + if (!f) return; + + char line[MAX_MSG_LEN]; + while (f.available()) { + int n = f.readBytesUntil('\n', line, sizeof(line) - 1); + if (n > 0 && line[n - 1] == '\r') + n--; // remove trailing /r from .println + line[n] = '\0'; + if (n == 0) continue; + notes_to_buffer(line); + } + f.close(); +} + +static void notes_draw() { + int visible = UI_VISIBLE_LINES; + int maxScroll = (notesCount > visible) ? (notesCount - visible) : 0; + if (notesScrollPos > maxScroll) notesScrollPos = maxScroll; + if (notesScrollPos < 0) notesScrollPos = 0; + + int windowEnd = notesCount - notesScrollPos; + int windowStart = windowEnd - visible; + if (windowStart < 0) windowStart = 0; + + int start = (notesCount < NOTES_HISTORY_LINES) ? 0 : currNote; + + ui_clear_messages(); + for (int i = windowStart; i < windowEnd; i++) + ui_push_message("", notesBuf[(start + i) % NOTES_HISTORY_LINES], true, false); +} + +void notes_begin() { + notes_load(); + notes_draw(); +} + +void notes_scroll(int dir) { + notesScrollPos += dir; + notes_draw(); +} + +/* ----- Notes mode / BT dump ----- */ + +static BleKeyboard* g_ble = nullptr; +static AppMode modeBeforeBt = AppMode::CHAT; + +void chat_set_ble(BleKeyboard* kb) { g_ble = kb; } + +bool chat_is_bt_mode() { return appMode == AppMode::BT; } + +// Append one line to the notes file (created on first write). +static void notes_append(const char* line) { + File f = LittleFS.open(NOTES_FILE, "a"); + if (!f) return; + f.println(line); + f.close(); +} + +void notes_add(const char* line) { + notes_append(line); + notes_to_buffer(line); + notesScrollPos = 0; // snap back to the latest note + notes_draw(); +} + +static bool btConnectedLast = false; + +static void set_bt_status_message() { + if (!g_ble) { ui_push_message("", "BT unavailable.", false, true); return; } + if (g_ble->isConnected()) { + ui_push_message("", "BT connected. Press FN + ESC to send notes. TAB to exit.", false, true); + } else { + char buf[64]; + snprintf(buf, sizeof(buf), "Pair Bluetooth keyboard: \"%s %s\"", currentNick, "Keyboard"); + ui_push_message("", buf, false, true); + } +} + +// Switch into/out of BT dump mode. /bt command +void toggle_bt_mode() { + inputBuffer[0] = '\0'; + ui_set_input(inputBuffer); + if (appMode != AppMode::BT) { + modeBeforeBt = appMode; // remember previous state. + appMode = AppMode::BT; + ui_set_header_mode("BT MODE"); + btConnectedLast = g_ble && g_ble->isConnected(); + String bleName = String(g_settings.nick) + " Keyboard"; + g_ble->setName(bleName.c_str()); // make sure to use latest nick + if (!btConnectedLast) NimBLEDevice::startAdvertising(); // TODO: not sure if this is still necessary? + set_bt_status_message(); + } else { + switch_mode(modeBeforeBt); + } +} + +/* +BT dump mode: countdown, then replay the notes file as keystrokes. +Blocking while it dumps. +*/ +void bt_dump_start() { + if (!g_ble || !g_ble->isConnected()) { // dump not started. + ui_push_message("", "Not connected yet.", false, true); + return; + } + for (int s = BT_DUMP_COUNTDOWN_S; s > 0; s--) { + char buf[24]; + snprintf(buf, sizeof(buf), "Sending in %d...", s); + ui_push_message("", buf, false, true); + ui_tick(); + delay(1000); + } + ui_push_message("", "Sending...", false, true); + ui_tick(); + + File f = LittleFS.open(NOTES_FILE, "r"); + if (f) { + char line[MAX_MSG_LEN]; + while (f.available()) { + int n = f.readBytesUntil('\n', line, sizeof(line) - 1); + if (n > 0 && line[n - 1] == '\r') n--; // remove \r + line[n] = '\0'; + g_ble->setDelay(30); // look what i found! + g_ble->print(line); // is too fast, drops keypresses on my machine + // for (int i = 0; line[i] != '\0'; i++) + // { + // g_ble->write(line[i]); + // //delay(30); // TODO: maybe make configurable? + // } + // g_ble->print(line); // is too fast, drops keypresses on my machine + g_ble->write(KEY_RETURN); + delay(30); + } + f.close(); + } + ui_push_message("", "Notes sent.", false, true); + toggle_bt_mode(); // done, leave BT mode, restore the previous screen +} + +// poll BT connection status +void bt_tick() { + bool nowConnected = g_ble && g_ble->isConnected(); + if (nowConnected != btConnectedLast) { + btConnectedLast = nowConnected; + if (!nowConnected) NimBLEDevice::startAdvertising(); + set_bt_status_message(); + } +} + +void bt_send_key(char key) { + if (!g_ble) return; + if (KB_KEY_ENTER == key) g_ble->write(KEY_RETURN); + else g_ble->write(key); +} diff --git a/src/meshberry/notes.h b/src/meshberry/notes.h new file mode 100644 index 0000000..2615f45 --- /dev/null +++ b/src/meshberry/notes.h @@ -0,0 +1,12 @@ +#pragma once + +// Notes mode / BT dump. + +void notes_begin(); +void notes_add(const char* line); +void notes_scroll(int dir); + +void toggle_bt_mode(); +void bt_dump_start(); +void bt_tick(); +void bt_send_key(char key); diff --git a/src/meshberry/settings.cpp b/src/meshberry/settings.cpp index 16f4d89..1bb9f0a 100644 --- a/src/meshberry/settings.cpp +++ b/src/meshberry/settings.cpp @@ -144,7 +144,7 @@ void settings_load() { if (*l == '\0' || *l == '#') continue; // blank line or comment char* eq = strchr(l, '='); - if (!eq) continue; // no '=' skip + if (!eq) continue;// no '=' skip *eq = '\0'; char* key = trim(l); char* val = trim(eq + 1);