mirror of https://github.com/meshcore-dev/MeshCore
5 changed files with 706 additions and 0 deletions
@ -0,0 +1,110 @@ |
|||
# 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) | |
|||
| `CMD_SET_SYNC_WORD` | `0x0B` | Sync word (1) | |
|||
| `CMD_GET_RADIO` | `0x0C` | - | |
|||
| `CMD_GET_TX_POWER` | `0x0D` | - | |
|||
| `CMD_GET_SYNC_WORD` | `0x0E` | - | |
|||
| `CMD_GET_VERSION` | `0x0F` | - | |
|||
|
|||
### Response Commands (Modem → Host) |
|||
|
|||
| Command | Value | Data | |
|||
|---------|-------|------| |
|||
| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | |
|||
| `RESP_IDENTITY` | `0x11` | PubKey (32) | |
|||
| `RESP_RANDOM` | `0x12` | Random bytes (1-64) | |
|||
| `RESP_VERIFY` | `0x13` | Result (1): 0x00=invalid, 0x01=valid | |
|||
| `RESP_SIGNATURE` | `0x14` | Signature (64) | |
|||
| `RESP_ENCRYPTED` | `0x15` | MAC (2) + Ciphertext | |
|||
| `RESP_DECRYPTED` | `0x16` | Plaintext | |
|||
| `RESP_SHARED_SECRET` | `0x17` | Shared secret (32) | |
|||
| `RESP_HASH` | `0x18` | SHA-256 hash (32) | |
|||
| `RESP_OK` | `0x19` | - | |
|||
| `RESP_RADIO` | `0x1A` | Freq (4) + BW (4) + SF (1) + CR (1) | |
|||
| `RESP_TX_POWER` | `0x1B` | Power dBm (1) | |
|||
| `RESP_SYNC_WORD` | `0x1C` | Sync word (1) | |
|||
| `RESP_VERSION` | `0x1D` | Version (1) + Reserved (1) | |
|||
| `RESP_ERROR` | `0x1E` | Error code (1) | |
|||
| `RESP_TX_DONE` | `0x1F` | Result (1): 0x00=failed, 0x01=success | |
|||
|
|||
## Error Codes |
|||
|
|||
| Code | Value | Description | |
|||
|------|-------|-------------| |
|||
| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | |
|||
| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | |
|||
| `ERR_NO_CALLBACK` | `0x03` | Radio callback not set | |
|||
| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | |
|||
| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | |
|||
| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | |
|||
|
|||
## 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 | |
|||
|
|||
## 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 |
|||
- See [packet_structure.md](./packet_structure.md) for packet format |
|||
@ -0,0 +1,362 @@ |
|||
#include "KissModem.h" |
|||
|
|||
KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng) |
|||
: _serial(serial), _identity(identity), _rng(rng) { |
|||
_rx_len = 0; |
|||
_rx_escaped = false; |
|||
_rx_active = false; |
|||
_has_pending_tx = false; |
|||
_pending_tx_len = 0; |
|||
_setRadioCallback = nullptr; |
|||
_setTxPowerCallback = nullptr; |
|||
_setSyncWordCallback = nullptr; |
|||
_config = {0, 0, 0, 0, 0, 0x12}; |
|||
} |
|||
|
|||
void KissModem::begin() { |
|||
_rx_len = 0; |
|||
_rx_escaped = false; |
|||
_rx_active = false; |
|||
_has_pending_tx = false; |
|||
} |
|||
|
|||
void KissModem::writeByte(uint8_t b) { |
|||
if (b == KISS_FEND) { |
|||
_serial.write(KISS_FESC); |
|||
_serial.write(KISS_TFEND); |
|||
} else if (b == KISS_FESC) { |
|||
_serial.write(KISS_FESC); |
|||
_serial.write(KISS_TFESC); |
|||
} else { |
|||
_serial.write(b); |
|||
} |
|||
} |
|||
|
|||
void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { |
|||
_serial.write(KISS_FEND); |
|||
writeByte(cmd); |
|||
for (uint16_t i = 0; i < len; i++) { |
|||
writeByte(data[i]); |
|||
} |
|||
_serial.write(KISS_FEND); |
|||
} |
|||
|
|||
void KissModem::writeErrorFrame(uint8_t error_code) { |
|||
writeFrame(RESP_ERROR, &error_code, 1); |
|||
} |
|||
|
|||
void KissModem::loop() { |
|||
while (_serial.available()) { |
|||
uint8_t b = _serial.read(); |
|||
|
|||
if (b == KISS_FEND) { |
|||
if (_rx_active && _rx_len > 0) { |
|||
processFrame(); |
|||
} |
|||
_rx_len = 0; |
|||
_rx_escaped = false; |
|||
_rx_active = true; |
|||
continue; |
|||
} |
|||
|
|||
if (!_rx_active) continue; |
|||
|
|||
if (b == KISS_FESC) { |
|||
_rx_escaped = true; |
|||
continue; |
|||
} |
|||
|
|||
if (_rx_escaped) { |
|||
_rx_escaped = false; |
|||
if (b == KISS_TFEND) b = KISS_FEND; |
|||
else if (b == KISS_TFESC) b = KISS_FESC; |
|||
} |
|||
|
|||
if (_rx_len < KISS_MAX_FRAME_SIZE) { |
|||
_rx_buf[_rx_len++] = b; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void KissModem::processFrame() { |
|||
if (_rx_len < 1) return; |
|||
|
|||
uint8_t cmd = _rx_buf[0]; |
|||
const uint8_t* data = &_rx_buf[1]; |
|||
uint16_t data_len = _rx_len - 1; |
|||
|
|||
switch (cmd) { |
|||
case CMD_DATA: |
|||
if (data_len < 2) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
} else if (data_len > KISS_MAX_PACKET_SIZE) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
} else { |
|||
memcpy(_pending_tx, data, data_len); |
|||
_pending_tx_len = data_len; |
|||
_has_pending_tx = true; |
|||
} |
|||
break; |
|||
case CMD_GET_IDENTITY: |
|||
handleGetIdentity(); |
|||
break; |
|||
case CMD_GET_RANDOM: |
|||
handleGetRandom(data, data_len); |
|||
break; |
|||
case CMD_VERIFY_SIGNATURE: |
|||
handleVerifySignature(data, data_len); |
|||
break; |
|||
case CMD_SIGN_DATA: |
|||
handleSignData(data, data_len); |
|||
break; |
|||
case CMD_ENCRYPT_DATA: |
|||
handleEncryptData(data, data_len); |
|||
break; |
|||
case CMD_DECRYPT_DATA: |
|||
handleDecryptData(data, data_len); |
|||
break; |
|||
case CMD_KEY_EXCHANGE: |
|||
handleKeyExchange(data, data_len); |
|||
break; |
|||
case CMD_HASH: |
|||
handleHash(data, data_len); |
|||
break; |
|||
case CMD_SET_RADIO: |
|||
handleSetRadio(data, data_len); |
|||
break; |
|||
case CMD_SET_TX_POWER: |
|||
handleSetTxPower(data, data_len); |
|||
break; |
|||
case CMD_SET_SYNC_WORD: |
|||
handleSetSyncWord(data, data_len); |
|||
break; |
|||
case CMD_GET_RADIO: |
|||
handleGetRadio(); |
|||
break; |
|||
case CMD_GET_TX_POWER: |
|||
handleGetTxPower(); |
|||
break; |
|||
case CMD_GET_SYNC_WORD: |
|||
handleGetSyncWord(); |
|||
break; |
|||
case CMD_GET_VERSION: |
|||
handleGetVersion(); |
|||
break; |
|||
default: |
|||
writeErrorFrame(ERR_UNKNOWN_CMD); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void KissModem::handleGetIdentity() { |
|||
writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); |
|||
} |
|||
|
|||
void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { |
|||
if (len < 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
uint8_t requested = data[0]; |
|||
if (requested < 1 || requested > 64) { |
|||
writeErrorFrame(ERR_INVALID_PARAM); |
|||
return; |
|||
} |
|||
|
|||
uint8_t buf[64]; |
|||
_rng.random(buf, requested); |
|||
writeFrame(RESP_RANDOM, buf, requested); |
|||
} |
|||
|
|||
void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { |
|||
if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
mesh::Identity signer(data); |
|||
const uint8_t* signature = data + PUB_KEY_SIZE; |
|||
const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; |
|||
uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; |
|||
|
|||
uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; |
|||
writeFrame(RESP_VERIFY, &result, 1); |
|||
} |
|||
|
|||
void KissModem::handleSignData(const uint8_t* data, uint16_t len) { |
|||
if (len < 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
uint8_t signature[SIGNATURE_SIZE]; |
|||
_identity.sign(signature, data, len); |
|||
writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); |
|||
} |
|||
|
|||
void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { |
|||
if (len < PUB_KEY_SIZE + 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
const uint8_t* key = data; |
|||
const uint8_t* plaintext = data + PUB_KEY_SIZE; |
|||
uint16_t plaintext_len = len - PUB_KEY_SIZE; |
|||
|
|||
uint8_t buf[KISS_MAX_FRAME_SIZE]; |
|||
int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); |
|||
|
|||
if (encrypted_len > 0) { |
|||
writeFrame(RESP_ENCRYPTED, buf, encrypted_len); |
|||
} else { |
|||
writeErrorFrame(ERR_ENCRYPT_FAILED); |
|||
} |
|||
} |
|||
|
|||
void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { |
|||
if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
const uint8_t* key = data; |
|||
const uint8_t* ciphertext = data + PUB_KEY_SIZE; |
|||
uint16_t ciphertext_len = len - PUB_KEY_SIZE; |
|||
|
|||
uint8_t buf[KISS_MAX_FRAME_SIZE]; |
|||
int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); |
|||
|
|||
if (decrypted_len > 0) { |
|||
writeFrame(RESP_DECRYPTED, buf, decrypted_len); |
|||
} else { |
|||
writeErrorFrame(ERR_MAC_FAILED); |
|||
} |
|||
} |
|||
|
|||
void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { |
|||
if (len < PUB_KEY_SIZE) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
uint8_t shared_secret[PUB_KEY_SIZE]; |
|||
_identity.calcSharedSecret(shared_secret, data); |
|||
writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); |
|||
} |
|||
|
|||
void KissModem::handleHash(const uint8_t* data, uint16_t len) { |
|||
if (len < 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
|
|||
uint8_t hash[32]; |
|||
mesh::Utils::sha256(hash, 32, data, len); |
|||
writeFrame(RESP_HASH, hash, 32); |
|||
} |
|||
|
|||
bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { |
|||
if (!_has_pending_tx) return false; |
|||
|
|||
memcpy(packet, _pending_tx, _pending_tx_len); |
|||
*len = _pending_tx_len; |
|||
_has_pending_tx = false; |
|||
return true; |
|||
} |
|||
|
|||
void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { |
|||
uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; |
|||
buf[0] = (uint8_t)snr; |
|||
buf[1] = (uint8_t)rssi; |
|||
memcpy(&buf[2], packet, len); |
|||
writeFrame(CMD_DATA, buf, 2 + len); |
|||
} |
|||
|
|||
void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { |
|||
if (len < 10) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
if (!_setRadioCallback) { |
|||
writeErrorFrame(ERR_NO_CALLBACK); |
|||
return; |
|||
} |
|||
|
|||
uint32_t freq_hz, bw_hz; |
|||
memcpy(&freq_hz, data, 4); |
|||
memcpy(&bw_hz, data + 4, 4); |
|||
uint8_t sf = data[8]; |
|||
uint8_t cr = data[9]; |
|||
|
|||
_config.freq_hz = freq_hz; |
|||
_config.bw_hz = bw_hz; |
|||
_config.sf = sf; |
|||
_config.cr = cr; |
|||
|
|||
float freq = freq_hz / 1000000.0f; |
|||
float bw = bw_hz / 1000.0f; |
|||
|
|||
_setRadioCallback(freq, bw, sf, cr); |
|||
writeFrame(RESP_OK, nullptr, 0); |
|||
} |
|||
|
|||
void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { |
|||
if (len < 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
if (!_setTxPowerCallback) { |
|||
writeErrorFrame(ERR_NO_CALLBACK); |
|||
return; |
|||
} |
|||
|
|||
_config.tx_power = data[0]; |
|||
_setTxPowerCallback(data[0]); |
|||
writeFrame(RESP_OK, nullptr, 0); |
|||
} |
|||
|
|||
void KissModem::handleSetSyncWord(const uint8_t* data, uint16_t len) { |
|||
if (len < 1) { |
|||
writeErrorFrame(ERR_INVALID_LENGTH); |
|||
return; |
|||
} |
|||
if (!_setSyncWordCallback) { |
|||
writeErrorFrame(ERR_NO_CALLBACK); |
|||
return; |
|||
} |
|||
|
|||
_config.sync_word = data[0]; |
|||
_setSyncWordCallback(data[0]); |
|||
writeFrame(RESP_OK, nullptr, 0); |
|||
} |
|||
|
|||
void KissModem::handleGetRadio() { |
|||
uint8_t buf[10]; |
|||
memcpy(buf, &_config.freq_hz, 4); |
|||
memcpy(buf + 4, &_config.bw_hz, 4); |
|||
buf[8] = _config.sf; |
|||
buf[9] = _config.cr; |
|||
writeFrame(RESP_RADIO, buf, 10); |
|||
} |
|||
|
|||
void KissModem::handleGetTxPower() { |
|||
writeFrame(RESP_TX_POWER, &_config.tx_power, 1); |
|||
} |
|||
|
|||
void KissModem::handleGetSyncWord() { |
|||
writeFrame(RESP_SYNC_WORD, &_config.sync_word, 1); |
|||
} |
|||
|
|||
void KissModem::handleGetVersion() { |
|||
uint8_t buf[2]; |
|||
buf[0] = KISS_FIRMWARE_VERSION; |
|||
buf[1] = 0; |
|||
writeFrame(RESP_VERSION, buf, 2); |
|||
} |
|||
|
|||
void KissModem::onTxComplete(bool success) { |
|||
uint8_t result = success ? 0x01 : 0x00; |
|||
writeFrame(RESP_TX_DONE, &result, 1); |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <Identity.h> |
|||
#include <Utils.h> |
|||
|
|||
#define KISS_FEND 0xC0 |
|||
#define KISS_FESC 0xDB |
|||
#define KISS_TFEND 0xDC |
|||
#define KISS_TFESC 0xDD |
|||
|
|||
#define KISS_MAX_FRAME_SIZE 512 |
|||
#define KISS_MAX_PACKET_SIZE 255 |
|||
|
|||
#define CMD_DATA 0x00 |
|||
#define CMD_GET_IDENTITY 0x01 |
|||
#define CMD_GET_RANDOM 0x02 |
|||
#define CMD_VERIFY_SIGNATURE 0x03 |
|||
#define CMD_SIGN_DATA 0x04 |
|||
#define CMD_ENCRYPT_DATA 0x05 |
|||
#define CMD_DECRYPT_DATA 0x06 |
|||
#define CMD_KEY_EXCHANGE 0x07 |
|||
#define CMD_HASH 0x08 |
|||
#define CMD_SET_RADIO 0x09 |
|||
#define CMD_SET_TX_POWER 0x0A |
|||
#define CMD_SET_SYNC_WORD 0x0B |
|||
#define CMD_GET_RADIO 0x0C |
|||
#define CMD_GET_TX_POWER 0x0D |
|||
#define CMD_GET_SYNC_WORD 0x0E |
|||
#define CMD_GET_VERSION 0x0F |
|||
|
|||
#define RESP_IDENTITY 0x11 |
|||
#define RESP_RANDOM 0x12 |
|||
#define RESP_VERIFY 0x13 |
|||
#define RESP_SIGNATURE 0x14 |
|||
#define RESP_ENCRYPTED 0x15 |
|||
#define RESP_DECRYPTED 0x16 |
|||
#define RESP_SHARED_SECRET 0x17 |
|||
#define RESP_HASH 0x18 |
|||
#define RESP_OK 0x19 |
|||
#define RESP_RADIO 0x1A |
|||
#define RESP_TX_POWER 0x1B |
|||
#define RESP_SYNC_WORD 0x1C |
|||
#define RESP_VERSION 0x1D |
|||
#define RESP_ERROR 0x1E |
|||
#define RESP_TX_DONE 0x1F |
|||
|
|||
#define ERR_INVALID_LENGTH 0x01 |
|||
#define ERR_INVALID_PARAM 0x02 |
|||
#define ERR_NO_CALLBACK 0x03 |
|||
#define ERR_MAC_FAILED 0x04 |
|||
#define ERR_UNKNOWN_CMD 0x05 |
|||
#define ERR_ENCRYPT_FAILED 0x06 |
|||
|
|||
#define KISS_FIRMWARE_VERSION 1 |
|||
|
|||
typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); |
|||
typedef void (*SetTxPowerCallback)(uint8_t power); |
|||
typedef void (*SetSyncWordCallback)(uint8_t syncWord); |
|||
|
|||
struct RadioConfig { |
|||
uint32_t freq_hz; |
|||
uint32_t bw_hz; |
|||
uint8_t sf; |
|||
uint8_t cr; |
|||
uint8_t tx_power; |
|||
uint8_t sync_word; |
|||
}; |
|||
|
|||
class KissModem { |
|||
Stream& _serial; |
|||
mesh::LocalIdentity& _identity; |
|||
mesh::RNG& _rng; |
|||
|
|||
uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; |
|||
uint16_t _rx_len; |
|||
bool _rx_escaped; |
|||
bool _rx_active; |
|||
|
|||
uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; |
|||
uint16_t _pending_tx_len; |
|||
bool _has_pending_tx; |
|||
|
|||
SetRadioCallback _setRadioCallback; |
|||
SetTxPowerCallback _setTxPowerCallback; |
|||
SetSyncWordCallback _setSyncWordCallback; |
|||
|
|||
RadioConfig _config; |
|||
|
|||
void writeByte(uint8_t b); |
|||
void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); |
|||
void writeErrorFrame(uint8_t error_code); |
|||
void processFrame(); |
|||
|
|||
void handleGetIdentity(); |
|||
void handleGetRandom(const uint8_t* data, uint16_t len); |
|||
void handleVerifySignature(const uint8_t* data, uint16_t len); |
|||
void handleSignData(const uint8_t* data, uint16_t len); |
|||
void handleEncryptData(const uint8_t* data, uint16_t len); |
|||
void handleDecryptData(const uint8_t* data, uint16_t len); |
|||
void handleKeyExchange(const uint8_t* data, uint16_t len); |
|||
void handleHash(const uint8_t* data, uint16_t len); |
|||
void handleSetRadio(const uint8_t* data, uint16_t len); |
|||
void handleSetTxPower(const uint8_t* data, uint16_t len); |
|||
void handleSetSyncWord(const uint8_t* data, uint16_t len); |
|||
void handleGetRadio(); |
|||
void handleGetTxPower(); |
|||
void handleGetSyncWord(); |
|||
void handleGetVersion(); |
|||
|
|||
public: |
|||
KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); |
|||
|
|||
void begin(); |
|||
void loop(); |
|||
|
|||
void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } |
|||
void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } |
|||
void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } |
|||
|
|||
bool getPacketToSend(uint8_t* packet, uint16_t* len); |
|||
void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); |
|||
void onTxComplete(bool success); |
|||
}; |
|||
@ -0,0 +1,108 @@ |
|||
#include <Arduino.h> |
|||
#include <target.h> |
|||
#include <helpers/ArduinoHelpers.h> |
|||
#include <helpers/IdentityStore.h> |
|||
#include "KissModem.h" |
|||
|
|||
#if defined(NRF52_PLATFORM) |
|||
#include <InternalFileSystem.h> |
|||
#elif defined(RP2040_PLATFORM) |
|||
#include <LittleFS.h> |
|||
#elif defined(ESP32) |
|||
#include <SPIFFS.h> |
|||
#endif |
|||
|
|||
StdRNG rng; |
|||
mesh::LocalIdentity identity; |
|||
KissModem* modem; |
|||
|
|||
void halt() { |
|||
while (1) ; |
|||
} |
|||
|
|||
void loadOrCreateIdentity() { |
|||
#if defined(NRF52_PLATFORM) |
|||
InternalFS.begin(); |
|||
IdentityStore store(InternalFS, ""); |
|||
#elif defined(ESP32) |
|||
SPIFFS.begin(true); |
|||
IdentityStore store(SPIFFS, "/identity"); |
|||
#elif defined(RP2040_PLATFORM) |
|||
LittleFS.begin(); |
|||
IdentityStore store(LittleFS, "/identity"); |
|||
store.begin(); |
|||
#else |
|||
#error "Filesystem not defined" |
|||
#endif |
|||
|
|||
if (!store.load("_main", identity)) { |
|||
identity = radio_new_identity(); |
|||
while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { |
|||
identity = radio_new_identity(); |
|||
} |
|||
store.save("_main", identity); |
|||
} |
|||
} |
|||
|
|||
void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { |
|||
radio_set_params(freq, bw, sf, cr); |
|||
} |
|||
|
|||
void onSetTxPower(uint8_t power) { |
|||
radio_set_tx_power(power); |
|||
} |
|||
|
|||
void onSetSyncWord(uint8_t sync_word) { |
|||
radio_set_sync_word(sync_word); |
|||
} |
|||
|
|||
void setup() { |
|||
board.begin(); |
|||
|
|||
if (!radio_init()) { |
|||
halt(); |
|||
} |
|||
|
|||
radio_driver.begin(); |
|||
|
|||
rng.begin(radio_get_rng_seed()); |
|||
loadOrCreateIdentity(); |
|||
|
|||
Serial.begin(115200); |
|||
uint32_t start = millis(); |
|||
while (!Serial && millis() - start < 3000) delay(10); |
|||
delay(100); |
|||
|
|||
modem = new KissModem(Serial, identity, rng); |
|||
modem->setRadioCallback(onSetRadio); |
|||
modem->setTxPowerCallback(onSetTxPower); |
|||
modem->setSyncWordCallback(onSetSyncWord); |
|||
modem->begin(); |
|||
} |
|||
|
|||
void loop() { |
|||
modem->loop(); |
|||
|
|||
uint8_t packet[KISS_MAX_PACKET_SIZE]; |
|||
uint16_t len; |
|||
|
|||
if (modem->getPacketToSend(packet, &len)) { |
|||
radio_driver.startSendRaw(packet, len); |
|||
while (!radio_driver.isSendComplete()) { |
|||
delay(1); |
|||
} |
|||
radio_driver.onSendFinished(); |
|||
modem->onTxComplete(true); |
|||
} |
|||
|
|||
uint8_t rx_buf[256]; |
|||
int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); |
|||
|
|||
if (rx_len > 0) { |
|||
int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); |
|||
int8_t rssi = (int8_t)radio_driver.getLastRSSI(); |
|||
modem->onPacketReceived(snr, rssi, rx_buf, rx_len); |
|||
} |
|||
|
|||
radio_driver.loop(); |
|||
} |
|||
Loading…
Reference in new issue