Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
4575800
Turn on register 0x8B5 LSB for improved RX, turn off boosted gain
Socalix Jan 14, 2026
46e4cc0
Revert boosted gain flag to original
Socalix Jan 22, 2026
3845a1c
Fix incorrect INA260 address in debug message
piotrmalek Jan 27, 2026
edeafde
Fix: Correct validation logic in isValidName function
jbrazio Jan 27, 2026
dd2a904
Refactor display scaling definitions for HELTEC_VISION_MASTER_T190
litruv Jan 28, 2026
f7e54ea
Add LilyGO T-Beam 1W Support
stevenlafl Jan 28, 2026
44e7c09
Add battery min/max voltage parameter support
stevenlafl Jan 28, 2026
a9a8299
Set LilyGO T-Beam 1W to use TX0 3.0V (within reference +2.85V~+3.15V)
stevenlafl Jan 28, 2026
3a7ccc0
fixed build errors and typos/inconsistencies
recrof Jan 29, 2026
2a321b5
renamed board files
recrof Jan 29, 2026
acca73f
Merge pull request #1524 from recrof/thinknode-m3-m6-fixes
fdlamotte Jan 29, 2026
31fbb67
Merge branch 'dev' into heltecv4-register1
Socalix Jan 29, 2026
c345f1d
Revert "Remove _serial->isConnected() logic from buzzer notifications"
recrof Jan 29, 2026
e50fe31
Merge pull request #1528 from recrof/revert-1437-dev
liamcottle Jan 29, 2026
e60fb14
Merge pull request #1500 from Meshcore-Portugal/jbrazio/2026_03a6aa94
ripplebiz Jan 30, 2026
cf7d95c
Merge pull request #1509 from stevenlafl/tbeam-1w
ripplebiz Jan 30, 2026
c7eea39
fix: remove esp_wifi.h from esp32board.h
oltaco Jan 30, 2026
019bbf7
Add recv_errors to CMD_GET_STATS STATS_TYPE_PACKETS response
agessaman Jan 30, 2026
06a83c0
Merge pull request #1531 from agessaman/add-recv-errors-stats
liamcottle Jan 30, 2026
c786cfe
Add KISS Modem firmware
ViezeVingertjes Jan 31, 2026
c5b1d30
t114: remove extra DCDC enable
oltaco Jan 31, 2026
e6e1b81
add DataStore::deleteBlobByKey()
oltaco Jan 27, 2026
31ba971
only store advblob when adding/updating contacts
oltaco Jan 27, 2026
8d5eaf5
add makeBlobPath inline helper for esp32
oltaco Jan 27, 2026
b5248fa
Revert "Merge pull request #1428 from etienn01/update-t114-i2c"
jbrazio Jan 31, 2026
1bcb52b
Add new commands and responses for RSSI, channel status, airtime, noi…
ViezeVingertjes Jan 31, 2026
240b5ea
Refactor KissModem to integrate radio and sensor management directly,…
ViezeVingertjes Jan 31, 2026
852c0b0
Merge pull request #1547 from oltaco/t114-remove-extra-dcdcen
ripplebiz Feb 1, 2026
f231347
Merge pull request #1512 from litruv/dev
ripplebiz Feb 1, 2026
13cfc9a
Merge pull request #1498 from piotrmalek/fix-env-sensor-manager
ripplebiz Feb 1, 2026
699d1cd
Merge pull request #1495 from oltaco/esp32-advblob-removal
ripplebiz Feb 1, 2026
a342ab8
nrf52: allow repeater to sleep when idle
oltaco Feb 1, 2026
2239307
Enable I2C sensors and EnvironmentSensorManager for Heltec T114
pinkolin Feb 1, 2026
ab99466
Merge pull request #1549 from Meshcore-Portugal/jbrazio/t114-fix
ripplebiz Feb 2, 2026
f0ba14f
Remove sync word handling from KissModem.
ViezeVingertjes Feb 2, 2026
84e68cf
initial port of M5Stack Unit C6L, update pioarduino to newer bugfix r…
recrof Feb 2, 2026
e03f311
Merge pull request #1545 from ViezeVingertjes/kiss-modem-firmware
ripplebiz Feb 3, 2026
598489b
refactor ui with ring buffer and display most recent
oltaco Jan 26, 2026
2360259
Merge pull request #1487 from oltaco/refactor-ui-with-ringbuffer
liamcottle Feb 4, 2026
0fb5703
fix(kiss): periodic noise floor calibration and AGC reset
agessaman Feb 4, 2026
4b9d546
Merge pull request #1532 from oltaco/esp32board.h-removewifi
ripplebiz Feb 5, 2026
76a46a7
Merge pull request #1562 from oltaco/nrf52-sleep-repeater
ripplebiz Feb 5, 2026
f7e92a7
Merge pull request #1398 from Socalix/heltecv4-register1
ripplebiz Feb 5, 2026
f0aa12f
Merge remote-tracking branch 'pikolin/T114_sensors' into 2026/t114-se…
jbrazio Feb 5, 2026
5cb26b9
Refactor Heltec T114 sensor management
jbrazio Feb 5, 2026
1847333
Merge remote-tracking branch 'upstream/dev' into 2026/t114-sensors
jbrazio Feb 5, 2026
c0b81b9
Clean up comments on kiss noise floor changes.
agessaman Feb 5, 2026
d0720c6
Allow negative tx power
weebl2000 Jan 3, 2026
6502067
Merge pull request #1591 from agessaman/fix-kiss-noise-floor
ripplebiz Feb 7, 2026
c1c9848
Merge pull request #1596 from jbrazio/2026/t114-sensors
ripplebiz Feb 7, 2026
bcb7a80
Merge pull request #1578 from recrof/m5stack-unit-c6l
ripplebiz Feb 7, 2026
c4c287d
Bridge always has work (prevents sleep)
weebl2000 Feb 7, 2026
e8646f5
Parse as signed int
weebl2000 Feb 7, 2026
fcfbb45
Refactor environment names and build flags for RAK variants
jbrazio Feb 7, 2026
10eacc4
Merge pull request #1316 from weebl2000/allow-negative-tx
liamcottle Feb 7, 2026
31a2e74
Correct manufacturer name 'Elecrow ThinkNode M5'
thanegill Feb 8, 2026
736ddbf
Merge pull request #1620 from thanegill/patch-2
liamcottle Feb 8, 2026
f6fc056
Merge pull request #1609 from weebl2000/bridge-always-has-work
ripplebiz Feb 8, 2026
b1094c2
Merge pull request #1616 from jbrazio/2026/raknorm
liamcottle Feb 8, 2026
d5b17b1
changed preambles to 32
OverkillFPV Feb 9, 2026
6a4c557
Merge branch 'meshcore-dev:main' into Longer-Preamble-Test
OverkillFPV Feb 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ For developers;
- Clone and open the MeshCore repository in Visual Studio Code.
- See the example applications you can modify and run:
- [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or WiFi.
- [KISS Modem](./examples/kiss_modem) - Serial KISS protocol bridge for host applications. ([protocol docs](./docs/kiss_modem_protocol.md))
- [Simple Repeater](./examples/simple_repeater) - Extends network coverage by relaying messages.
- [Simple Room Server](./examples/simple_room_server) - A simple BBS server for shared Posts.
- [Simple Secure Chat](./examples/simple_secure_chat) - Secure terminal based text communication between devices.
- [Simple Sensor](./examples/simple_sensor) - Remote sensor node with telemetry and alerting.

The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android.

Expand Down
50 changes: 50 additions & 0 deletions boards/t_beam_1w.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "lilygo_tbeam_1w"
},
"connectivity": [
"wifi",
"bluetooth",
"lora"
],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino"
],
"name": "LilyGo TBeam-1W",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "http://www.lilygo.cn/",
"vendor": "LilyGo"
}
162 changes: 162 additions & 0 deletions docs/kiss_modem_protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# MeshCore KISS Modem Protocol

Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity.

## Serial Configuration

115200 baud, 8N1, no flow control.

## Frame Format

Standard KISS framing with byte stuffing.

| Byte | Name | Description |
|------|------|-------------|
| `0xC0` | FEND | Frame delimiter |
| `0xDB` | FESC | Escape character |
| `0xDC` | TFEND | Escaped FEND (FESC + TFEND = 0xC0) |
| `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) |

```
┌──────┬─────────┬──────────────┬──────┐
│ FEND │ Command │ Data (escaped)│ FEND │
│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │
└──────┴─────────┴──────────────┴──────┘
```

Maximum unescaped frame size: 512 bytes.

## Commands

### Request Commands (Host → Modem)

| Command | Value | Data |
|---------|-------|------|
| `CMD_DATA` | `0x00` | Packet (2-255 bytes) |
| `CMD_GET_IDENTITY` | `0x01` | - |
| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) |
| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data |
| `CMD_SIGN_DATA` | `0x04` | Data to sign |
| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext |
| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext |
| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) |
| `CMD_HASH` | `0x08` | Data to hash |
| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) |
| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) |
| *reserved* | `0x0B` | *(not implemented)* |
| `CMD_GET_RADIO` | `0x0C` | - |
| `CMD_GET_TX_POWER` | `0x0D` | - |
| *reserved* | `0x0E` | *(not implemented)* |
| `CMD_GET_VERSION` | `0x0F` | - |
| `CMD_GET_CURRENT_RSSI` | `0x10` | - |
| `CMD_IS_CHANNEL_BUSY` | `0x11` | - |
| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) |
| `CMD_GET_NOISE_FLOOR` | `0x13` | - |
| `CMD_GET_STATS` | `0x14` | - |
| `CMD_GET_BATTERY` | `0x15` | - |
| `CMD_PING` | `0x16` | - |
| `CMD_GET_SENSORS` | `0x17` | Permissions (1) |

### Response Commands (Modem → Host)

| Command | Value | Data |
|---------|-------|------|
| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet |
| `RESP_IDENTITY` | `0x21` | PubKey (32) |
| `RESP_RANDOM` | `0x22` | Random bytes (1-64) |
| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid |
| `RESP_SIGNATURE` | `0x24` | Signature (64) |
| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext |
| `RESP_DECRYPTED` | `0x26` | Plaintext |
| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) |
| `RESP_HASH` | `0x28` | SHA-256 hash (32) |
| `RESP_OK` | `0x29` | - |
| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) |
| `RESP_TX_POWER` | `0x2B` | Power dBm (1) |
| *reserved* | `0x2C` | *(not implemented)* |
| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) |
| `RESP_ERROR` | `0x2E` | Error code (1) |
| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success |
| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) |
| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy |
| `RESP_AIRTIME` | `0x32` | Milliseconds (4) |
| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) |
| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) |
| `RESP_BATTERY` | `0x35` | Millivolts (2) |
| `RESP_PONG` | `0x36` | - |
| `RESP_SENSORS` | `0x37` | CayenneLPP payload |

## Error Codes

| Code | Value | Description |
|------|-------|-------------|
| `ERR_INVALID_LENGTH` | `0x01` | Request data too short |
| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value |
| `ERR_NO_CALLBACK` | `0x03` | Feature not available |
| `ERR_MAC_FAILED` | `0x04` | MAC verification failed |
| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command |
| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed |
| `ERR_TX_PENDING` | `0x07` | TX already pending |

## Data Formats

### Radio Parameters (CMD_SET_RADIO / RESP_RADIO)

All values little-endian.

| Field | Size | Description |
|-------|------|-------------|
| Frequency | 4 bytes | Hz (e.g., 869618000) |
| Bandwidth | 4 bytes | Hz (e.g., 62500) |
| SF | 1 byte | Spreading factor (5-12) |
| CR | 1 byte | Coding rate (5-8) |

### Received Packet (CMD_DATA response)

| Field | Size | Description |
|-------|------|-------------|
| SNR | 1 byte | Signal-to-noise × 4, signed |
| RSSI | 1 byte | Signal strength dBm, signed |
| Packet | variable | Raw MeshCore packet |

### Noise Floor (RESP_NOISE_FLOOR)

Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian.

| Field | Size | Description |
|--------------|------|--------------------------------|
| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 |

The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds.

### Stats (RESP_STATS)

Response to `CMD_GET_STATS` (0x14). All values little-endian.

| Field | Size | Description |
|-------|------|-------------|
| RX | 4 bytes | Packets received |
| TX | 4 bytes | Packets transmitted |
| Errors | 4 bytes | Receive errors |

### Sensor Permissions (CMD_GET_SENSORS)

| Bit | Value | Description |
|-----|-------|-------------|
| 0 | `0x01` | Base (battery) |
| 1 | `0x02` | Location (GPS) |
| 2 | `0x04` | Environment (temp, humidity, pressure) |

Use `0x07` for all permissions.

### Sensor Data (RESP_SENSORS)

Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing.

## Notes

- Modem generates identity on first boot (stored in flash)
- SNR values multiplied by 4 for 0.25 dB precision
- Wait for `RESP_TX_DONE` before sending next packet
- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING`
- See [packet_structure.md](./packet_structure.md) for packet format
26 changes: 21 additions & 5 deletions docs/stats_binary_frames.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ struct StatsRadio {

## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)

**Total Frame Size:** 26 bytes
**Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`)

| Offset | Size | Type | Field Name | Description | Range/Notes |
|--------|------|------|------------|-------------|-------------|
Expand All @@ -106,12 +106,14 @@ struct StatsRadio {
| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib); present only in 30-byte frame | 0 - 4,294,967,295 |

### Notes

- Counters are cumulative from boot and may wrap.
- `recv = flood_rx + direct_rx`
- `sent = flood_tx + direct_tx`
- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26.

### Example Structure (C/C++)

Expand All @@ -125,6 +127,7 @@ struct StatsPackets {
uint32_t direct_tx;
uint32_t flood_rx;
uint32_t direct_rx;
uint32_t recv_errors; // present when frame size is 30
} __attribute__((packed));
```

Expand Down Expand Up @@ -183,18 +186,23 @@ def parse_stats_radio(frame):
}

def parse_stats_packets(frame):
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)"""
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 or 30 bytes)"""
assert len(frame) >= 26, "STATS_TYPE_PACKETS frame too short"
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
struct.unpack('<B B I I I I I I', frame)
struct.unpack('<B B I I I I I I', frame[:26])
assert response_code == 24 and stats_type == 2, "Invalid response type"
return {
result = {
'recv': recv,
'sent': sent,
'flood_tx': flood_tx,
'direct_tx': direct_tx,
'flood_rx': flood_rx,
'direct_rx': direct_rx
}
if len(frame) >= 30:
(recv_errors,) = struct.unpack('<I', frame[26:30])
result['recv_errors'] = recv_errors
return result
```

---
Expand Down Expand Up @@ -251,6 +259,7 @@ interface StatsPackets {
direct_tx: number;
flood_rx: number;
direct_rx: number;
recv_errors?: number; // present when frame is 30 bytes
}

function parseStatsCore(buffer: ArrayBuffer): StatsCore {
Expand Down Expand Up @@ -286,19 +295,26 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {

function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
const view = new DataView(buffer);
if (buffer.byteLength < 26) {
throw new Error('STATS_TYPE_PACKETS frame too short');
}
const response_code = view.getUint8(0);
const stats_type = view.getUint8(1);
if (response_code !== 24 || stats_type !== 2) {
throw new Error('Invalid response type');
}
return {
const result: StatsPackets = {
recv: view.getUint32(2, true),
sent: view.getUint32(6, true),
flood_tx: view.getUint32(10, true),
direct_tx: view.getUint32(14, true),
flood_rx: view.getUint32(18, true),
direct_rx: view.getUint32(22, true)
};
if (buffer.byteLength >= 30) {
result.recv_errors = view.getUint32(26, true);
}
return result;
}
```

Expand Down
27 changes: 19 additions & 8 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,14 +560,20 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
}
return false; // error
}
bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) {
return true; // this is just a stub on NRF52/STM32 platforms
}
#else
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
char path[64];
inline void makeBlobPath(const uint8_t key[], int key_len, char* path, size_t path_size) {
char fname[18];

if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
mesh::Utils::toHex(fname, key, key_len);
sprintf(path, "/bl/%s", fname);
}

uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
char path[64];
makeBlobPath(key, key_len, path, sizeof(path));

if (_fs->exists(path)) {
File f = openRead(_fs, path);
Expand All @@ -582,11 +588,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b

bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) {
char path[64];
char fname[18];

if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
mesh::Utils::toHex(fname, key, key_len);
sprintf(path, "/bl/%s", fname);
makeBlobPath(key, key_len, path, sizeof(path));

File f = openWrite(_fs, path);
if (f) {
Expand All @@ -598,4 +600,13 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
}
return false; // error
}

bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) {
char path[64];
makeBlobPath(key, key_len, path, sizeof(path));

_fs->remove(path);

return true; // return true even if file did not exist
}
#endif
1 change: 1 addition & 0 deletions examples/companion_radio/DataStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class DataStore {
void migrateToSecondaryFS();
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
bool deleteBlobByKey(const uint8_t key[], int key_len);
File openRead(const char* filename);
File openRead(FILESYSTEM* fs, const char* filename);
bool removeFile(const char* filename);
Expand Down
Loading