Merge branch 'dev' into trace

# Conflicts:
#	examples/companion_radio/main.cpp
#	src/Dispatcher.cpp
This commit is contained in:
Scott Powell 2025-03-15 15:12:50 +11:00
commit 9aa2edf9ba
37 changed files with 1558 additions and 390 deletions

View file

@ -78,9 +78,11 @@ void Dispatcher::checkRecv() {
float score;
uint32_t air_time;
{
uint8_t raw[MAX_TRANS_UNIT];
uint8_t raw[MAX_TRANS_UNIT+1];
int len = _radio->recvRaw(raw, MAX_TRANS_UNIT);
if (len > 0) {
logRxRaw(_radio->getLastSNR(), _radio->getLastRSSI(), raw, len);
pkt = _mgr->allocNew();
if (pkt == NULL) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime());
@ -106,10 +108,17 @@ void Dispatcher::checkRecv() {
memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len;
pkt->payload_len = len - i; // payload is remainder
memcpy(pkt->payload, &raw[i], pkt->payload_len);
if (pkt->payload_len > sizeof(pkt->payload)) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len);
_mgr->free(pkt); // put back into pool
pkt = NULL;
} else {
memcpy(pkt->payload, &raw[i], pkt->payload_len);
score = _radio->packetScore(pkt->_snr = (_radio->getLastSNR() * 4.0f), len);
air_time = _radio->getEstAirtimeFor(len);
pkt->_snr = _radio->getLastSNR() * 4.0f;
score = _radio->packetScore(_radio->getLastSNR(), len);
air_time = _radio->getEstAirtimeFor(len);
}
}
}
} else {
@ -122,6 +131,12 @@ void Dispatcher::checkRecv() {
Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d",
2 + pkt->path_len + pkt->payload_len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
(int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000));
static uint8_t packet_hash[MAX_HASH_SIZE];
pkt->calculatePacketHash(packet_hash);
Serial.print(" hash=");
mesh::Utils::printHex(Serial, packet_hash, MAX_HASH_SIZE);
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ
|| pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) {
Serial.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]);

View file

@ -117,6 +117,8 @@ protected:
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
virtual void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { } // custom hook
virtual void logRx(Packet* packet, int len, float score) { } // hooks for custom logging
virtual void logTx(Packet* packet, int len) { }
virtual void logTxFail(Packet* packet, int len) { }

View file

@ -40,6 +40,7 @@ public:
virtual void onBeforeTransmit() { }
virtual void onAfterTransmit() { }
virtual void reboot() = 0;
virtual void powerOff() { /* no op */ }
virtual uint8_t getStartupReason() const = 0;
virtual bool startOTAUpdate() { return false; } // not supported
};

View file

@ -23,7 +23,12 @@ public:
#ifdef LILYGO_T3S3
void begin(HWCDC& serial) { _serial = &serial; }
#elif defined(NRF52_PLATFORM)
void begin(Adafruit_USBD_CDC& serial) { _serial = &serial; }
void begin(Adafruit_USBD_CDC& serial) {
_serial = &serial;
#ifdef RAK_4631
pinMode(WB_IO2, OUTPUT);
#endif
}
#else
void begin(HardwareSerial& serial) { _serial = &serial; }
#endif

View file

@ -247,9 +247,9 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
#ifdef MAX_GROUP_CHANNELS
int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) {
int n = 0;
for (int i = 0; i < num_channels && n < max_matches; i++) {
if (channels[i].hash[0] == hash[0]) {
dest[n++] = channels[i];
for (int i = 0; i < MAX_GROUP_CHANNELS && n < max_matches; i++) {
if (channels[i].channel.hash[0] == hash[0]) {
dest[n++] = channels[i].channel;
}
}
return n;
@ -651,24 +651,61 @@ bool BaseChatMesh::removeContact(ContactInfo& contact) {
#ifdef MAX_GROUP_CHANNELS
#include <base64.hpp>
mesh::GroupChannel* BaseChatMesh::addChannel(const char* psk_base64) {
ChannelDetails* BaseChatMesh::addChannel(const char* name, const char* psk_base64) {
if (num_channels < MAX_GROUP_CHANNELS) {
auto dest = &channels[num_channels];
memset(dest->secret, 0, sizeof(dest->secret));
int len = decode_base64((unsigned char *) psk_base64, strlen(psk_base64), dest->secret);
memset(dest->channel.secret, 0, sizeof(dest->channel.secret));
int len = decode_base64((unsigned char *) psk_base64, strlen(psk_base64), dest->channel.secret);
if (len == 32 || len == 16) {
mesh::Utils::sha256(dest->hash, sizeof(dest->hash), dest->secret, len);
mesh::Utils::sha256(dest->channel.hash, sizeof(dest->channel.hash), dest->channel.secret, len);
StrHelper::strncpy(dest->name, name, sizeof(dest->name));
num_channels++;
return dest;
}
}
return NULL;
}
bool BaseChatMesh::getChannel(int idx, ChannelDetails& dest) {
if (idx >= 0 && idx < MAX_GROUP_CHANNELS) {
dest = channels[idx];
return true;
}
return false;
}
bool BaseChatMesh::setChannel(int idx, const ChannelDetails& src) {
static uint8_t zeroes[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
if (idx >= 0 && idx < MAX_GROUP_CHANNELS) {
channels[idx] = src;
if (memcmp(&src.channel.secret[16], zeroes, 16) == 0) {
mesh::Utils::sha256(channels[idx].channel.hash, sizeof(channels[idx].channel.hash), src.channel.secret, 16); // 128-bit key
} else {
mesh::Utils::sha256(channels[idx].channel.hash, sizeof(channels[idx].channel.hash), src.channel.secret, 32); // 256-bit key
}
return true;
}
return false;
}
int BaseChatMesh::findChannelIdx(const mesh::GroupChannel& ch) {
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
if (memcmp(ch.secret, channels[i].channel.secret, sizeof(ch.secret)) == 0) return i;
}
return -1; // not found
}
#else
mesh::GroupChannel* BaseChatMesh::addChannel(const char* psk_base64) {
ChannelDetails* BaseChatMesh::addChannel(const char* name, const char* psk_base64) {
return NULL; // not supported
}
bool BaseChatMesh::getChannel(int idx, ChannelDetails& dest) {
return false;
}
bool BaseChatMesh::setChannel(int idx, const ChannelDetails& src) {
return false;
}
int BaseChatMesh::findChannelIdx(const mesh::GroupChannel& ch) {
return -1; // not found
}
#endif
ContactsIterator BaseChatMesh::startContactsIterator() {

View file

@ -61,6 +61,11 @@ struct ConnectionInfo {
uint32_t expected_ack;
};
struct ChannelDetails {
mesh::GroupChannel channel;
char name[32];
};
/**
* \brief abstract Mesh class for common 'chat' client
*/
@ -74,8 +79,8 @@ class BaseChatMesh : public mesh::Mesh {
int matching_peer_indexes[MAX_SEARCH_RESULTS];
unsigned long txt_send_timeout;
#ifdef MAX_GROUP_CHANNELS
mesh::GroupChannel channels[MAX_GROUP_CHANNELS];
int num_channels;
ChannelDetails channels[MAX_GROUP_CHANNELS];
int num_channels; // only for addChannel()
#endif
mesh::Packet* _pendingLoopback;
uint8_t temp_buf[MAX_TRANS_UNIT];
@ -89,6 +94,7 @@ protected:
{
num_contacts = 0;
#ifdef MAX_GROUP_CHANNELS
memset(channels, 0, sizeof(channels));
num_channels = 0;
#endif
txt_send_timeout = 0;
@ -154,7 +160,10 @@ public:
bool addContact(const ContactInfo& contact);
int getNumContacts() const { return num_contacts; }
ContactsIterator startContactsIterator();
mesh::GroupChannel* addChannel(const char* psk_base64);
ChannelDetails* addChannel(const char* name, const char* psk_base64);
bool getChannel(int idx, ChannelDetails& dest);
bool setChannel(int idx, const ChannelDetails& src);
int findChannelIdx(const mesh::GroupChannel& ch);
void loop();
};

View file

@ -41,6 +41,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
file.read((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115
file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
file.read(pad, 4); // 120
file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@ -91,6 +92,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115
file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
file.write(pad, 4); // 120
file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
file.close();
}
@ -176,6 +178,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
} else if (memcmp(config, "txdelay", 7) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
} else if (memcmp(config, "flood.max", 9) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
} else if (memcmp(config, "direct.txdelay", 14) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
@ -262,6 +266,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else {
strcpy(reply, "Error, cannot be negative");
}
} else if (memcmp(config, "flood.max ", 10) == 0) {
uint8_t m = atoi(&config[10]);
if (m <= 64) {
_prefs->flood_max = m;
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error, max 64");
}
} else if (memcmp(config, "direct.txdelay ", 15) == 0) {
float f = atof(&config[15]);
if (f >= 0) {

View file

@ -23,6 +23,7 @@ struct NodePrefs { // persisted to file
uint8_t reserved1;
uint8_t reserved2;
float bw;
uint8_t flood_max;
};
class CommonCLICallbacks {

View file

@ -9,7 +9,7 @@ class CustomSX1262 : public SX1262 {
CustomSX1262(Module *mod) : SX1262(mod) { }
bool isReceiving() {
uint16_t irq = getIrqStatus();
uint16_t irq = getIrqFlags();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
return hasPreamble;
}

View file

@ -9,7 +9,7 @@ class CustomSX1268 : public SX1268 {
CustomSX1268(Module *mod) : SX1268(mod) { }
bool isReceiving() {
uint16_t irq = getIrqStatus();
uint16_t irq = getIrqFlags();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
return hasPreamble;
}

View file

@ -17,6 +17,10 @@ public:
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
#ifdef ESP32_CPU_FREQ
setCpuFrequencyMhz(ESP32_CPU_FREQ);
#endif
#ifdef PIN_VBAT_READ
// battery read support
pinMode(PIN_VBAT_READ, INPUT);

View file

@ -24,12 +24,22 @@
#include <driver/rtc_io.h>
class HeltecV3Board : public ESP32Board {
private:
bool adc_active_state;
public:
void begin() {
ESP32Board::begin();
// Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2)
pinMode(PIN_ADC_CTRL, INPUT);
adc_active_state = !digitalRead(PIN_ADC_CTRL);
pinMode(PIN_ADC_CTRL, OUTPUT);
//pinMode(PIN_VEXT_EN, OUTPUT);
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
pinMode(PIN_VEXT_EN, OUTPUT);
digitalWrite(PIN_VEXT_EN, LOW); // for V3.2 boards
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
@ -66,9 +76,14 @@ public:
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void powerOff() override {
// TODO: re-enable this when there is a definite wake-up source pin:
// enterDeepSleep(0);
}
uint16_t getBattMilliVolts() override {
analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_ACTIVE);
digitalWrite(PIN_ADC_CTRL, adc_active_state);
uint32_t raw = 0;
for (int i = 0; i < 8; i++) {
@ -76,7 +91,7 @@ public:
}
raw = raw / 8;
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_INACTIVE);
digitalWrite(PIN_ADC_CTRL, !adc_active_state);
return (5.2 * (3.3 / 1024.0) * raw) * 1000;
}

View file

@ -52,10 +52,11 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
int err = _radio->readData(bytes, len);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData(%d)", err);
len = 0;
} else {
// Serial.print(" readData() -> "); Serial.println(len);
n_recv++;
}
n_recv++;
}
state = STATE_IDLE; // need another startReceive()
return len;

View file

@ -0,0 +1,69 @@
#pragma once
#include <Arduino.h>
// LoRa radio module pins for Station G2
#define P_LORA_DIO_1 48
#define P_LORA_NSS 11
#define P_LORA_RESET 21
#define P_LORA_BUSY 47
#define P_LORA_SCLK 12
#define P_LORA_MISO 14
#define P_LORA_MOSI 13
// built-ins
//#define PIN_LED_BUILTIN 35
//#define PIN_VEXT_EN 36
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class StationG2Board : public ESP32Board {
public:
void begin() {
ESP32Board::begin();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
uint16_t getBattMilliVolts() override {
return 0;
}
const char* getManufacturerName() const override {
return "Station G2";
}
};

View file

@ -3,8 +3,6 @@
#include <MeshCore.h>
#include <Arduino.h>
#define HAS_T1000e_POWEROFF
// LoRa and SPI pins
#define P_LORA_DIO_1 (32 + 1) // P1.1
#define P_LORA_NSS (0 + 12) // P0.12
@ -31,8 +29,13 @@ public:
uint16_t getBattMilliVolts() override {
#ifdef BATTERY_PIN
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12);
float volts = (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096;
analogReference(AR_DEFAULT); // put back to default
analogReadResolution(10);
return volts * 1000;
#else
return 0;
@ -56,7 +59,7 @@ public:
return 0;
}
void powerOff() {
void powerOff() override {
#ifdef HAS_GPS
digitalWrite(GPS_VRTC_EN, LOW);
digitalWrite(GPS_RESET, LOW);

View file

@ -32,6 +32,11 @@ void T114Board::begin() {
Wire.begin();
#endif
#ifdef P_LORA_TX_LED
pinMode(P_LORA_TX_LED, OUTPUT);
digitalWrite(P_LORA_TX_LED, HIGH);
#endif
pinMode(SX126X_POWER_EN, OUTPUT);
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up

View file

@ -28,6 +28,15 @@ public:
void begin();
uint8_t getStartupReason() const override { return startup_reason; }
#if defined(P_LORA_TX_LED)
void onBeforeTransmit() override {
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
}
void onAfterTransmit() override {
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
}
#endif
#define BATTERY_SAMPLES 8
uint16_t getBattMilliVolts() override {

View file

@ -0,0 +1,63 @@
#include <Arduino.h>
#include "faketecBoard.h"
#include <bluefruit.h>
#include <Wire.h>
static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle)
{
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
bool faketecBoard::startOTAUpdate() {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName("Faketec_OTA");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Set up and start advertising
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
return true;
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
#include <Wire.h>
#define P_LORA_NSS 13 //P1.13 45
#define P_LORA_DIO_1 11 //P0.10 10
#define P_LORA_RESET 10 //P0.09 9
#define P_LORA_BUSY 16 //P0.29 29
#define P_LORA_MISO 15 //P0.02 2
#define P_LORA_SCLK 12 //P1.11 43
#define P_LORA_MOSI 14 //P1.15 47
#define SX126X_POWER_EN 21 //P0.13 13
#define SX126X_RXEN 2 //P0.17
#define SX126X_TXEN RADIOLIB_NC
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE (1.8f)
#define PIN_VBAT_READ 17
#define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
class faketecBoard : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
pinMode(PIN_VBAT_READ, INPUT);
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL);
#else
Wire.begin();
#endif
pinMode(SX126X_POWER_EN, OUTPUT);
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
uint8_t getStartupReason() const override { return startup_reason; }
#define BATTERY_SAMPLES 8
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
uint32_t raw = 0;
for (int i = 0; i < BATTERY_SAMPLES; i++) {
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw);
}
const char* getManufacturerName() const override {
return "Faketec DIY";
}
void reboot() override {
NVIC_SystemReset();
}
bool startOTAUpdate() override;
};

View file

@ -1,7 +1,7 @@
#include "SSD1306Display.h"
bool SSD1306Display::begin() {
return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS);
return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false);
}
void SSD1306Display::turnOn() {