diff --git a/lib/comms/comms.cpp b/lib/comms/comms.cpp index 0882096..19e66fb 100644 --- a/lib/comms/comms.cpp +++ b/lib/comms/comms.cpp @@ -10,6 +10,9 @@ Comms *HostComms; Comms *Comms0 = NULL; Comms *Comms1 = NULL; +RadioComms *RxComms = NULL; +RadioComms *TxComms = NULL; + void _onReceiveUsb(size_t len) { if (HostComms == NULL) @@ -102,6 +105,16 @@ bool Comms::initComms(Config &c) Serial.println("Configured none - Initialized no communications on Serial1"); } + if (c.rx_lora != NULL) + { + RxComms = new RadioComms("RxComms", radio, *c.rx_lora); + } + + if (c.tx_lora != NULL) + { + TxComms = new RadioComms("TxComms", radio, *c.tx_lora); + } + if (!fine) { HostComms = new NoopComms(); diff --git a/lib/comms/comms.h b/lib/comms/comms.h index 64663ad..455a8d4 100644 --- a/lib/comms/comms.h +++ b/lib/comms/comms.h @@ -2,6 +2,9 @@ #define __COMMS_H #include +#include + +#include #include #ifdef HELTEC @@ -109,4 +112,25 @@ extern Comms *Comms0; extern Comms *Comms1; +struct RadioComms +{ + String name; + RADIO_TYPE &radio; + LoRaConfig &loraCfg; + + RadioComms(String name, RADIO_TYPE &radio, LoRaConfig &cfg) + : name(name), radio(radio), loraCfg(cfg) + { + } + + Message **received; + + int16_t configureRadio(); + int16_t send(Message &); + Message *receive(uint16_t timeout_ms); +}; + +extern RadioComms *RxComms; +extern RadioComms *TxComms; + #endif diff --git a/lib/comms/radio_comms.cpp b/lib/comms/radio_comms.cpp new file mode 100644 index 0000000..c686c10 --- /dev/null +++ b/lib/comms/radio_comms.cpp @@ -0,0 +1,234 @@ +#include "comms.h" + +int16_t RadioComms::configureRadio() +{ + int16_t status = + radio.begin(loraCfg.freq, loraCfg.bw, loraCfg.sf, loraCfg.cr, loraCfg.sync_word, + loraCfg.tx_power, loraCfg.preamble_len); + if (status != RADIOLIB_ERR_NONE) + { + Serial.printf("could not configure Lora: %d\n", status); + return status; + } + + if (!loraCfg.crc) + { + status = radio.setCRC(0); + if (status != RADIOLIB_ERR_NONE) + { + Serial.printf("could not configure CRC for Lora: %d\n", status); + return status; + } + } + + return status; +} + +size_t _write(uint8_t *m, size_t sz, size_t p, uint8_t v) +{ + if (p >= sz) + return sz; + m[p] = v; + return p + 1; +} + +size_t _write(uint8_t *m, size_t sz, size_t p, uint8_t *v, size_t v_sz) +{ + while (v_sz > 0) + { + p = _write(m, sz, p, *v); + v++; + v_sz--; + } + + return p; +} + +#define MAX_MSG 128 +#define RSSI_HI -80 +#define DETAIL_RSSIS 8 +#define RSSI_LO (RSSI_HI - 1 - DETAIL_RSSIS) +int16_t RadioComms::send(Message &m) +{ + if (m.type != SCAN_RESULT) + { + return RADIOLIB_ERR_INVALID_FUNCTION; + } + + uint8_t msg[MAX_MSG]; + size_t dump_sz = m.payload.dump.sz; + size_t p = _write(msg, MAX_MSG, 0, (uint8_t)m.type); + + // first cut: dump the RSSI as-is + // optimize the message size later + p = _write(msg, MAX_MSG, p, (uint8_t *)&m.payload.dump.freqs_khz[0], 4); + p = _write(msg, MAX_MSG, p, (uint8_t *)&m.payload.dump.freqs_khz[dump_sz - 1], 4); + p = _write(msg, MAX_MSG, p, (uint8_t *)&dump_sz, 2); + + size_t rem = MAX_MSG - p; + if (rem > dump_sz) + rem = dump_sz; + + uint8_t bits = 0; + size_t pp = p; + for (int i = 0; i < dump_sz; i++) + { + if (i * rem / dump_sz > p - pp) + { + p = _write(msg, MAX_MSG, p, bits); + bits = 0; + } + int16_t v = m.payload.dump.rssis[i]; + if (v >= 0) + { + v = 255; + } + else + { + v += 255; + if (v < 0) + { + v = 0; + } + } + + bits = max(bits, (uint8_t)v); + } + + if (dump_sz > 0) + { + p = _write(msg, MAX_MSG, p, bits); + } + + return radio.transmit(msg, p); +} + +size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v, size_t len) +{ + for (; p < sz && len > 0; v++, p++, len--) + { + *v = buf[p]; + } + + return p; +} + +size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v) +{ + return _read(buf, sz, p, v, 1); +} + +volatile bool _received = false; +void _rcv() { _received = true; } + +Message *RadioComms::receive(uint16_t timeout_ms) +{ + uint8_t msg[MAX_MSG]; + + // because of this, receive is single-threaded, single-device + _received = false; + radio.setDio1Action(_rcv); + uint32_t timeout_ticks = (uint32_t)timeout_ms * (1000000 / 15625); + + int16_t status = radio.startReceive(timeout_ticks); + if (status != RADIOLIB_ERR_NONE) + { + radio.clearDio1Action(); + Serial.printf("Failed to start receive: %d\n", status); + return NULL; + } + + // wait on a semaphore + while (!_received) + { + yield(); + } + radio.clearDio1Action(); + + size_t len = radio.getPacketLength(true); + uint8_t *packet = msg; + + if (len > MAX_MSG) + { + packet = new uint8_t[len]; + } + status = radio.readData(packet, len); + + if (status == RADIOLIB_ERR_RX_TIMEOUT) + { + return NULL; + } + + if (status != RADIOLIB_ERR_NONE || len == 0) + { + if (packet != msg) + delete[] packet; + + Serial.printf("Failed to read data: E(%d), len == %d\n", status, len); + return NULL; + } + + if (len > MAX_MSG) + { + Serial.printf("Packet is longer than expected: %d\n", len); + } + + size_t p; + uint8_t b; + p = _read(packet, len, 0, &b); + + Message *message = NULL; + if (b == SCAN_RESULT) + { + message = new Message(); + message->type = SCAN_RESULT; + + uint32_t s, e; + size_t dump_sz = 0; + p = _read(packet, len, p, (uint8_t *)&s, 4); + p = _read(packet, len, p, (uint8_t *)&e, 4); + p = _read(packet, len, p, (uint8_t *)&dump_sz, 2); + size_t rem = len - p; + + message->payload.dump.sz = dump_sz; + if (dump_sz > 0) + { + message->payload.dump.rssis = new int16_t[dump_sz]; + message->payload.dump.freqs_khz = new uint32_t[dump_sz]; + message->payload.dump.freqs_khz[0] = s; + message->payload.dump.freqs_khz[dump_sz - 1] = e; + + for (int i = 1; i < dump_sz - 1; i++) + { + uint32_t incr = (e - s) / (dump_sz - i); + s += incr; + message->payload.dump.freqs_khz[i] = s; + } + + for (int i = 0, k = 0; i < rem; i++) + { + int j = (i + 1) * dump_sz / rem; + int16_t rssi = 0; + p = _read(packet, len, p, (uint8_t *)&rssi); + rssi -= 255; + for (; k < j; k++) + { + message->payload.dump.rssis[k] = rssi; + } + } + } + } + else + { + Serial.printf("Received message type %" PRIu8 ", length %d, but expecting %" PRIu8 + " - ignoring\n", + b, len, (uint8_t)MessageType::SCAN_RESULT); + } + + if (packet != msg) + { + delete[] packet; + } + + return message; +} diff --git a/lib/config/config.cpp b/lib/config/config.cpp index ab766fe..0193900 100644 --- a/lib/config/config.cpp +++ b/lib/config/config.cpp @@ -88,6 +88,18 @@ Config Config::init() continue; } + if (r.key.equalsIgnoreCase("rx_lora")) + { + c.rx_lora = configureLora(r.value); + continue; + } + + if (r.key.equalsIgnoreCase("tx_lora")) + { + c.tx_lora = configureLora(r.value); + continue; + } + Serial.printf("Unknown key '%s' will be ignored\n", r.key); } @@ -143,9 +155,52 @@ bool Config::updateConfig(String key, String value) return true; } + if (key.equalsIgnoreCase("rx_lora")) + { + rx_lora = configureLora(value); + return true; + } + + if (key.equalsIgnoreCase("tx_lora")) + { + tx_lora = configureLora(value); + return true; + } + + if (key.equalsIgnoreCase("is_host")) + { + String v = value; + bool p = v.equalsIgnoreCase("true"); + if (!p && !v.equalsIgnoreCase("false")) + { + Serial.printf("Expected bool for '%s', found '%s' - ignoring\n", key.c_str(), + value.c_str()); + } + else + { + is_host = p; + } + return true; + } + return false; } +String loraConfigToStr(LoRaConfig *cfg) +{ + if (cfg == NULL) + { + return String("none"); + } + + return String("freq:") + String(cfg->freq) + String(",bw:") + String(cfg->bw) + + String(",sf:") + String(cfg->sf) + String(",cr:") + String(cfg->cr) + + String(",tx_power:") + String(cfg->tx_power) + String(",preamble_len:") + + String(cfg->preamble_len) + String(",sync_word:") + + String(cfg->sync_word, 16) + String(",crc:") + String(cfg->crc ? "1" : "0") + + String(",implicit_header:") + String(cfg->implicit_header); +} + String detectionStrategyToStr(Config &c) { String res = c.detection_strategy; @@ -195,6 +250,31 @@ int findSepa(String s, String sepa, int begin, int &end) return i + sepa.length(); } +uint64_t fromHex(String s) +{ + uint64_t r = 0; + for (char c : s) + { + uint64_t v; + if (c >= 'A' && c <= 'F') + { + v = c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + v = c - 'a' + 10; + } + else + { + v = c - '0'; + } + + r = (r << 4) + v; + } + + return r; +} + uint64_t toUint64(String s) { String v = s.substring(0, s.length() % 15); @@ -269,6 +349,94 @@ ScanRange parseScanRange(String &cfg, int &begin) return res; } +LoRaConfig *configureLora(String cfg) +{ + if (cfg.equalsIgnoreCase("none")) + { + return NULL; + } + + LoRaConfig *lora = new LoRaConfig(); + + int begin = 0; + int end, i; + + while ((i = findSepa(cfg, ",", begin, end)) >= 0) + { + String param = cfg.substring(begin, end); + begin = i; + int j = param.indexOf(":"); + if (j < 0) + { + Serial.printf("Expected ':' to be present in '%s' - ignoring config\n", + param); + continue; + } + + String k = param.substring(0, j); + + if (k.equalsIgnoreCase("sync_word")) + { + lora->sync_word = (uint8_t)fromHex(param.substring(j + 1)); + continue; + } + + int v = param.substring(j + 1).toInt(); + + if (k.equalsIgnoreCase("freq")) + { + lora->freq = (uint16_t)v; + continue; + } + + if (k.equalsIgnoreCase("bw")) + { + lora->bw = (uint16_t)v; + continue; + } + + if (k.equalsIgnoreCase("sf")) + { + lora->sf = (uint8_t)v; + continue; + } + + if (k.equalsIgnoreCase("cr")) + { + lora->cr = (uint8_t)v; + continue; + } + + if (k.equalsIgnoreCase("tx_power")) + { + lora->tx_power = (uint8_t)v; + continue; + } + + if (k.equalsIgnoreCase("preamble_len")) + { + lora->preamble_len = (uint8_t)v; + continue; + } + + if (k.equalsIgnoreCase("crc")) + { + lora->crc = v != 0; + continue; + } + + if (k.equalsIgnoreCase("implicit_header")) + { + lora->implicit_header = (uint8_t)v; + continue; + } + + Serial.printf("Unknown key '%s' will be ignored\n", k); + } + + return lora; +} + void Config::configureDetectionStrategy(String cfg) { if (scan_ranges_sz > 0) @@ -329,6 +497,10 @@ bool Config::write_config(const char *path) f.println("detection_strategy = " + getConfig("detection_strategy")); + f.println("rx_lora = " + getConfig("rx_lora")); + f.println("tx_lora = " + getConfig("tx_lora")); + f.println("is_host = " + getConfig("is_host")); + f.close(); return true; } @@ -365,6 +537,21 @@ String Config::getConfig(String key) return detectionStrategyToStr(*this); } + if (key.equalsIgnoreCase("rx_lora")) + { + return loraConfigToStr(rx_lora); + } + + if (key.equalsIgnoreCase("tx_lora")) + { + return loraConfigToStr(tx_lora); + } + + if (key.equalsIgnoreCase("is_host")) + { + return String(is_host ? "true" : "false"); + } + return ""; } diff --git a/lib/config/config.h b/lib/config/config.h index 59bc2e4..b4f32f6 100644 --- a/lib/config/config.h +++ b/lib/config/config.h @@ -10,6 +10,21 @@ struct ScanRange uint64_t step_khz; }; +struct LoRaConfig +{ + uint16_t freq; + uint16_t bw; + uint8_t sf; + uint8_t cr; + uint8_t tx_power; + uint16_t preamble_len; + uint8_t sync_word; + bool crc; + uint8_t implicit_header; +}; + +LoRaConfig *configureLora(String cfg); + #define CREATE_MISSING_CONFIG true struct Config { @@ -23,12 +38,19 @@ struct Config String listen_on_serial0; String listen_on_serial1; String listen_on_usb; + LoRaConfig *rx_lora; + LoRaConfig *tx_lora; + + bool is_host; Config() : create_missing_config(CREATE_MISSING_CONFIG), print_profile_time(false), - detection_strategy("RSSI"), samples(0), scan_ranges_sz(0), scan_ranges(NULL), - log_data_json_interval(1000), listen_on_serial0(String("none")), - listen_on_serial1(String("readline")), listen_on_usb("readline") {}; + detection_strategy(String("RSSI")), samples(0), scan_ranges_sz(0), + scan_ranges(NULL), log_data_json_interval(1000), + listen_on_serial0(String("none")), listen_on_serial1(String("readline")), + listen_on_usb(String("readline")), rx_lora(NULL), tx_lora(NULL), + is_host(false) {}; + bool write_config(const char *path); static Config init(); diff --git a/src/main.cpp b/src/main.cpp index 25dc541..8650b37 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,6 +86,8 @@ long noDevicesMillis = 0, cycleCnt = 0; bool present = false; bool scanFinished = true; +bool radioIsScan = false; + // time to scan BT #define BT_SCAN_TIME 10 @@ -533,19 +535,71 @@ StackedChart stacked(display, 0, 0, 0, 0); UptimeClock *uptime; +int16_t initForScan(float freq) +{ + int16_t state; + +#if defined(USING_SX1280PA) + state = radio.beginGFSK(freq); +#elif defined(USING_LR1121) + state = radio.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F); +#else + state = radio.beginFSK(freq); +#endif + + return state; +} + +bool setFrequency(float curr_freq) +{ + r.current_frequency = curr_freq; + LOG("setFrequency:%f\n", r.current_frequency); + + int16_t state; +#ifdef USING_SX1280PA + int16_t state1 = + radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration + + state = radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF); + if (state != RADIOLIB_ERR_NONE) + { + Serial.println("Error:startReceive:" + String(state)); + } + + state = state1; +#elif USING_SX1276 + state = radio.setFrequency(freq); +#elif defined(USING_LR1121) + state = radio.setFrequency(r.current_frequency, + true); // true = no calibration need here +#else + state = radio.setFrequency(r.current_frequency, + false); // false = calibration is needed here +#endif + if (state != RADIOLIB_ERR_NONE) + { + display.drawString(0, 64 - 10, + "E(" + String(state) + + "):setFrequency:" + String(r.current_frequency)); + Serial.println("E(" + String(state) + + "):setFrequency:" + String(r.current_frequency)); + display.display(); + delay(2); + return false; + } + + return true; +} + void init_radio() { // initialize SX1262 FSK modem at the initial frequency both.println("Init radio"); -#if defined(USING_SX1280PA) - state = radio.beginGFSK(CONF_FREQ_BEGIN); -#elif defined(USING_LR1121) - state = radio.beginGFSK(CONF_FREQ_BEGIN, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F); -#else - state = radio.beginFSK(CONF_FREQ_BEGIN); -#endif + state = initForScan(CONF_FREQ_BEGIN); + if (state == RADIOLIB_ERR_NONE) { + radioIsScan = true; Serial.println(F("success!")); } else @@ -591,25 +645,9 @@ void init_radio() } both.println("Starting scanning..."); -// calibrate only once ,,, at startup -// TODO: check documentation (9.2.1) if we must calibrate in certain ranges -#ifdef USING_SX1280PA - state = radio.setFrequency(CONF_FREQ_BEGIN); - if (state != RADIOLIB_ERR_NONE) - { - Serial.println("Error:setFrequency:" + String(state)); - } - state = radio.startReceive(); - if (state != RADIOLIB_ERR_NONE) - { - Serial.println("Error:startReceive:" + String(state)); - } -#elif USING_SX1276 - // Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz. - radio.setFrequency(CONF_FREQ_BEGIN); -#else - radio.setFrequency(CONF_FREQ_BEGIN, true); -#endif + // calibrate only once ,,, at startup + // TODO: check documentation (9.2.1) if we must calibrate in certain ranges + setFrequency(CONF_FREQ_BEGIN); delay(50); } @@ -1453,6 +1491,12 @@ void checkComms() // MAX Frequency RSSI BIN value of the samples int max_rssi_x = 999; +void doScan(); + +void reportScan(RadioComms &c); + +int16_t checkRadio(RadioComms &c); + void loop(void) { r.led_flag = false; @@ -1462,6 +1506,38 @@ void loop(void) checkComms(); + if (config.is_host) + { + if (TxComms != NULL) + { + // NB: swapping the use of Tx and Rx comms, so a pair of modules + // with identical rx/tx_lora config can talk + int16_t status = checkRadio(*TxComms); + if (status != RADIOLIB_ERR_NONE) + { + Serial.printf("Error getting a message: %d\n", status); + } + } + } + else + { + doScan(); + if (TxComms != NULL) + reportScan(*TxComms); + if (RxComms != NULL) + checkRadio(*RxComms); + } +} + +void doScan() +{ + if (!radioIsScan) + { + radioIsScan = true; + initForScan(CONF_FREQ_BEGIN); + state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE); + } + // reset scan time if (config.print_profile_time) { @@ -1562,36 +1638,7 @@ void loop(void) // x > STEPS on SCAN_RBW_FACTOR int display_x = x / SCAN_RBW_FACTOR; - r.current_frequency = (float)curr_freq / 1000.0; - LOG("setFrequency:%f\n", r.current_frequency); - -#ifdef USING_SX1280PA - state = - radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration - radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF); -#elif USING_SX1276 - state = radio.setFrequency(freq); -#elif defined(USING_LR1121) - state = radio.setFrequency(r.current_frequency, - true); // true = no calibration need here -#else - state = radio.setFrequency(r.current_frequency, - false); // false = calibration is needed here -#endif - int radio_error_count = 0; - if (state != RADIOLIB_ERR_NONE) - { - display.drawString(0, 64 - 10, - "E(" + String(state) + - "):setFrequency:" + String(r.current_frequency)); - Serial.println("E(" + String(state) + - "):setFrequency:" + String(r.current_frequency)); - display.display(); - delay(2); - radio_error_count++; - if (radio_error_count > 10) - continue; - } + setFrequency(curr_freq / 1000.0); LOG("Step:%d Freq: %f\n", x, r.current_frequency); // SpectralScan Method @@ -1914,3 +1961,57 @@ void loop(void) sideBarCol = SIDEBAR_START_COL; #endif } + +int16_t checkRadio(RadioComms &comms) +{ + radioIsScan = false; + int16_t status = comms.configureRadio(); + if (status != RADIOLIB_ERR_NONE) + return status; + + Message *msg = comms.receive( + config.is_host + ? 2000 + : 200); // 200ms should be enough to receive 500 bytes at SF 7 and BW 500 + if (msg == NULL) + { + return status; + } + + if (msg->type == SCAN_RESULT) + { + HostComms->send(*msg); + } + else + { + Serial.printf("Received a message of unsupported type: %d\n", msg->type); + } + + delete msg; + + return status; +} + +void reportScan(RadioComms &comms) +{ + radioIsScan = false; + int16_t status = comms.configureRadio(); + if (status != RADIOLIB_ERR_NONE) + { + Serial.printf("Failed to configure Radio: %d\n", status); + return; + } + + Message m; + m.type = SCAN_RESULT; + m.payload.dump = frequency_scan_result.dump; + status = comms.send(m); + + m.payload.dump.sz = 0; // dump is shared, so should not delete underlying arrays + + if (status != RADIOLIB_ERR_NONE) + { + Serial.printf("Failed to send scan result: %d\n", status); + return; + } +}