mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-10 07:14:46 +02:00
meshpocket morse port
This commit is contained in:
@@ -1313,13 +1313,22 @@ void MyMesh::begin(bool has_display) {
|
||||
BaseChatMesh::begin();
|
||||
|
||||
if (!_store->loadMainIdentity(self_id)) {
|
||||
Serial.println("[ID] loadMainIdentity FAILED — generating new identity");
|
||||
self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
self_id = radio_new_identity();
|
||||
count++;
|
||||
}
|
||||
_store->saveMainIdentity(self_id);
|
||||
bool ok = _store->saveMainIdentity(self_id);
|
||||
Serial.printf("[ID] saveMainIdentity returned %d\n", ok ? 1 : 0);
|
||||
} else {
|
||||
Serial.println("[ID] loadMainIdentity OK — using persisted identity");
|
||||
}
|
||||
{
|
||||
char hex[10];
|
||||
mesh::Utils::toHex(hex, self_id.pub_key, 4);
|
||||
Serial.printf("[ID] pub_key[0..3] = %s\n", hex);
|
||||
}
|
||||
|
||||
// if name is provided as a build flag, use that as default node name instead
|
||||
@@ -1418,6 +1427,7 @@ void MyMesh::startInterface(BaseSerialInterface &serial) {
|
||||
}
|
||||
|
||||
void MyMesh::handleCmdFrame(size_t len) {
|
||||
Serial.printf("[CMD] rx opcode=0x%02X len=%d\n", cmd_frame[0], (int)len);
|
||||
if (cmd_frame[0] == CMD_DEVICE_QEURY && len >= 2) { // sent when app establishes connection
|
||||
app_target_ver = cmd_frame[1]; // which version of protocol does app understand
|
||||
|
||||
@@ -1720,12 +1730,19 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
}
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_IMPORT_CONTACT && len > 2 + 32 + 64) {
|
||||
Serial.printf("[IMP] CMD_IMPORT_CONTACT received, len=%d\n", len);
|
||||
if (importContact(&cmd_frame[1], len - 1)) {
|
||||
Serial.println("[IMP] importContact OK, scheduling dirty flush");
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
Serial.println("[IMP] importContact REJECTED by BaseChatMesh");
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_IMPORT_CONTACT) {
|
||||
Serial.printf("[IMP] CMD_IMPORT_CONTACT dropped — len=%d too short (need >%d)\n",
|
||||
len, 2 + 32 + 64);
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (cmd_frame[0] == CMD_SYNC_NEXT_MESSAGE) {
|
||||
int out_len;
|
||||
if ((out_len = getFromOfflineQueue(out_frame)) > 0) {
|
||||
@@ -1862,23 +1879,29 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeDisabledFrame();
|
||||
#endif
|
||||
} else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) {
|
||||
Serial.printf("[PK] CMD_IMPORT_PRIVATE_KEY received, len=%d\n", (int)len);
|
||||
#if ENABLE_PRIVATE_KEY_IMPORT
|
||||
if (!mesh::LocalIdentity::validatePrivateKey(&cmd_frame[1])) {
|
||||
Serial.println("[PK] validatePrivateKey FAILED — key bytes rejected");
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid key
|
||||
} else {
|
||||
Serial.println("[PK] validatePrivateKey OK — attempting save");
|
||||
mesh::LocalIdentity identity;
|
||||
identity.readFrom(&cmd_frame[1], 64);
|
||||
if (_store->saveMainIdentity(identity)) {
|
||||
Serial.println("[PK] saveMainIdentity OK");
|
||||
self_id = identity;
|
||||
writeOKFrame();
|
||||
// re-load contacts, to invalidate ecdh shared_secrets
|
||||
resetContacts();
|
||||
_store->loadContacts(this);
|
||||
} else {
|
||||
Serial.println("[PK] saveMainIdentity FAILED");
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
}
|
||||
#else
|
||||
Serial.println("[PK] ENABLE_PRIVATE_KEY_IMPORT not defined — responding DISABLED");
|
||||
writeDisabledFrame();
|
||||
#endif
|
||||
} else if (cmd_frame[0] == CMD_SEND_RAW_DATA && len >= 6) {
|
||||
@@ -2073,17 +2096,21 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 32) {
|
||||
Serial.printf("[CH] CMD_SET_CHANNEL 256-bit secret not supported (len=%d)\n", len);
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); // not supported (yet)
|
||||
} else if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16) {
|
||||
uint8_t channel_idx = cmd_frame[1];
|
||||
Serial.printf("[CH] CMD_SET_CHANNEL idx=%d len=%d\n", channel_idx, len);
|
||||
ChannelDetails channel;
|
||||
StrHelper::strncpy(channel.name, (char *)&cmd_frame[2], 32);
|
||||
memset(channel.channel.secret, 0, sizeof(channel.channel.secret));
|
||||
memcpy(channel.channel.secret, &cmd_frame[2 + 32], 16); // NOTE: only 128-bit supported
|
||||
if (setChannel(channel_idx, channel)) {
|
||||
Serial.println("[CH] setChannel OK, calling saveChannels");
|
||||
saveChannels();
|
||||
writeOKFrame();
|
||||
} else {
|
||||
Serial.printf("[CH] setChannel REJECTED (bad idx=%d)\n", channel_idx);
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SIGN_START) {
|
||||
@@ -2313,6 +2340,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
Serial.printf("[CMD] UNKNOWN opcode=0x%02X len=%d — responded UNSUPPORTED\n",
|
||||
cmd_frame[0], (int)len);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
}
|
||||
}
|
||||
@@ -3205,18 +3234,32 @@ void MyMesh::loop() {
|
||||
if (_deferSaves) {
|
||||
// Voice session receiving — push save forward to avoid SPI contention
|
||||
dirty_contacts_expiry = futureMillis(2000);
|
||||
} else if (!_store->isSaveInProgress()) {
|
||||
_store->beginSaveContacts(this);
|
||||
} else {
|
||||
#ifdef HELTEC_MESH_POCKET
|
||||
// Meshpocket: use upstream-style synchronous save. Max 500 contacts =
|
||||
// ~74KB, finishes in well under a second on InternalFS. Avoids the
|
||||
// chunked trio's File* lifetime pitfalls entirely.
|
||||
Serial.println("[DS] saveContacts (synchronous) triggered by dirty flag");
|
||||
_store->saveContacts(this);
|
||||
dirty_contacts_expiry = 0;
|
||||
#else
|
||||
if (!_store->isSaveInProgress()) {
|
||||
_store->beginSaveContacts(this);
|
||||
dirty_contacts_expiry = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Drive chunked contact save — write a batch each loop iteration
|
||||
#ifndef HELTEC_MESH_POCKET
|
||||
// Drive chunked contact save — write a batch each loop iteration.
|
||||
// Only used for non-Meshpocket builds (ESP32 PSRAM heavyweight).
|
||||
if (_store->isSaveInProgress() && !_deferSaves) {
|
||||
if (!_store->saveContactsChunk(20)) { // 20 contacts per chunk (~3KB, ~30ms)
|
||||
_store->finishSaveContacts(); // Done or error — verify and commit
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Discovery scan timeout
|
||||
if (_discoveryActive && millisHasNowPassed(_discoveryTimeout)) {
|
||||
|
||||
@@ -1663,10 +1663,29 @@ void halt() {
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(100); // Give serial time to initialize
|
||||
#ifdef NRF52_PLATFORM
|
||||
// nRF52 USB Serial (TinyUSB) needs to enumerate before the host sees prints.
|
||||
// Wait up to 3s for a serial connection; without this, early setup() prints
|
||||
// are silently dropped. Skip the wait entirely if nothing ever connects
|
||||
// (running off battery with no USB).
|
||||
uint32_t t0 = millis();
|
||||
while (!Serial && (millis() - t0) < 3000) { delay(10); }
|
||||
#else
|
||||
delay(100); // Give serial time to initialize (ESP32: CDC is already up)
|
||||
#endif
|
||||
|
||||
// Unconditional entry marker — tells us setup() is running and Serial works
|
||||
Serial.println("[DIAG] setup() entered");
|
||||
#ifdef DISPLAY_CLASS
|
||||
Serial.println("[DIAG] DISPLAY_CLASS is defined at compile time");
|
||||
#else
|
||||
Serial.println("[DIAG] DISPLAY_CLASS NOT defined at compile time!");
|
||||
#endif
|
||||
|
||||
MESH_DEBUG_PRINTLN("=== setup() - STARTING ===");
|
||||
|
||||
board.begin();
|
||||
Serial.println("[DIAG] board.begin() returned");
|
||||
MESH_DEBUG_PRINTLN("setup() - board.begin() done");
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -345,7 +345,10 @@ public:
|
||||
renderBatteryIndicator(display, _task->getBattMilliVolts());
|
||||
#endif
|
||||
|
||||
// centered clock — only show when time is valid
|
||||
// centered clock — only show when time is valid.
|
||||
// Skipped on Meshpocket: no RTC hardware means time is unreliable between
|
||||
// BLE sync events and wrong after any power cycle.
|
||||
#ifndef HELTEC_MESH_POCKET
|
||||
{
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
if (now > 1700000000) { // valid timestamp (after ~Nov 2023)
|
||||
@@ -371,6 +374,7 @@ public:
|
||||
display.setTextSize(1); // restore
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// curr page indicator
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
int y = 14; // Closer to header
|
||||
@@ -483,8 +487,20 @@ public:
|
||||
}
|
||||
display.setTextSize(1);
|
||||
|
||||
#elif defined(HELTEC_MESH_POCKET)
|
||||
// ----- Heltec Meshpocket: single USER button, no keyboard shortcuts -----
|
||||
// MSG/Pin/Connected already drawn above (lines ~400-432). Add a nav hint
|
||||
// at y=72 (virtual coords) — matches other pages on this panel. Note:
|
||||
// with EINK_SCALE_Y=1.28 and EINK_Y_OFFSET=10 the usable virtual Y range
|
||||
// is ~0-85, so display.height()-12 overshoots the physical screen.
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(_node_prefs->smallTextSize());
|
||||
display.drawTextCentered(display.width() / 2, 72,
|
||||
"Click: next Hold: action");
|
||||
display.setTextSize(1);
|
||||
|
||||
#else
|
||||
// ----- T-Deck Pro: Keyboard shortcut text menu -----
|
||||
// ----- T-Deck Pro / Heltec V4 / other keyboard variants -----
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(_node_prefs->smallTextSize());
|
||||
int menuLH = _node_prefs->smallLineH();
|
||||
@@ -1324,6 +1340,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
|
||||
#ifdef MORSE_COMPOSE_ENABLED
|
||||
morse_screen = new MorseScreen(&rtc_clock);
|
||||
Serial.printf("[UI] MorseScreen allocated at %p\n", morse_screen);
|
||||
#else
|
||||
Serial.println("[UI] MORSE_COMPOSE_ENABLED not defined — triple-click disabled");
|
||||
#endif
|
||||
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
@@ -2198,6 +2217,7 @@ char UITask::handleDoubleClick(char c) {
|
||||
}
|
||||
|
||||
char UITask::handleTripleClick(char c) {
|
||||
Serial.println("[UI] triple click event received");
|
||||
MESH_DEBUG_PRINTLN("UITask: triple click triggered");
|
||||
checkDisplayOn(c);
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
@@ -2213,13 +2233,18 @@ char UITask::handleTripleClick(char c) {
|
||||
// Triple-click from home screen → enter Morse compose mode.
|
||||
// From any other screen, fall through to the existing buzzer toggle (no-op
|
||||
// on Meshpocket but kept for other single-button variants).
|
||||
Serial.printf("[UI] triple click: morse_screen=%p curr=%p home=%p\n",
|
||||
morse_screen, curr, home);
|
||||
if (morse_screen != nullptr && curr == home) {
|
||||
Serial.println("[UI] triple click: activating MorseScreen");
|
||||
morse_screen->activate();
|
||||
setCurrScreen(morse_screen);
|
||||
} else {
|
||||
Serial.println("[UI] triple click: conditions not met, falling back to buzzer toggle");
|
||||
toggleBuzzer();
|
||||
}
|
||||
#else
|
||||
Serial.println("[UI] triple click: MORSE_COMPOSE_ENABLED not defined");
|
||||
toggleBuzzer();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -246,6 +246,7 @@ void SerialBLEInterface::enable() {
|
||||
clearBuffers();
|
||||
_last_health_check = millis();
|
||||
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.start(0);
|
||||
}
|
||||
|
||||
@@ -259,8 +260,9 @@ void SerialBLEInterface::disable() {
|
||||
_isEnabled = false;
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disable");
|
||||
|
||||
disconnect();
|
||||
Bluefruit.Advertising.restartOnDisconnect(false);
|
||||
Bluefruit.Advertising.stop();
|
||||
disconnect();
|
||||
_last_health_check = 0;
|
||||
}
|
||||
|
||||
@@ -394,4 +396,4 @@ bool SerialBLEInterface::isConnected() const {
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,4 @@ public:
|
||||
#else
|
||||
#define BLE_DEBUG_PRINT(...) {}
|
||||
#define BLE_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
#endif
|
||||
@@ -25,6 +25,14 @@ bool GxEPDDisplay::begin() {
|
||||
// Tell GxEPD2 to use our SPI instance
|
||||
// Using slower speed (4MHz) for reliable e-ink communication
|
||||
display.epd2.selectSPI(displaySpi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
// nRF52 (Meshpocket et al): LoRa sits on the default SPI bus (PIN_SPI_MISO/
|
||||
// MOSI/SCK), e-ink sits on SPI1 (MISO/MOSI/SCK globals set by the variant's
|
||||
// variant.cpp). GxEPD2's default _pSPIx=&SPI would send display traffic to
|
||||
// the LoRa bus — hand the e-ink its own bus explicitly. This matches
|
||||
// upstream MeshCore's approach (which uses SPI1 universally).
|
||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI1.begin();
|
||||
#endif
|
||||
|
||||
// Initialize with:
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
; ============================================================================
|
||||
; Meck — Heltec MeshPocket variant configuration
|
||||
; ============================================================================
|
||||
; nRF52840 + SX1262 + 2.13" E-Ink (GxEPD2_213_B74)
|
||||
; Single USER button (GPIO 42), no buzzer, no user-accessible LED
|
||||
; ============================================================================
|
||||
|
||||
[Mesh_pocket]
|
||||
extends = nrf52_base
|
||||
board = heltec_mesh_pocket
|
||||
@@ -27,6 +20,8 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D EINK_X_OFFSET=0
|
||||
-D EINK_Y_OFFSET=10
|
||||
-D DISPLAY_CLASS=GxEPDDisplay
|
||||
-D DISPLAY_ROTATION=3
|
||||
-D MORSE_COMPOSE_ENABLED
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
@@ -43,20 +38,20 @@ lib_deps =
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
[env:Mesh_pocket_companion_radio_ble]
|
||||
|
||||
[env:meck_mesh_pocket_companion_radio_ble]
|
||||
extends = Mesh_pocket
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${Mesh_pocket.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=500
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=234567
|
||||
-D OFFLINE_QUEUE_SIZE=64
|
||||
-D MAX_CONTACTS=380
|
||||
-D MAX_GROUP_CHANNELS=12
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D AUTO_OFF_MILLIS=0
|
||||
-D MORSE_COMPOSE_ENABLED=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user