code cleanup. Moved the original mesh code to own file.
This commit is contained in:
parent
44f10676b3
commit
bb6b0e4d73
2
src/idf_component.yml
Normal file
2
src/idf_component.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
idf: '>=5.1'
|
||||
2
src/idf_component.yml.orig
Normal file
2
src/idf_component.yml.orig
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
idf: '>=5.1'
|
||||
375
src/meshberry/EvilChatMesh.cpp
Normal file
375
src/meshberry/EvilChatMesh.cpp
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
#include "EvilChatMesh.h"
|
||||
#include "chat.h" // currentNick, appMode, chat_add_message()
|
||||
#include "ui.h"
|
||||
#include "sound.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_now.h>
|
||||
|
||||
/*
|
||||
============================================================================================================================
|
||||
EvilChatMesh
|
||||
============================================================================================================================
|
||||
*/
|
||||
|
||||
/* ---------- VARIABLES GLOBALES ---------- */
|
||||
std::vector<NodePresence> nodesConnected;
|
||||
int nodesCount = 0;
|
||||
|
||||
static std::vector<std::array<char,9>> 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<Message> 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);
|
||||
// }
|
||||
//}
|
||||
62
src/meshberry/EvilChatMesh.h
Normal file
62
src/meshberry/EvilChatMesh.h
Normal file
|
|
@ -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 <Arduino.h>
|
||||
#include <esp_now.h>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#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<NodePresence> 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();
|
||||
|
|
@ -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 <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_sleep.h> // deep sleep
|
||||
#include <driver/gpio.h> // gpio_hold_en keeps backlight off during sleep
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <LittleFS.h>
|
||||
#include <BleKeyboard.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
/*
|
||||
============================================================================================================================
|
||||
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<NodePresence> nodesConnected;
|
||||
int nodesCount = 0;
|
||||
|
||||
static std::vector<std::array<char,9>> seenMessageIds;
|
||||
int seenIndex = 0;
|
||||
|
||||
/* GLOBALS */
|
||||
char inputBuffer[MAX_MSG_LEN] = "";
|
||||
|
||||
uint8_t broadcastAddressEspNow[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
||||
|
||||
// static std::vector<Message> 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 <key> <value>", false, true);
|
||||
|
|
@ -466,11 +146,11 @@ void handleCommand(const char* in) {
|
|||
}
|
||||
|
||||
} else if (!strcmp(in, "/help")) {
|
||||
//addMessage("/nick <name> : change pseudo");
|
||||
//addMessage("/nick <name> : change pseudo");
|
||||
//addMessage("/me <action> : action roleplay");
|
||||
//addMessage("/clear : clear the chat");
|
||||
//addMessage("/people : list connected");
|
||||
ui_push_message("", "/nick <name> : change pseudo", false, true);
|
||||
ui_push_message("", "/nick <name> : change pseudo", false, true);
|
||||
ui_push_message("", "/me <action> : 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();
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
183
src/meshberry/notes.cpp
Normal file
183
src/meshberry/notes.cpp
Normal file
|
|
@ -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 <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <BleKeyboard.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
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);
|
||||
}
|
||||
12
src/meshberry/notes.h
Normal file
12
src/meshberry/notes.h
Normal file
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue