- test/kiss_client.py: Full-featured KISS modem client library Supports all 14 config commands, KISS frame encoding/decoding, signal quality reporting, packet send/receive Can communicate over serial ports or file-like objects (stdin/stdout) - test/test_integration.py: Integration test suite using kiss_client.py Tests radio configuration, parameter setting, KISS frame encoding/decoding Designed to run against modem or simulator Run with: python3 test/test_integration.py - test/main_simulator.cpp: Hardware simulator scaffold for native platform Mocks SX126X radio with configurable state Communicates via stdin/stdout for easy testing Note: Full compilation requires Arduino.h mock (future work) Verified: seeed_xiao_s3_wio_sx1262 and heltec_v3 still build without errors
205 lines
7.6 KiB
C++
205 lines
7.6 KiB
C++
/*
|
|
* Dummy LoRa Radio Hardware Simulator
|
|
*
|
|
* Simulates a LoRa radio on the native (host) platform using stdio for communication.
|
|
* Allows testing the KISS protocol and modem logic without hardware.
|
|
*
|
|
* Compiled as PlatformIO native environment for integration testing.
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/select.h>
|
|
|
|
/* Include the KISS implementation directly */
|
|
#include "../src/kiss.cpp"
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Simulated Radio State */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
struct {
|
|
uint32_t freq_khz;
|
|
uint32_t bw_hz;
|
|
uint8_t sf;
|
|
uint8_t cr;
|
|
int8_t power_dbm;
|
|
uint8_t syncword;
|
|
bool rx_active;
|
|
uint8_t rx_buf[256];
|
|
int rx_len;
|
|
int8_t rx_snr;
|
|
int16_t rx_rssi;
|
|
} radio_state = {
|
|
.freq_khz = 868000,
|
|
.bw_hz = 125000,
|
|
.sf = 7,
|
|
.cr = 5,
|
|
.power_dbm = 14,
|
|
.syncword = 0x12,
|
|
.rx_active = true,
|
|
.rx_len = 0,
|
|
};
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Mock C API (must match radio.h) */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct {
|
|
uint32_t freq_khz;
|
|
uint32_t bw_hz;
|
|
uint8_t sf;
|
|
uint8_t cr;
|
|
int8_t power_dbm;
|
|
uint8_t syncword;
|
|
} radio_config_t;
|
|
|
|
typedef struct {
|
|
int8_t snr;
|
|
int16_t rssi;
|
|
} radio_rx_info_t;
|
|
|
|
int radio_init(void) {
|
|
fprintf(stderr, "[radio] Initialized: %lu kHz, %lu Hz BW, SF %d, CR %d, %d dBm\n",
|
|
radio_state.freq_khz, radio_state.bw_hz, radio_state.sf, radio_state.cr,
|
|
radio_state.power_dbm);
|
|
return 0;
|
|
}
|
|
|
|
int radio_tx(const uint8_t *data, size_t len) {
|
|
fprintf(stderr, "[radio] TX %zu bytes at %lu kHz\n", len, radio_state.freq_khz);
|
|
return 0;
|
|
}
|
|
|
|
int radio_rx_start(void) {
|
|
radio_state.rx_active = true;
|
|
fprintf(stderr, "[radio] RX started\n");
|
|
return 0;
|
|
}
|
|
|
|
bool radio_rx_available(void) { return radio_state.rx_len > 0; }
|
|
|
|
int radio_rx_read(uint8_t *buf, size_t buf_cap, radio_rx_info_t *info) {
|
|
if (radio_state.rx_len == 0)
|
|
return 0;
|
|
|
|
size_t n = (radio_state.rx_len < (int)buf_cap) ? radio_state.rx_len : buf_cap;
|
|
memcpy(buf, radio_state.rx_buf, n);
|
|
|
|
if (info) {
|
|
info->snr = radio_state.rx_snr;
|
|
info->rssi = radio_state.rx_rssi;
|
|
}
|
|
|
|
radio_state.rx_len = 0;
|
|
radio_rx_start();
|
|
return (int)n;
|
|
}
|
|
|
|
int radio_set_config(const radio_config_t *cfg) {
|
|
radio_state.freq_khz = cfg->freq_khz;
|
|
radio_state.bw_hz = cfg->bw_hz;
|
|
radio_state.sf = cfg->sf;
|
|
radio_state.cr = cfg->cr;
|
|
radio_state.power_dbm = cfg->power_dbm;
|
|
radio_state.syncword = cfg->syncword;
|
|
fprintf(stderr, "[radio] Config updated: %lu kHz, %lu Hz BW, SF %d, CR %d, %d dBm\n",
|
|
cfg->freq_khz, cfg->bw_hz, cfg->sf, cfg->cr, cfg->power_dbm);
|
|
return 0;
|
|
}
|
|
|
|
void radio_get_config(radio_config_t *cfg) {
|
|
cfg->freq_khz = radio_state.freq_khz;
|
|
cfg->bw_hz = radio_state.bw_hz;
|
|
cfg->sf = radio_state.sf;
|
|
cfg->cr = radio_state.cr;
|
|
cfg->power_dbm = radio_state.power_dbm;
|
|
cfg->syncword = radio_state.syncword;
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Serial I/O Wrapper (stdio) */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
static void set_stdin_nonblocking(void) {
|
|
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
|
|
}
|
|
|
|
static int stdin_available(void) {
|
|
fd_set readfds;
|
|
FD_ZERO(&readfds);
|
|
FD_SET(STDIN_FILENO, &readfds);
|
|
struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
|
|
return select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) > 0;
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Dummy Serial replacement (for main.cpp) */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
namespace DummySerial {
|
|
bool initialized = false;
|
|
}
|
|
|
|
/* Mock Serial object for Arduino compatibility */
|
|
class DummySerialClass {
|
|
public:
|
|
void begin(unsigned long baud) {
|
|
fprintf(stderr, "[serial] begin(%lu)\n", baud);
|
|
DummySerial::initialized = true;
|
|
set_stdin_nonblocking();
|
|
}
|
|
|
|
int available(void) { return stdin_available(); }
|
|
|
|
int read(void) {
|
|
unsigned char c;
|
|
if (::read(STDIN_FILENO, &c, 1) == 1)
|
|
return c;
|
|
return -1;
|
|
}
|
|
|
|
size_t write(const uint8_t *buf, size_t size) {
|
|
return fwrite(buf, 1, size, stdout);
|
|
}
|
|
|
|
size_t write(uint8_t c) { return putchar(c) == EOF ? 0 : 1; }
|
|
|
|
int peek(void) { return -1; }
|
|
|
|
void flush(void) { fflush(stdout); }
|
|
};
|
|
|
|
DummySerialClass Serial;
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Include main modem logic */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
#include "../src/main.cpp"
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
/* Main Entry Point */
|
|
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
|
|
int main(int argc, char *argv[]) {
|
|
fprintf(stderr, "[simulator] LoRa KISS Modem Simulator\n");
|
|
fprintf(stderr, "[simulator] Reading KISS frames from stdin, writing to stdout\n");
|
|
fprintf(stderr, "[simulator] Ctrl+D to exit\n\n");
|
|
|
|
setup();
|
|
|
|
int iterations = 0;
|
|
while (true) {
|
|
loop();
|
|
iterations++;
|
|
usleep(1000); /* 1ms sleep to avoid busy waiting */
|
|
}
|
|
|
|
return 0;
|
|
}
|