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
|
// Heavily based on Evil-Cardputer-v1-5-2.ino
|
||||||
//
|
|
||||||
// Original source : Evil-Cardputer-v1-5-2.ino, lines 20480-20871
|
|
||||||
// Original author : 7h30th3r0n3, https://github.com/7h30th3r0n3/Evil-M5Project
|
// 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 "chat.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
#include "EvilChatMesh.h"
|
||||||
|
#include "notes.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <esp_now.h>
|
|
||||||
#include <esp_sleep.h> // deep sleep
|
#include <esp_sleep.h> // deep sleep
|
||||||
#include <driver/gpio.h> // gpio_hold_en keeps backlight off during sleep
|
#include <driver/gpio.h> // gpio_hold_en keeps backlight off during sleep
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
#include <LittleFS.h>
|
#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] = "";
|
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
|
// init from g_settings.nick
|
||||||
static char currentNick[16];
|
char currentNick[16];
|
||||||
|
|
||||||
/* ---------- Notes mode / BT dump ---------- */
|
/* Notes mode / BT dump */
|
||||||
enum class AppMode { CHAT, NOTES, BT };
|
AppMode appMode = AppMode::CHAT;
|
||||||
static AppMode appMode = AppMode::CHAT;
|
|
||||||
static AppMode modeBeforeBt = AppMode::CHAT;
|
|
||||||
static BleKeyboard* g_ble = nullptr;
|
|
||||||
|
|
||||||
// for redrawing
|
// for redrawing
|
||||||
#define MODE_HISTORY_LINES 12
|
#define MODE_HISTORY_LINES 12
|
||||||
|
|
||||||
// buffer for chat messages
|
// buffer for chat messages
|
||||||
struct ChatHistEntry {
|
struct ChatMessage {
|
||||||
char nick[CHAT_MAX_NICK_LEN];
|
char nick[CHAT_MAX_NICK_LEN];
|
||||||
char text[MAX_MSG_LEN];
|
char text[MAX_MSG_LEN];
|
||||||
bool is_own;
|
bool is_own;
|
||||||
bool is_system;
|
bool is_system;
|
||||||
};
|
};
|
||||||
static ChatHistEntry chatHistory[MODE_HISTORY_LINES];
|
static ChatMessage chatHistory[MODE_HISTORY_LINES];
|
||||||
static int chatHistCount = 0;
|
static int chatHistCount = 0;
|
||||||
static int chatHistNext = 0; // next write slot
|
static int chatHistNext = 0; // next write slot
|
||||||
|
|
||||||
static void chat_history_record(const char* nick, const char* text, bool is_own, bool is_system) {
|
void chat_add_message(const char* nick, const char* text, bool is_own, bool is_system) {
|
||||||
ChatHistEntry& e = chatHistory[chatHistNext];
|
ChatMessage& e = chatHistory[chatHistNext];
|
||||||
strncpy(e.nick, nick, sizeof(e.nick) - 1);
|
strncpy(e.nick, nick, sizeof(e.nick) - 1);
|
||||||
e.nick[sizeof(e.nick) - 1] = '\0';
|
e.nick[sizeof(e.nick) - 1] = '\0';
|
||||||
strncpy(e.text, text, sizeof(e.text) - 1);
|
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++;
|
if (chatHistCount < MODE_HISTORY_LINES) chatHistCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void chat_show_recent() {
|
static void chat_draw() {
|
||||||
int start = (chatHistCount < MODE_HISTORY_LINES) ? 0 : chatHistNext;
|
int start = (chatHistCount < MODE_HISTORY_LINES) ? 0 : chatHistNext;
|
||||||
for (int i = 0; i < chatHistCount; i++) {
|
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);
|
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.
|
Switch between chat mode (default) adn notes mode.
|
||||||
also redraw the UI somewhat
|
also redraw the UI somewhat
|
||||||
*/
|
*/
|
||||||
static void switch_mode(AppMode m) {
|
void switch_mode(AppMode m) {
|
||||||
appMode = m;
|
appMode = m;
|
||||||
ui_clear_messages();
|
ui_clear_messages();
|
||||||
if (m == AppMode::NOTES) {
|
if (m == AppMode::NOTES) {
|
||||||
ui_set_header_mode("NOTE MODE");
|
ui_set_header_mode("NOTE MODE");
|
||||||
notes_load();
|
notes_begin();
|
||||||
notes_render();
|
|
||||||
} else {
|
} else {
|
||||||
ui_set_header(nodesCount, currentNick);
|
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;
|
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) ---------- */
|
/* ---------- 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();
|
static void go_to_sleep();
|
||||||
|
|
||||||
/* ---------- CALLBACK ESP‑NOW ULTRA‑COURT ---------- */
|
/* ----- Commandes ----- */
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool apply_setting(String key, String value) {
|
bool apply_setting(String key, String value) {
|
||||||
if (key.startsWith("color_")) {
|
if (key.startsWith("color_")) {
|
||||||
|
|
@ -414,7 +95,6 @@ bool apply_setting(String key, String value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----- Commandes ----- */
|
|
||||||
void handleCommand(const char* in) {
|
void handleCommand(const char* in) {
|
||||||
if (!strncmp(in, "/nick ", 6)) {
|
if (!strncmp(in, "/nick ", 6)) {
|
||||||
strncpy(currentNick, in + 6, 15); currentNick[15] = '\0';
|
strncpy(currentNick, in + 6, 15); currentNick[15] = '\0';
|
||||||
|
|
@ -437,7 +117,7 @@ void handleCommand(const char* in) {
|
||||||
} else if (!strncmp(in, "/set ", 5)) {
|
} else if (!strncmp(in, "/set ", 5)) {
|
||||||
String args = String(in + 5);
|
String args = String(in + 5);
|
||||||
args.trim();
|
args.trim();
|
||||||
|
|
||||||
int spaceIdx = args.indexOf(' ');
|
int spaceIdx = args.indexOf(' ');
|
||||||
if (spaceIdx == -1) {
|
if (spaceIdx == -1) {
|
||||||
ui_push_message("", "Usage: /set <key> <value>", false, true);
|
ui_push_message("", "Usage: /set <key> <value>", false, true);
|
||||||
|
|
@ -466,11 +146,11 @@ void handleCommand(const char* in) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (!strcmp(in, "/help")) {
|
} else if (!strcmp(in, "/help")) {
|
||||||
//addMessage("/nick <name> : change pseudo");
|
//addMessage("/nick <name> : change pseudo");
|
||||||
//addMessage("/me <action> : action roleplay");
|
//addMessage("/me <action> : action roleplay");
|
||||||
//addMessage("/clear : clear the chat");
|
//addMessage("/clear : clear the chat");
|
||||||
//addMessage("/people : list connected");
|
//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("", "/me <action> : action roleplay", false, true);
|
||||||
ui_push_message("", "/clear : clear the chat", false, true);
|
ui_push_message("", "/clear : clear the chat", false, true);
|
||||||
ui_push_message("", "/clearnotes : erase saved notes", 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);
|
ui_push_message("", "TAB : notes mode", false, true);
|
||||||
//drawChatWindow();
|
//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?
|
go_to_sleep(); // sleep device. maybe add a button?
|
||||||
|
|
||||||
} else {
|
} 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.
|
Triggered by /sleep command.
|
||||||
Broadcasts a leave message, turns off the backlight.
|
|
||||||
two wake sources: touch IRQ (GPIO36) and BOOT button (GPIO0).
|
|
||||||
Touch is quirky and not recommended.
|
Touch is quirky and not recommended.
|
||||||
*/
|
*/
|
||||||
static void go_to_sleep() {
|
static void go_to_sleep() {
|
||||||
|
|
@ -696,10 +180,8 @@ static void go_to_sleep() {
|
||||||
// broadcastChatMessage("System", m); // same here, only show don't broadcasst
|
// broadcastChatMessage("System", m); // same here, only show don't broadcasst
|
||||||
ui_push_message("", m, false, true);
|
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.
|
// 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);
|
digitalWrite(PIN_TFT_BL, LOW);
|
||||||
gpio_hold_en((gpio_num_t)PIN_TFT_BL);
|
gpio_hold_en((gpio_num_t)PIN_TFT_BL);
|
||||||
|
|
||||||
|
|
@ -715,27 +197,7 @@ void chat_init() {
|
||||||
strncpy(currentNick, g_settings.nick, sizeof(currentNick) - 1);
|
strncpy(currentNick, g_settings.nick, sizeof(currentNick) - 1);
|
||||||
currentNick[sizeof(currentNick) - 1] = '\0';
|
currentNick[sizeof(currentNick) - 1] = '\0';
|
||||||
|
|
||||||
nodesConnected.clear(); nodesConnected.resize(MAX_NODES);
|
mesh_init();
|
||||||
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);
|
|
||||||
|
|
||||||
char msgArr[MAX_MSG_LEN];
|
char msgArr[MAX_MSG_LEN];
|
||||||
snprintf(msgArr, sizeof(msgArr), "%s entered the chat!", currentNick);
|
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)
|
// update bt connection status (here instead of waiting for keypress)
|
||||||
if (appMode == AppMode::BT) {
|
if (appMode == AppMode::BT) bt_tick();
|
||||||
bool nowConnected = g_ble && g_ble->isConnected();
|
|
||||||
if (nowConnected != btConnectedLast) {
|
|
||||||
btConnectedLast = nowConnected;
|
|
||||||
if (!nowConnected) NimBLEDevice::startAdvertising();
|
|
||||||
set_bt_status_message();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. clavier */
|
/* 3. clavier */
|
||||||
if (key == 0) return;
|
if (key == 0) return;
|
||||||
|
|
@ -792,8 +247,7 @@ void chat_tick(char key) {
|
||||||
/* Scroll through notes */
|
/* Scroll through notes */
|
||||||
if (appMode == AppMode::NOTES &&
|
if (appMode == AppMode::NOTES &&
|
||||||
((uint8_t)key == KB_KEY_UP || (uint8_t)key == KB_KEY_DOWN)) {
|
((uint8_t)key == KB_KEY_UP || (uint8_t)key == KB_KEY_DOWN)) {
|
||||||
notesScrollPos += ((uint8_t)key == KB_KEY_UP) ? 1 : -1;
|
notes_scroll(((uint8_t)key == KB_KEY_UP) ? 1 : -1);
|
||||||
notes_render();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -801,11 +255,7 @@ void chat_tick(char key) {
|
||||||
if((uint8_t)key == 0x80) { // FN + ESC
|
if((uint8_t)key == 0x80) { // FN + ESC
|
||||||
bt_dump_start();
|
bt_dump_start();
|
||||||
} else {
|
} else {
|
||||||
if(KB_KEY_ENTER == key) {
|
bt_send_key(key);
|
||||||
g_ble->write(KEY_RETURN);
|
|
||||||
} else {
|
|
||||||
g_ble->write(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Serial.print("Received key: ");
|
// 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
|
} else if (key == KB_KEY_ENTER || key == '\n') { // '\n' for Serial keypresses
|
||||||
// if (appMode == AppMode::BT && inputBuffer[0] != '/') { // let /command pass...
|
// if (appMode == AppMode::BT && inputBuffer[0] != '/') { // let /command pass...
|
||||||
// bt_dump_start();
|
// bt_dump_start();
|
||||||
// } else
|
// } else
|
||||||
if (*inputBuffer) { // if (st.enter && *inputBuffer) {
|
if (*inputBuffer) { // if (st.enter && *inputBuffer) {
|
||||||
if (inputBuffer[0] == '/')
|
if (inputBuffer[0] == '/')
|
||||||
handleCommand(inputBuffer);
|
handleCommand(inputBuffer);
|
||||||
else if (appMode == AppMode::NOTES) {
|
else if (appMode == AppMode::NOTES) {
|
||||||
notes_append(inputBuffer);
|
notes_add(inputBuffer);
|
||||||
notes_to_buffer(inputBuffer);
|
|
||||||
notesScrollPos = 0; // snap back to the latest note
|
|
||||||
notes_render();
|
|
||||||
} else if (appMode == AppMode::CHAT) {
|
} else if (appMode == AppMode::CHAT) {
|
||||||
broadcastChatMessage(currentNick, inputBuffer);
|
broadcastChatMessage(currentNick, inputBuffer);
|
||||||
}
|
}
|
||||||
|
|
@ -841,14 +288,14 @@ void chat_tick(char key) {
|
||||||
|
|
||||||
|
|
||||||
} else if ((uint8_t)key >= 0x20 && (uint8_t)key < 0x7F) {
|
} else if ((uint8_t)key >= 0x20 && (uint8_t)key < 0x7F) {
|
||||||
|
|
||||||
int n = strlen(inputBuffer);
|
int n = strlen(inputBuffer);
|
||||||
if (n < MAX_MSG_LEN - 2) { // -2 : place pour '\0'
|
if (n < MAX_MSG_LEN - 2) { // -2 : place pour '\0'
|
||||||
inputBuffer[n] = key;
|
inputBuffer[n] = key;
|
||||||
inputBuffer[n+1] = '\0';
|
inputBuffer[n+1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// else if (appMode == AppMode::BT && (uint8_t)key == 0x80) { // FN + ESC
|
// else if (appMode == AppMode::BT && (uint8_t)key == 0x80) { // FN + ESC
|
||||||
// bt_dump_start();
|
// bt_dump_start();
|
||||||
// }
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "EvilChatMesh.h"
|
||||||
|
|
||||||
class BleKeyboard;
|
class BleKeyboard;
|
||||||
|
|
||||||
// Lifted from EvilChatMesh in Evil-Cardputer-v1-5-2.ino and adapted for
|
enum class AppMode { CHAT, NOTES, BT };
|
||||||
// the CYD hardware.
|
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_init();
|
||||||
void chat_tick(char key);
|
void chat_tick(char key);
|
||||||
void chat_set_ble(BleKeyboard* kb);
|
void chat_set_ble(BleKeyboard* kb);
|
||||||
bool chat_is_bt_mode();
|
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
|
if (*l == '\0' || *l == '#') continue; // blank line or comment
|
||||||
|
|
||||||
char* eq = strchr(l, '=');
|
char* eq = strchr(l, '=');
|
||||||
if (!eq) continue; // no '=' skip
|
if (!eq) continue;// no '=' skip
|
||||||
*eq = '\0';
|
*eq = '\0';
|
||||||
char* key = trim(l);
|
char* key = trim(l);
|
||||||
char* val = trim(eq + 1);
|
char* val = trim(eq + 1);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue