4 Commits

8 changed files with 227 additions and 27 deletions

View File

@@ -1,5 +1,5 @@
## Meshcore + Fork = Meck
This fork was created specifically to focus on enabling BLE companion firmware for the LilyGo T-Deck Pro. Created with the assistance of Claude AI using Meshcore v1.11 code.
This fork was created specifically to focus on enabling BLE & WiFi companion firmware for the LilyGo T-Deck Pro. Created with the assistance of Claude AI using Meshcore v1.11 code.
<img src="https://github.com/user-attachments/assets/b30ce6bd-79af-44d3-93c4-f5e7e21e5621" alt="IMG_1453" width="300" height="650">
@@ -13,6 +13,7 @@ This fork was created specifically to focus on enabling BLE companion firmware f
- [Sending a Direct Message](#sending-a-direct-message)
- [Repeater Admin Screen](#repeater-admin-screen)
- [Settings Screen](#settings-screen)
- [Serial Settings (USB)](Serial_Settings_Guide.md)
- [Compose Mode](#compose-mode)
- [Symbol Entry (Sym Key)](#symbol-entry-sym-key)
- [Emoji Picker](#emoji-picker)
@@ -34,7 +35,7 @@ This fork was created specifically to focus on enabling BLE companion firmware f
## T-Deck Pro Keyboard Controls
The T-Deck Pro BLE companion firmware includes full keyboard support for standalone messaging without a phone.
The T-Deck Pro companion firmware includes full keyboard support for standalone messaging without a phone.
### Navigation (Home Screen)
@@ -51,6 +52,8 @@ The T-Deck Pro BLE companion firmware includes full keyboard support for standal
| B | Open web browser (BLE and 4G variants only) |
| T | Open SMS & Phone app (4G variant only) |
| P | Open audiobook player (audio variant only) |
| F | Open node discovery (search for nearby repeaters/nodes) |
| G | Open map screen (shows contacts with GPS positions) |
| Q | Back to home screen |
### Bluetooth (BLE)
@@ -165,6 +168,8 @@ When adding a hashtag channel, type the channel name and press Enter. The channe
If you've changed radio parameters, pressing Q will prompt you to apply changes before exiting.
> **Tip:** All device settings (plus mesh tuning parameters not available on-screen) can also be configured via USB serial. See the [Serial Settings Guide](Serial_Settings_Guide.md) for complete documentation.
### Compose Mode
| Key | Action |
@@ -349,4 +354,4 @@ However, this firmware links against libraries with different license terms. Bec
| [base64](https://github.com/Densaugeo/base64_arduino) | MIT | densaugeo |
| [Arduino Crypto](https://github.com/rweather/arduinolibs) | MIT | Rhys Weatherley |
Full license texts for each dependency are available in their respective repositories linked above.
Full license texts for each dependency are available in their respective repositories linked above.

View File

@@ -59,6 +59,12 @@ All commands follow a simple pattern: `get` to read, `set` to write.
| `get notify` | Keyboard flash notification (on/off) |
| `get gps` | GPS status and interval |
| `get pin` | BLE pairing PIN |
| `get path.hash.mode` | Path hash size (0=1-byte, 1=2-byte, 2=3-byte) |
| `get rxdelay` | Rx delay base (0=disabled) |
| `get af` | Airtime factor |
| `get multi.acks` | Redundant ACKs (0 or 1) |
| `get int.thresh` | Interference threshold (0=disabled) |
| `get gps.baud` | GPS baud rate (0=compile-time default) |
| `get channels` | List all channels with index numbers |
| `get presets` | List all radio presets with parameters |
| `get pubkey` | Device public key (hex) |
@@ -163,6 +169,78 @@ set notify off
set pin 123456
```
#### Path Hash Mode
Controls the byte size of each repeater's identity stamp in forwarded flood packets. Larger hashes reduce collisions at the cost of fewer maximum hops.
```
set path.hash.mode 1
```
| Mode | Bytes/hop | Max hops | Notes |
|------|-----------|----------|-------|
| 0 | 1 | 64 | Legacy — prone to hash collisions in larger networks |
| 1 | 2 | 32 | Recommended — effectively eliminates collisions |
| 2 | 3 | 21 | Maximum precision, rarely needed |
Nodes with different modes can coexist — the mode only affects packets your node originates. The hash size is encoded in each packet's header, so receiving nodes adapt automatically.
### Mesh Tuning
These settings control how the device participates in the mesh network. They take effect immediately — no reboot required (except `gps.baud`).
#### Rx Delay (rxdelay)
Delays processing of flood packets based on signal quality. Stronger signals are processed first; weaker copies wait longer and are typically discarded as duplicates. Direct messages are always processed immediately.
```
set rxdelay 3
```
Range: 020 (0 = disabled, default). Higher values create larger timing differences between strong and weak signals. Values below 1.0 have no practical effect. See the [MeshSydney wiki](https://meshsydney.com/wiki) for detailed tuning profiles.
#### Airtime Factor (af)
Adjusts how long certain internal timing windows remain open. Does not change the LoRa radio parameters (SF, BW, CR) — those remain as configured.
```
set af 1.0
```
Range: 09 (default: 1.0). Keep this value consistent across nodes in your mesh for best coherence.
#### Multiple Acknowledgments (multi.acks)
Sends redundant ACK packets for direct messages. When enabled, two ACKs are sent (a multi-ack first, then the standard ACK), improving delivery confirmation reliability.
```
set multi.acks 1
```
Values: 0 (single ACK) or 1 (redundant ACKs, default).
#### Interference Threshold (int.thresh)
Enables channel activity scanning before transmitting. Not recommended unless your device is in a high RF interference environment — specifically where the noise floor is low but shows significant fluctuations indicating interference. Enabling this adds approximately 4 seconds of receive delay per packet.
```
set int.thresh 14
set int.thresh 0
```
Values: 0 (disabled, default) or 14+ (14 is the typical setting). Values between 113 are not functional and will be rejected.
#### GPS Baud Rate (gps.baud)
Override the GPS serial baud rate. The default (0) uses the compile-time value of 38400. **Requires a reboot to take effect** — the GPS serial port is only configured at startup.
```
set gps.baud 9600
set gps.baud 0
```
Valid rates: 0 (default), 4800, 9600, 19200, 38400, 57600, 115200.
### Channel Management
#### List Channels

View File

@@ -257,7 +257,7 @@ float MyMesh::getAirtimeBudgetFactor() const {
}
int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved
return _prefs.interference_threshold;
}
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
@@ -1133,6 +1133,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
// defaults
memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half
_prefs.multi_acks = 1; // redundant ACKs on by default
strcpy(_prefs.node_name, "NONAME");
_prefs.freq = LORA_FREQ;
_prefs.sf = LORA_SF;
@@ -1183,6 +1184,17 @@ void MyMesh::begin(bool has_display) {
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
_prefs.utc_offset_hours = constrain(_prefs.utc_offset_hours, -12, 14); // Valid timezone range
// gps_baudrate: 0 means use compile-time default; validate known rates
if (_prefs.gps_baudrate != 0 && _prefs.gps_baudrate != 4800 &&
_prefs.gps_baudrate != 9600 && _prefs.gps_baudrate != 19200 &&
_prefs.gps_baudrate != 38400 && _prefs.gps_baudrate != 57600 &&
_prefs.gps_baudrate != 115200) {
_prefs.gps_baudrate = 0; // reset to default if invalid
}
// interference_threshold: 0 = disabled, minimum functional value is 14
if (_prefs.interference_threshold > 0 && _prefs.interference_threshold < 14) {
_prefs.interference_threshold = 0;
}
#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {
@@ -2130,20 +2142,26 @@ void MyMesh::enterCLIRescue() {
void MyMesh::checkCLIRescueCmd() {
int len = strlen(cli_command);
bool line_complete = false;
while (Serial.available() && len < sizeof(cli_command)-1) {
char c = Serial.read();
if (c != '\n') {
cli_command[len++] = c;
cli_command[len] = 0;
if (c == '\r' || c == '\n') {
if (len > 0) {
line_complete = true;
Serial.println(); // echo newline
}
break; // stop reading — remaining LF (from CR+LF) is consumed next loop
}
cli_command[len++] = c;
cli_command[len] = 0;
Serial.print(c); // echo
}
if (len == sizeof(cli_command)-1) { // command buffer full
cli_command[sizeof(cli_command)-1] = '\r';
if (len == sizeof(cli_command)-1) { // buffer full — force processing
line_complete = true;
}
if (len > 0 && cli_command[len - 1] == '\r') { // received complete line
cli_command[len - 1] = 0; // replace newline with C string null terminator
if (line_complete && len > 0) {
cli_command[len] = 0; // ensure null terminated
// =====================================================================
// GET commands — read settings
@@ -2174,6 +2192,21 @@ void MyMesh::checkCLIRescueCmd() {
_prefs.gps_enabled ? "on" : "off", _prefs.gps_interval);
} else if (strcmp(key, "pin") == 0) {
Serial.printf(" > %06d\n", _prefs.ble_pin);
// --- Mesh tuning parameters ---
} else if (strcmp(key, "rxdelay") == 0) {
Serial.printf(" > %.1f\n", _prefs.rx_delay_base);
} else if (strcmp(key, "af") == 0) {
Serial.printf(" > %.1f\n", _prefs.airtime_factor);
} else if (strcmp(key, "multi.acks") == 0) {
Serial.printf(" > %d\n", _prefs.multi_acks);
} else if (strcmp(key, "int.thresh") == 0) {
Serial.printf(" > %d\n", _prefs.interference_threshold);
} else if (strcmp(key, "gps.baud") == 0) {
uint32_t effective = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE;
Serial.printf(" > %lu (effective: %lu)\n",
(unsigned long)_prefs.gps_baudrate, (unsigned long)effective);
} else if (strcmp(key, "radio") == 0) {
Serial.printf(" > freq=%.3f bw=%.1f sf=%d cr=%d tx=%d\n",
_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr, _prefs.tx_power_dbm);
@@ -2225,6 +2258,14 @@ void MyMesh::checkCLIRescueCmd() {
Serial.printf(" gps: %s (interval: %ds)\n",
_prefs.gps_enabled ? "on" : "off", _prefs.gps_interval);
Serial.printf(" pin: %06d\n", _prefs.ble_pin);
Serial.printf(" rxdelay: %.1f\n", _prefs.rx_delay_base);
Serial.printf(" af: %.1f\n", _prefs.airtime_factor);
Serial.printf(" multi.acks: %d\n", _prefs.multi_acks);
Serial.printf(" int.thresh: %d\n", _prefs.interference_threshold);
{
uint32_t eff_baud = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE;
Serial.printf(" gps.baud: %lu\n", (unsigned long)eff_baud);
}
#ifdef HAS_4G_MODEM
Serial.printf(" modem: %s\n", ModemManager::loadEnabledConfig() ? "on" : "off");
Serial.printf(" apn: %s\n", modemManager.getAPN());
@@ -2558,6 +2599,69 @@ void MyMesh::checkCLIRescueCmd() {
Serial.println(" > modem disabled");
#endif
// --- Mesh tuning parameters ---
} else if (memcmp(config, "rxdelay ", 8) == 0) {
float val = atof(&config[8]);
if (val >= 0.0f && val <= 20.0f) {
_prefs.rx_delay_base = val;
savePrefs();
Serial.printf(" > rxdelay = %.1f\n", _prefs.rx_delay_base);
} else {
Serial.println(" Error: rxdelay out of range (0-20)");
}
} else if (memcmp(config, "af ", 3) == 0) {
float val = atof(&config[3]);
if (val >= 0.0f && val <= 9.0f) {
_prefs.airtime_factor = val;
savePrefs();
Serial.printf(" > af = %.1f\n", _prefs.airtime_factor);
} else {
Serial.println(" Error: af out of range (0-9)");
}
} else if (memcmp(config, "multi.acks ", 11) == 0) {
int val = atoi(&config[11]);
if (val == 0 || val == 1) {
_prefs.multi_acks = (uint8_t)val;
savePrefs();
Serial.printf(" > multi.acks = %d\n", _prefs.multi_acks);
} else {
Serial.println(" Error: use 0 or 1");
}
// Interference threshold — not recommended unless the device is in a high
// RF interference environment (low noise floor with significant fluctuations).
// Enabling adds ~4s receive delay per packet for channel activity scanning.
} else if (memcmp(config, "int.thresh ", 11) == 0) {
int val = atoi(&config[11]);
if (val == 0) {
_prefs.interference_threshold = 0;
savePrefs();
Serial.println(" > int.thresh = 0 (disabled)");
} else if (val >= 14 && val <= 255) {
_prefs.interference_threshold = (uint8_t)val;
savePrefs();
Serial.printf(" > int.thresh = %d (enabled — adds ~4s rx delay)\n",
_prefs.interference_threshold);
Serial.println(" Note: only recommended for high RF interference environments");
} else {
Serial.println(" Error: use 0 (disabled) or 14+ (typical: 14)");
}
} else if (memcmp(config, "gps.baud ", 9) == 0) {
uint32_t val = (uint32_t)atol(&config[9]);
if (val == 0 || val == 4800 || val == 9600 || val == 19200 ||
val == 38400 || val == 57600 || val == 115200) {
_prefs.gps_baudrate = val;
savePrefs();
uint32_t effective = val ? val : GPS_BAUDRATE;
Serial.printf(" > gps.baud = %lu (effective: %lu, reboot to apply)\n",
(unsigned long)val, (unsigned long)effective);
} else {
Serial.println(" Error: use 0 (default), 4800, 9600, 19200, 38400, 57600, or 115200");
}
} else {
Serial.printf(" Error: unknown setting '%s' (try 'help')\n", config);
}
@@ -2574,6 +2678,13 @@ void MyMesh::checkCLIRescueCmd() {
Serial.println(" name, freq, bw, sf, cr, tx, utc, notify, pin");
Serial.println(" path.hash.mode Path hash size (0=1B, 1=2B, 2=3B)");
Serial.println("");
Serial.println(" Mesh tuning:");
Serial.println(" rxdelay <0-20> Rx delay base (0=disabled)");
Serial.println(" af <0-9> Airtime factor");
Serial.println(" multi.acks <0|1> Redundant ACKs (default: 1)");
Serial.println(" int.thresh <0|14+> Interference threshold dB (0=off, 14=typical)");
Serial.println(" gps.baud <rate> GPS baud (0=default, reboot to apply)");
Serial.println("");
Serial.println(" Compound commands:");
Serial.println(" get all Dump all settings");
Serial.println(" get radio Show all radio params");

View File

@@ -8,11 +8,11 @@
#define FIRMWARE_VER_CODE 10
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "7 March 2026"
#define FIRMWARE_BUILD_DATE "8 March 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "Meck v0.9.9"
#define FIRMWARE_VERSION "Meck v1.0"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

View File

@@ -33,4 +33,6 @@ struct NodePrefs { // persisted to file
uint8_t ringtone_enabled; // Ringtone on incoming call (0=off, 1=on) — 4G only
uint8_t path_hash_mode; // 0=1-byte (legacy), 1=2-byte, 2=3-byte path hashes
uint8_t autoadd_max_hops; // 0=no limit, N=up to N-1 hops (max 64)
uint32_t gps_baudrate; // GPS baud rate (0 = use compile-time GPS_BAUDRATE default)
uint8_t interference_threshold; // Interference threshold in dB (0=disabled, 14+=enabled)
};

View File

@@ -730,8 +730,12 @@ void setup() {
// We need to reinitialize Serial2 to reclaim them
#if HAS_GPS
Serial2.end(); // Close any existing Serial2
Serial2.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS after sensors.begin()");
{
uint32_t gps_baud = the_mesh.getNodePrefs()->gps_baudrate;
if (gps_baud == 0) gps_baud = GPS_BAUDRATE;
Serial2.begin(gps_baud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS at %lu baud", (unsigned long)gps_baud);
}
#endif
#ifdef DISPLAY_CLASS

View File

@@ -142,7 +142,7 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D MECK_AUDIO_VARIANT
-D MECK_WEB_READER=1
-D FIRMWARE_VERSION='"Meck v0.9.9WiFi"'
-D FIRMWARE_VERSION='"Meck v1.0.WiFi"'
build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
@@ -192,7 +192,7 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D HAS_4G_MODEM=1
-D MECK_WEB_READER=1
-D FIRMWARE_VERSION='"Meck v0.9.94G"'
-D FIRMWARE_VERSION='"Meck v1.0.4G"'
build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
@@ -222,7 +222,7 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D HAS_4G_MODEM=1
-D MECK_WEB_READER=1
-D FIRMWARE_VERSION='"Meck v0.9.94G.WiFi"'
-D FIRMWARE_VERSION='"Meck v1.0.4G.WiFi"'
build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
@@ -248,7 +248,7 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D HAS_4G_MODEM=1
-D MECK_WEB_READER=1
-D FIRMWARE_VERSION='"Meck v0.9.94G.SA"'
-D FIRMWARE_VERSION='"Meck v1.0.4G.SA"'
build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>

View File

@@ -49,19 +49,11 @@ bool radio_init() {
loraSpi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS);
MESH_DEBUG_PRINTLN("radio_init() - SPI initialized, calling radio.std_init()...");
bool result = radio.std_init(&loraSpi);
if (result) {
radio.setPreambleLength(32);
MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols");
}
MESH_DEBUG_PRINTLN("radio_init() - radio.std_init() returned: %s", result ? "SUCCESS" : "FAILED");
return result;
#else
MESH_DEBUG_PRINTLN("radio_init() - calling radio.std_init() without custom SPI...");
bool result = radio.std_init();
if (result) {
radio.setPreambleLength(32);
MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols");
}
return result;
#endif
}
@@ -75,6 +67,14 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
// Longer preamble for low SF improves reliability — each symbol is shorter
// at low SF, so more symbols are needed for reliable detection.
// SF <= 8 gets 32 symbols (~65ms at SF7/62.5kHz); SF >= 9 keeps 16 (already ~131ms+).
// See: https://github.com/meshcore-dev/MeshCore/pull/1954
uint16_t preamble = (sf <= 8) ? 32 : 16;
radio.setPreambleLength(preamble);
MESH_DEBUG_PRINTLN("radio_set_params() - bw=%.1f sf=%u preamble=%u", bw, sf, preamble);
}
void radio_set_tx_power(uint8_t dbm) {