Merge upstream dev into keymindCascade

This commit is contained in:
mikecarper committed 2026-09-24 00:51:07 -07:00
commit c31a0e5ed5
40 files changed
+2405 -200

No files matched your search

+3
View File
@@ -251,6 +251,9 @@ public:
return false;
}
// Let a board feed its watchdog and perform periodic housekeeping.
virtual void loop() {}
inline static uint32_t n_cad_busy = 0;
};
+8
View File
@@ -40,6 +40,14 @@ struct ClientInfo {
unsigned long ack_timeout;
uint8_t push_failures;
} room;
struct {
uint32_t expiry_timestamp; // epoch seconds
uint32_t push_tag;
uint16_t scope_region_id; // scope to use when sending telemetry to this client/subscriber
uint8_t min_deltas_len;
uint8_t min_deltas[14]; // LPP encoded
uint8_t prev_telem[14]; // LPP encoded
} sensor;
} extra;
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
+2 -2
View File
@@ -300,8 +300,8 @@ private:
void setTxPower(int8_t dbm) override { _parent->tx_power_dbm = dbm; markDirty(); }
float getRxDelay() const override { return _parent->rx_delay_base; }
void setRxDelay(float d) override { _parent->rx_delay_base = d; markDirty(); }
uint8_t getAgcResetInt() const override { return _parent->agc_reset_interval * 4; }
void setAgcResetInt(uint8_t secs) override { _parent->agc_reset_interval = secs / 4; markDirty(); }
uint16_t getAgcResetInt() const override { return _parent->agc_reset_interval * 4; }
void setAgcResetInt(uint16_t secs) override { _parent->agc_reset_interval = secs / 4; markDirty(); }
uint8_t getHashMode() const override { return _parent->path_hash_mode; }
void setHashMode(uint8_t m) override { _parent->path_hash_mode = m; markDirty(); }
uint8_t getMultiAcks() const override { return _parent->multi_acks; }
+2 -2
View File
@@ -41,8 +41,8 @@ public:
virtual float getRxDelay() const = 0;
virtual void setRxDelay(float d) = 0;
virtual uint8_t getAgcResetInt() const = 0;
virtual void setAgcResetInt(uint8_t secs) = 0;
virtual uint16_t getAgcResetInt() const = 0;
virtual void setAgcResetInt(uint16_t secs) = 0;
virtual uint8_t getHashMode() const = 0;
virtual void setHashMode(uint8_t m) = 0;
+7 -2
View File
@@ -43,8 +43,13 @@ bool StrHelper::isBlank(const char* str) {
}
#include <Arduino.h>
#include <stdio.h>
#if __has_include(<api/itoa.h>)
// https://github.com/stm32duino/Arduino_Core_STM32/issues/3084
#include <api/itoa.h>
#endif
union int32_Float_t
union int32_Float_t
{
int32_t Long;
float Float;
@@ -116,7 +121,7 @@ static void _ftoa(float f, char *p, int *status)
*p++ = '-';
if (int_part == 0)
*p++ = '0';
else
else
{
#if defined(STM32_PLATFORM)
// STM32 Arduino exposes utoa, but recent cores no longer declare ltoa.
+105
View File
@@ -0,0 +1,105 @@
#pragma once
#include <RadioLib.h>
#include "MeshCore.h"
class CustomLR1121 : public LR1121 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false;
public:
CustomLR1121(Module *mod) : LR1121(mod) { }
int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7,
uint8_t syncWord = RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, int8_t power = 10,
uint16_t preambleLength = 8, float tcxoVoltage = 1.6) {
int16_t state = LR1121::begin(freq, bw, sf, cr, syncWord, power, preambleLength,
tcxoVoltage);
// RadioLib begin() defaults to LDO; use the LR1121 DC/DC regulator.
if (state == RADIOLIB_ERR_NONE) state = setRegulatorDCDC();
return state;
}
size_t getPacketLength(bool update) override {
size_t len = LR1121::getPacketLength(update);
if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) {
// we've just received a corrupted packet
// this may have triggered a bug causing subsequent packets to be shifted
// call standby() to return radio to known-good state
// recvRaw will call startReceive() to restart rx
MESH_DEBUG_PRINTLN("LR1121: got header err, calling standby()");
standby();
}
return len;
}
float getFreqMHz() const { return freqMHz; }
int16_t setRxBoostedGainMode(bool en) {
_rx_boosted = en;
return LR1121::setRxBoostedGainMode(en);
}
bool getRxBoostedGainMode() const { return _rx_boosted; }
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags.
return LR1121::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint32_t irq = getIrqStatus();
bool preamble = irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; // bit 4
bool header = irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; // bit 5
bool hdrErr = irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR; // bit 6
uint32_t now = millis();
if (hdrErr) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
};
@@ -0,0 +1,53 @@
#pragma once
#include "CustomLR1121.h"
#include "RadioLibWrappers.h"
#include "LR11x0Reset.h"
class CustomLR1121Wrapper : public RadioLibWrapper {
public:
CustomLR1121Wrapper(CustomLR1121& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
((CustomLR1121 *)_radio)->setFrequency(freq);
((CustomLR1121 *)_radio)->setSpreadingFactor(sf);
((CustomLR1121 *)_radio)->setBandwidth(bw);
((CustomLR1121 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLR1121 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLR1121 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1121 *)_radio)->getFreqMHz(), getRxBoostedGainMode()); }
bool isReceivingPacket() override {
return ((CustomLR1121 *)_radio)->isReceiving();
}
float getCurrentRSSI() override {
float rssi = -110;
((CustomLR1121 *)_radio)->getRssiInst(&rssi);
return rssi;
}
void onSendFinished() override {
RadioLibWrapper::onSendFinished();
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
}
uint32_t getEstAirtimeFor(int len_bytes) override {
auto airtime = RadioLibWrapper::getEstAirtimeFor(len_bytes);
return airtime < 200 ? 200 : airtime; // at least 200 millis
}
float getLastRSSI() const override { return ((CustomLR1121 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR1121 *)_radio)->getSNR(); }
uint8_t getSpreadingFactor() const override { return ((CustomLR1121 *)_radio)->getSpreadingFactor(); }
bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1121 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLR1121 *)_radio)->getRxBoostedGainMode();
}
};
+113 -46
View File
@@ -63,12 +63,66 @@
#define LPP_ERROR_OVERFLOW 1
#define LPP_ERROR_UNKOWN_TYPE 2
class LPPReader {
const uint8_t* _buf;
uint8_t _len;
uint8_t _pos;
class LPPData {
public:
static uint8_t getDataSize(uint8_t type) {
switch (type) {
case LPP_GPS:
return 9;
case LPP_POLYLINE:
return 8; // TODO: this is MINIMIUM
case LPP_GYROMETER:
case LPP_ACCELEROMETER:
return 6;
case LPP_GENERIC_SENSOR:
case LPP_FREQUENCY:
case LPP_DISTANCE:
case LPP_ENERGY:
case LPP_UNIXTIME:
return 4;
case LPP_COLOUR:
return 3;
case LPP_ANALOG_INPUT:
case LPP_ANALOG_OUTPUT:
case LPP_LUMINOSITY:
case LPP_TEMPERATURE:
case LPP_CONCENTRATION:
case LPP_BAROMETRIC_PRESSURE:
case LPP_RELATIVE_HUMIDITY:
case LPP_ALTITUDE:
case LPP_VOLTAGE:
case LPP_CURRENT:
case LPP_DIRECTION:
case LPP_POWER:
return 2;
}
return 1;
}
float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) {
static uint32_t getMultiplier(uint8_t type) {
switch (type) {
case LPP_CURRENT:
case LPP_DISTANCE:
case LPP_ENERGY:
return 1000;
case LPP_VOLTAGE:
case LPP_ANALOG_INPUT:
case LPP_ANALOG_OUTPUT:
return 100;
case LPP_TEMPERATURE:
case LPP_BAROMETRIC_PRESSURE:
case LPP_RELATIVE_HUMIDITY:
return 10;
}
return 1;
}
static bool isSigned(uint8_t type) {
return type == LPP_ALTITUDE || type == LPP_TEMPERATURE || type == LPP_GYROMETER ||
type == LPP_ANALOG_INPUT || type == LPP_ANALOG_OUTPUT || type == LPP_GPS || type == LPP_ACCELEROMETER;
}
static float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) {
uint32_t value = 0;
for (uint8_t i = 0; i < size; i++) {
value = (value << 8) + buffer[i];
@@ -85,6 +139,36 @@ class LPPReader {
return sign * ((float) value / multiplier);
}
static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t multiplier, bool is_signed) {
// check sign
bool sign = value < 0;
if (sign) value = -value;
// get value to store
uint32_t v = value * multiplier;
// format an uint32_t as if it was an int32_t
if (is_signed & sign) {
uint32_t mask = (1 << (size * 8)) - 1;
v = v & mask;
if (sign) v = mask - v + 1;
}
// add bytes (MSB first)
for (uint8_t i=1; i<=size; i++) {
dest[size - i] = (v & 0xFF);
v >>= 8;
}
return size;
}
};
class LPPReader {
const uint8_t* _buf;
uint8_t _len;
uint8_t _pos;
public:
LPPReader(const uint8_t buf[], uint8_t len) : _buf(buf), _len(len), _pos(0) { }
@@ -103,72 +187,42 @@ public:
}
bool readGPS(float& lat, float& lon, float& alt) {
lat = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3;
lon = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3;
alt = getFloat(&_buf[_pos], 3, 100, true); _pos += 3;
lat = LPPData::getFloat(&_buf[_pos], 3, 10000, true); _pos += 3;
lon = LPPData::getFloat(&_buf[_pos], 3, 10000, true); _pos += 3;
alt = LPPData::getFloat(&_buf[_pos], 3, 100, true); _pos += 3;
return _pos <= _len;
}
bool readVoltage(float& voltage) {
voltage = getFloat(&_buf[_pos], 2, 100, false); _pos += 2;
voltage = LPPData::getFloat(&_buf[_pos], 2, 100, false); _pos += 2;
return _pos <= _len;
}
bool readCurrent(float& amps) {
amps = getFloat(&_buf[_pos], 2, 1000, true); _pos += 2;
amps = LPPData::getFloat(&_buf[_pos], 2, 1000, true); _pos += 2;
return _pos <= _len;
}
bool readPower(float& watts) {
watts = getFloat(&_buf[_pos], 2, 1, false); _pos += 2;
watts = LPPData::getFloat(&_buf[_pos], 2, 1, false); _pos += 2;
return _pos <= _len;
}
bool readTemperature(float& degrees_c) {
degrees_c = getFloat(&_buf[_pos], 2, 10, true); _pos += 2;
degrees_c = LPPData::getFloat(&_buf[_pos], 2, 10, true); _pos += 2;
return _pos <= _len;
}
bool readPressure(float& pa) {
pa = getFloat(&_buf[_pos], 2, 10, false); _pos += 2;
pa = LPPData::getFloat(&_buf[_pos], 2, 10, false); _pos += 2;
return _pos <= _len;
}
bool readRelativeHumidity(float& pct) {
pct = getFloat(&_buf[_pos], 1, 2, false); _pos += 1;
pct = LPPData::getFloat(&_buf[_pos], 1, 2, false); _pos += 1;
return _pos <= _len;
}
bool readAltitude(float& m) {
m = getFloat(&_buf[_pos], 2, 1, true); _pos += 2;
m = LPPData::getFloat(&_buf[_pos], 2, 1, true); _pos += 2;
return _pos <= _len;
}
void skipData(uint8_t type) {
switch (type) {
case LPP_GPS:
_pos += 9; break;
case LPP_POLYLINE:
_pos += 8; break; // TODO: this is MINIMUM
case LPP_GYROMETER:
case LPP_ACCELEROMETER:
_pos += 6; break;
case LPP_GENERIC_SENSOR:
case LPP_FREQUENCY:
case LPP_DISTANCE:
case LPP_ENERGY:
case LPP_UNIXTIME:
_pos += 4; break;
case LPP_COLOUR:
_pos += 3; break;
case LPP_ANALOG_INPUT:
case LPP_ANALOG_OUTPUT:
case LPP_LUMINOSITY:
case LPP_TEMPERATURE:
case LPP_CONCENTRATION:
case LPP_BAROMETRIC_PRESSURE:
case LPP_ALTITUDE:
case LPP_VOLTAGE:
case LPP_CURRENT:
case LPP_DIRECTION:
case LPP_POWER:
_pos += 2; break;
default:
_pos++;
}
_pos += LPPData::getDataSize(type);
}
};
@@ -185,6 +239,19 @@ class LPPWriter {
public:
LPPWriter(uint8_t buf[], uint8_t max_len): _buf(buf), _max_len(max_len), _len(0) { }
bool writeData(uint8_t channel, uint8_t type, float v) {
uint8_t sz = LPPData::getDataSize(type);
bool s = LPPData::isSigned(type);
uint32_t mul = LPPData::getMultiplier(type);
if (_len + 2 + sz <= _max_len) {
_buf[_len++] = channel;
_buf[_len++] = type;
_len += LPPData::putFloat(&_buf[_len], v, sz, mul, s);
return true;
}
return false;
}
bool writeVoltage(uint8_t channel, float voltage) {
if (_len + 4 <= _max_len) {
_buf[_len++] = channel;
+113
View File
@@ -0,0 +1,113 @@
#include "SH1107Display.h"
#include <Adafruit_GrayOLED.h>
#include "Adafruit_SH110X.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 0
#endif
ColorVal UIColor::window_bkg = SH110X_BLACK;
ColorVal UIColor::title_bkg = SH110X_BLACK;
ColorVal UIColor::title_txt = SH110X_WHITE;
ColorVal UIColor::primary_txt = SH110X_WHITE;
ColorVal UIColor::secondary_txt = SH110X_WHITE;
ColorVal UIColor::warning_txt = SH110X_WHITE;
ColorVal UIColor::popup_bkg = SH110X_BLACK;
ColorVal UIColor::popup_txt = SH110X_WHITE;
ColorVal UIColor::corp_blue = SH110X_WHITE;
bool SH1107Display::i2c_probe(TwoWire &wire, uint8_t addr)
{
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
}
bool SH1107Display::begin()
{
bool result = display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS);
if (result) {
display.setRotation(DISPLAY_ROTATION);
}
return result;
}
void SH1107Display::turnOn()
{
display.oled_command(SH110X_DISPLAYON);
uint8_t cmd[] = {0xD5, 0xF0};
display.oled_commandList(cmd, 2);
_isOn = true;
}
void SH1107Display::turnOff()
{
display.oled_command(SH110X_DISPLAYOFF);
_isOn = false;
}
void SH1107Display::clear()
{
display.clearDisplay();
display.display();
}
void SH1107Display::startFrame(ColorVal bkg)
{
display.clearDisplay(); // TODO: apply 'bkg'
display.setContrast(120); // 0-127. default setting was causing some flickering.
// display.SH110X_SETPRECHARGE(255);
_color = SH110X_WHITE;
display.setTextColor(_color);
display.setTextSize(1);
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void SH1107Display::setTextSize(int sz)
{
display.setTextSize(sz);
}
void SH1107Display::setColor(ColorVal c)
{
_color = (c != 0) ? SH110X_WHITE : SH110X_BLACK;
display.setTextColor(_color);
}
void SH1107Display::setCursor(int x, int y)
{
display.setCursor(x, y);
}
void SH1107Display::print(const char *str)
{
display.print(str);
}
void SH1107Display::fillRect(int x, int y, int w, int h)
{
display.fillRect(x, y, w, h, _color);
}
void SH1107Display::drawRect(int x, int y, int w, int h)
{
display.drawRect(x, y, w, h, _color);
}
void SH1107Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
{
display.drawBitmap(x, y, bits, w, h, SH110X_WHITE);
}
uint16_t SH1107Display::getTextWidth(const char *str)
{
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
void SH1107Display::endFrame()
{
display.display();
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#define SH110X_NO_SPLASH
#include <Adafruit_SH110X.h>
#ifndef PIN_OLED_RESET
#define PIN_OLED_RESET -1
#endif
#ifndef DISPLAY_ADDRESS
#define DISPLAY_ADDRESS 0x3c
#endif
class SH1107Display : public DisplayDriver
{
Adafruit_SH1107 display;
bool _isOn;
uint8_t _color;
bool i2c_probe(TwoWire &wire, uint8_t addr);
public:
SH1107Display() : DisplayDriver(128, 128), display(128, 128, &Wire, PIN_OLED_RESET) { _isOn = false; }
bool begin();
bool isOn() override { return _isOn; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char *str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override;
uint16_t getTextWidth(const char *str) override;
void endFrame() override;
};