Compare commits

..

10 Commits

Author SHA1 Message Date
d913d7b010 PMU edits 2026-03-18 02:54:44 +03:00
3f05d46411 Added MeshAdventurer-S3 and DIY-V1 variants 2026-03-10 00:13:12 +03:00
0e74e0cd10 Initial commit 2026-02-26 16:13:54 +03:00
Mark Qvist
180207aa2e Updated readme 2025-12-28 01:07:31 +01:00
Mark Qvist
4564dc3e9e Merge branch 'master' of github.com:markqvist/RNode_Firmware 2025-12-22 22:20:14 +01:00
Mark Qvist
c1efbe727d Updated readme 2025-12-22 22:19:06 +01:00
markqvist
15b8219966 Merge pull request #122 from vehsamrak/fix-readme-heltecv4
Heltec LoRa32 v4 devices added to supported hardware list in README
2025-12-19 14:10:44 +01:00
Petr Karmashev
5ac359ba06 Heltec LoRa32 v4 devices added to supported hardware list in README 2025-12-18 16:46:21 +07:00
Mark Qvist
7f868c6c28 Added interference display to waterfall. Improved Heltec V4 false interference rejection. 2025-11-22 14:19:23 +01:00
Mark Qvist
9ea2a589cb Updated console image 2025-11-22 02:09:26 +01:00
17 changed files with 735 additions and 1243 deletions

223
Boards.h
View File

@@ -122,6 +122,12 @@
#define MODEL_FE 0xFE // Homebrew board, max 17dBm output power
#define MODEL_FF 0xFF // Homebrew board, max 14dBm output power
#define BOARD_MESHADVENTURER_S3 0xF2 // MeshAdventurer-S3
#define BOARD_AETHERNODE 0xF3 // Aethernode
#define BOARD_MESHADVENTURER 0xF4 // MeshAdventurer
#define BOARD_PROMICRO 0xF5 // FakeTec (Promicro)
#define BOARD_DIY_V1 0xF6 // DIY-V1
#if defined(__AVR_ATmega1284P__)
#define PLATFORM PLATFORM_AVR
#define MCU_VARIANT MCU_1284P
@@ -219,14 +225,163 @@
#define PIN_GPS_RX 34
#if BOARD_MODEL == BOARD_GENERIC_ESP32
#define HAS_DISPLAY true
#define HAS_BLUETOOTH true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
const int pin_cs = 4;
const int pin_reset = 33;
const int pin_dio = 39;
const int pin_led_rx = 14;
const int pin_led_tx = 32;
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH true
#define HAS_RF_SWITCH_RX_TX false
const int pin_cs = 5;
const int pin_sclk = 18;
const int pin_miso = 19;
const int pin_mosi = 23;
const int pin_busy = 32;
const int pin_reset = 34;
const int pin_dio = 33;
const int pin_txen = -1;
const int pin_rxen = -1;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 39;
const int pin_led_rx = 2;
const int pin_led_tx = 4;
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define IS_ESP32S3 true
#define HAS_DISPLAY true
#define HAS_NP true
#define HAS_BLUETOOTH false
#define HAS_BLE true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH false
#define HAS_RF_SWITCH_RX_TX true
#define HAS_LORA_LNA true
#define LORA_LNA_GAIN 30
#define LORA_LNA_GVT 14
const int pin_cs = 39;
const int pin_sclk = 38;
const int pin_miso = 40;
const int pin_mosi = 18;
const int pin_busy = 7;
const int pin_reset = 43;
const int pin_dio = 15;
const int pin_txen = 9;
const int pin_rxen = 8;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 4;
const int pin_np = 48;
#if HAS_NP == false
#if defined(EXTERNAL_LEDS)
const int pin_led_rx = 48;
const int pin_led_tx = 48;
#else
const int pin_led_rx = 48;
const int pin_led_tx = 48;
#endif
#endif
#elif BOARD_MODEL == BOARD_MESHADVENTURER
#define HAS_DISPLAY true
#define HAS_BLUETOOTH true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH false
#define HAS_RF_SWITCH_RX_TX true
#define HAS_LORA_LNA true
#define LORA_LNA_GAIN 30
#define LORA_LNA_GVT 14
const int pin_cs = 18;
const int pin_sclk = 5;
const int pin_miso = 19;
const int pin_mosi = 27;
const int pin_busy = 32;
const int pin_reset = 23;
const int pin_dio = 33;
const int pin_txen = 13;
const int pin_rxen = 14;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 39;
const int pin_led_rx = 2;
const int pin_led_tx = 4;
#elif BOARD_MODEL == BOARD_DIY_V1
#define HAS_DISPLAY true
#define HAS_BLUETOOTH true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH false
#define HAS_RF_SWITCH_RX_TX true
#define HAS_LORA_LNA true
#define LORA_LNA_GAIN 17
#define LORA_LNA_GVT 12
const int pin_cs = 18;
const int pin_sclk = 5;
const int pin_miso = 19;
const int pin_mosi = 27;
const int pin_busy = 32;
const int pin_reset = 23;
const int pin_dio = 33;
const int pin_txen = 13;
const int pin_rxen = 14;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 39;
const int pin_led_rx = 2;
const int pin_led_tx = 4;
#elif BOARD_MODEL == BOARD_AETHERNODE
#define HAS_DISPLAY true
#define HAS_BLUETOOTH true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH true
#define HAS_RF_SWITCH_RX_TX false
const int pin_cs = 5;
const int pin_sclk = 18;
const int pin_miso = 19;
const int pin_mosi = 23;
const int pin_busy = 32;
const int pin_reset = 34;
const int pin_dio = 33;
const int pin_txen = -1;
const int pin_rxen = -1;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 39;
const int pin_led_rx = 2;
const int pin_led_tx = 4;
#elif BOARD_MODEL == BOARD_TBEAM
#define HAS_DISPLAY true
@@ -417,6 +572,8 @@
const int pin_tcxo_enable = -1;
#define HAS_BUSY true
#define DIO2_AS_RF_SWITCH true
#define LNA_GD_THRSHLD (-109)
#define LNA_GD_LIMIT (-89)
#define LORA_LNA_GAIN 17
#define LORA_LNA_GVT 12
@@ -885,6 +1042,62 @@
const int DISPLAY_BL_PIN = PIN_T114_TFT_BLGT;
const int DISPLAY_RST = PIN_T114_TFT_RST;
#elif BOARD_MODEL == BOARD_PROMICRO
//TODO:
// - Fix low output power
// - Make compatible with non-TCXO radios
// - Add PMU
#define MODEM SX1262
#define HAS_EEPROM false
#define HAS_BLUETOOTH false
#define HAS_BLE true
#define HAS_CONSOLE false
#define HAS_PMU true
#define HAS_NP false
#define HAS_SD false
#define HAS_TCXO true
#define HAS_BUSY true
#define HAS_RF_SWITCH_RX_TX true
#define DIO2_AS_RF_SWITCH true
#define OCP_TUNED 0x38
#define HAS_SLEEP false
#define BLE_MANUFACTURER "DIY"
#define BLE_MODEL "ProMicro"
#define HAS_INPUT true
#define EEPROM_SIZE 296
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
#define CONFIG_UART_BUFFER_SIZE 6144
#define CONFIG_QUEUE_SIZE 6144
#define CONFIG_QUEUE_MAX_LENGTH 200
//Confused with the pin numbers??
//https://github.com/pdcook/nRFMicro-Arduino-Core/blob/a83161e619da8668f726b52578a3dd89c1ef5956/variants/nice_nano/variant.h#L59
#define HAS_DISPLAY true
#define I2C_SDA 8 //P1.04
#define I2C_SCL 7 //P0.11
#define PIN_LED_RED 22 //P0.15
const int pin_led_rx = PIN_LED_RED;
const int pin_led_tx = PIN_LED_RED;
#define PIN_VEXT_EN 21 //P0.13
const int pin_btn_usr1 = 6; //P1.00
const int pin_reset = 10; //P0.09
const int pin_cs = 13; //P1.13
const int pin_sclk = 12; //P1.11
const int pin_mosi = 14; //P1.15
const int pin_miso = 15; //P0.02
const int pin_busy = 16; //P0.29
const int pin_dio = 11; //P0.10
const int pin_rxen = 2; //P0.17
const int pin_txen = -1;
const int pin_tcxo_enable = -1;
#else
#error An unsupported nRF board was selected. Cannot compile RNode firmware.
#endif

View File

@@ -110,7 +110,16 @@
#define CSMA_CW_PER_BAND_WINDOWS 15
#define CSMA_BAND_1_MAX_AIRTIME 7
#define CSMA_BAND_N_MIN_AIRTIME 85
#define CSMA_INFR_THRESHOLD_DB 11
// Increase threshold for specific boards
#if BOARD_MODEL == BOARD_MESHADVENTURER_S3 || BOARD_MODEL == BOARD_MESHADVENTURER
#define CSMA_INFR_THRESHOLD_DB 14
#elif BOARD_MODEL == BOARD_PROMICRO
#define CSMA_INFR_THRESHOLD_DB 13
#else
#define CSMA_INFR_THRESHOLD_DB 11
#endif
#define CSMA_RFENV_RECAL_MS 2500
#define CSMA_RFENV_RECAL_LIMIT_DB -83
bool interference_detected = false;
@@ -151,7 +160,7 @@
uint8_t model = 0x00;
uint8_t hwrev = 0x00;
#define NOISE_FLOOR_SAMPLES 64
#define NOISE_FLOOR_SAMPLES 128
int noise_floor = -292;
int current_rssi = -292;
int last_rssi = -292;

View File

@@ -53,7 +53,7 @@
#elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_LORA32_V1_0
#define DISP_RST 16
#define DISP_ADDR 0x3C
#define SCL_OLED 15
#define OBSCL_OLED 15
#define SDA_OLED 4
#elif BOARD_MODEL == BOARD_HELTEC32_V3
#define DISP_RST 21
@@ -65,6 +65,36 @@
#define DISP_ADDR 0x3C
#define SCL_OLED 18
#define SDA_OLED 17
#elif BOARD_MODEL == BOARD_GENERIC_ESP32
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 22
#define SDA_OLED 11
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 44
#define SDA_OLED 42
#elif BOARD_MODEL == BOARD_MESHADVENTURER
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 22
#define SDA_OLED 21
#elif BOARD_MODEL == BOARD_DIY_V1
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 22
#define SDA_OLED 21
#elif BOARD_MODEL == BOARD_AETHERNODE
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 22
#define SDA_OLED 21
#elif BOARD_MODEL == BOARD_PROMICRO
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 7
#define SDA_OLED 8
#elif BOARD_MODEL == BOARD_RAK4631
// RAK1921/SSD1306
#define DISP_RST -1
@@ -152,6 +182,7 @@ bool device_firmware_ok();
#define WATERFALL_SIZE 46
int waterfall[WATERFALL_SIZE];
int waterfall_meta[WATERFALL_SIZE];
int waterfall_head = 0;
int p_ad_x = 0;
@@ -310,6 +341,12 @@ bool display_init() {
#elif BOARD_MODEL == BOARD_HELTEC_T114
pinMode(PIN_T114_TFT_EN, OUTPUT);
digitalWrite(PIN_T114_TFT_EN, LOW);
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
Wire.setPins(SDA_OLED, SCL_OLED);
Wire.begin();
#elif BOARD_MODEL == BOARD_PROMICRO
Wire.setPins(SDA_OLED, SCL_OLED);
Wire.begin();
#elif BOARD_MODEL == BOARD_TECHO
display.init(0, true, 10, false, displaySPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
display.setPartialWindow(0, 0, DISP_W, DISP_H);
@@ -433,6 +470,21 @@ bool display_init() {
#elif BOARD_MODEL == BOARD_TECHO
disp_mode = DISP_MODE_PORTRAIT;
display.setRotation(3);
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#elif BOARD_MODEL == BOARD_MESHADVENTURER
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#elif BOARD_MODEL == BOARD_DIY_V1
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#elif BOARD_MODEL == BOARD_AETHERNODE
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#elif BOARD_MODEL == BOARD_PROMICRO
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#else
disp_mode = DISP_MODE_PORTRAIT;
display.setRotation(3);
@@ -722,6 +774,9 @@ void draw_signal_bars(int px, int py) {
#define WF_RSSI_MIN -135
#define WF_RSSI_SPAN (WF_RSSI_MAX-WF_RSSI_MIN)
#define WF_PIXEL_WIDTH 10
#define WF_M_RX 0x00
#define WF_M_TX 0x01
#define WF_M_NTFR 0x02
void draw_waterfall(int px, int py) {
int rssi_val = current_rssi;
if (rssi_val < WF_RSSI_MIN) rssi_val = WF_RSSI_MIN;
@@ -729,11 +784,14 @@ void draw_waterfall(int px, int py) {
int rssi_normalised = ((rssi_val - WF_RSSI_MIN)*(1.0/WF_RSSI_SPAN))*WF_PIXEL_WIDTH;
if (display_tx) {
for (uint8_t i = 0; i < WF_TX_SIZE; i++) {
waterfall_meta[waterfall_head] = WF_M_TX;
waterfall[waterfall_head++] = -1;
if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0;
}
display_tx = false;
} else {
if (interference_detected) { waterfall_meta[waterfall_head] = WF_M_NTFR; }
else { waterfall_meta[waterfall_head] = WF_M_RX; }
waterfall[waterfall_head++] = rssi_normalised;
if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0;
}
@@ -742,8 +800,13 @@ void draw_waterfall(int px, int py) {
for (int i = 0; i < WATERFALL_SIZE; i++){
int wi = (waterfall_head+i)%WATERFALL_SIZE;
int ws = waterfall[wi];
int wm = waterfall_meta[wi];
if (ws > 0) {
stat_area.drawLine(px, py+i, px+ws-1, py+i, SSD1306_WHITE);
if (wm == WF_M_RX) { stat_area.drawLine(px, py+i, px+ws-1, py+i, SSD1306_WHITE); }
else if (wm == WF_M_NTFR) {
uint8_t o = 0;
for (uint8_t ti = 0; ti < WF_PIXEL_WIDTH/2; ti++) { stat_area.drawPixel(px+ti*2+o, py+i, SSD1306_WHITE); }
}
} else if (ws == -1) {
uint8_t o = i%2;
for (uint8_t ti = 0; ti < WF_PIXEL_WIDTH/2; ti++) {
@@ -1059,6 +1122,7 @@ void update_display(bool blank = false) {
#if BOARD_MODEL == BOARD_HELTEC_T114
display.clear();
display.display();
digitalWrite(PIN_T114_TFT_BLGT, HIGH);
#elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO
display.clearDisplay();
display.display();
@@ -1116,6 +1180,9 @@ void update_display(bool blank = false) {
void display_unblank() {
last_unblank_event = millis();
#if BOARD_MODEL == BOARD_HELTEC_T114
digitalWrite(PIN_T114_TFT_BLGT, LOW);
#endif
}
void ext_fb_enable() {

33
MIRROR.md Normal file
View File

@@ -0,0 +1,33 @@
This repository is a public mirror. All potential future development is happening elsewhere.
I am stepping back from all public-facing interaction with this project. Reticulum has always been primarily my work, and continuing in the current public, internet-facing model is no longer sustainable.
The software remains available for use as-is. Occasional updates may appear at unpredictable intervals, but there will be no support, no responses to issues, no discussions, and no community management in this or any other public venue. If it doesn't work for you, it doesn't work. That is the entire extent of available troubleshooting assistance I can offer you.
If you've followed this project for a while, you already know what this means. You know who designed, wrote and tested this, and you know how many years of my life it took. You'll also know about both my particular challenges and strengths, and how I believe anything worth building needs to be built and maintained with our own hands.
Seven months ago, I said I needed to step back, that I was exhausted, and that I needed to recover. I believed a public resolve would be enough to effectuate that, but while striving to get just a few more useful features and protocols out, the unproductive requests and demands also ramped up, and I got pulled back into the same patterns and draining interactions that I'd explicitly said I couldn't sustain anymore.
So here's what you might have already guessed: I'm done playing the game by rules I can't win at.
Everything you need is right here, and by any sensible measure, it's done. Anyone who wants to invest the time, skill and persistence can build on it, or completely re-imagine it with different priorities. That was always the point.
The people who actually contributed - you know who you are, and you know I mean it when I say: Thank you. All of you who've used this to build something real - that was the goal, and you did it without needing me to hold your hand.
The rest of you: You have what you need. Use it or don't. I am not going to be the person who explains it to you anymore.
This is not a temporary break. It's not "see you after some rest", but a recognition that the current model is fundamentally incompatible with my life, my health, and my reality.
If you want to support continued work, you can do so at the donation links listed in this repository. But please understand, that this is not purchasing support or guaranteeing updates. It is support for work that happens on my timeline, according to my capacity, which at the moment is not what it was.
If you want Reticulum to continue evolving, you have the power to make that happen. The protocol is public domain. The code is open source. Everything you need is right here. I've provided the tools, but building what comes next is not my responsibility anymore. It's yours.
To the small group of people who has actually been here, and understood what this work was and what it cost - you already know where to find me if it actually matters.
To everyone else: This is where we part ways. No hard feelings. It's just time.
---
असतो मा सद्गमय
तमसो मा ज्योतिर्गमय
मृत्योर्मा अमृतं गमय

104
Makefile
View File

@@ -17,7 +17,7 @@
ARDUINO_ESP_CORE_VER = 2.0.17
# Version 3.2.0 of the Arduino ESP core is based on ESP-IDF v5.4.1
# ARDUINO_ESP_CORE_VER = 3.2.0
#ARDUINO_ESP_CORE_VER = 3.3.7
all: release
@@ -51,10 +51,12 @@ prep-nrf:
arduino-cli core install rakwireless:nrf52 --config-file arduino-cli.yaml
arduino-cli core install Heltec_nRF52:Heltec_nRF52 --config-file arduino-cli.yaml
arduino-cli core install adafruit:nrf52 --config-file arduino-cli.yaml
arduino-cli core install "promicro:nrf52" --config-file arduino-cli.yaml
sed -i.bak 's/nicenanov2\.build\.ldscript=nrf52840_s140_v7\.ld/nicenanov2.build.ldscript=nrf52840_s140_v6.ld/' ~/.arduino15/packages/promicro/hardware/nrf52/1.0.2/boards.txt
arduino-cli lib install "GxEPD2"
arduino-cli config set library.enable_unsafe_install true
arduino-cli lib install --git-url https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95
pip install adafruit-nrfutil --upgrade
pip install adafruit-nrfutil --upgrade --break-system-packages
console-site:
make -C Console clean site
@@ -140,12 +142,27 @@ firmware-featheresp32: check_bt_buffers
firmware-genericesp32: check_bt_buffers
arduino-cli compile --log --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\""
firmware-meshadventurer_s3: check_bt_buffers
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF2\""
firmware-meshadventurer: check_bt_buffers
arduino-cli compile --log --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF4\""
firmware-diy_v1: check_bt_buffers
arduino-cli compile --log --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF6\""
firmware-aethernode: check_bt_buffers
arduino-cli compile --log --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\""
firmware-rak4631:
arduino-cli compile --log --fqbn rakwireless:nrf52:WisCoreRAK4631Board -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x51\""
firmware-heltec_t114:
arduino-cli compile --log --fqbn Heltec_nRF52:Heltec_nRF52:HT-n5262 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3C\""
firmware-promicro:
arduino-cli compile --log --fqbn promicro:nrf52:nicenanov2:softdevice=s140v6 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF5\""
firmware-techo:
arduino-cli compile --log --fqbn adafruit:nrf52:pca10056 -e --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x44\""
@@ -155,6 +172,41 @@ firmware-xiao_s3:
upload:
arduino-cli upload -p /dev/ttyUSB0 --fqbn unsignedio:avr:rnode
upload-genericesp32:
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32
@sleep 1
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-meshadventurer_s3:
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3
# @sleep 1
# rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-meshadventurer:
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32
@sleep 1
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-diy_v1:
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32
@sleep 1
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-aethernode:
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32
@sleep 1
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-mega2560:
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:mega
@@ -198,7 +250,7 @@ upload-heltec32_v2:
@sleep 1
rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin)
@sleep 3
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB1 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin
upload-heltec32_v3:
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:heltec_wifi_lora_32_V3
@@ -266,6 +318,11 @@ upload-heltec_t114:
@sleep 1
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0)
upload-promicro:
arduino-cli upload -p /dev/ttyACM0 --fqbn promicro:nrf52:nicenanov2:softdevice=s140v6
@sleep 6
rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0)
upload-techo:
arduino-cli upload -p /dev/ttyACM0 --fqbn adafruit:nrf52:pca10056
@sleep 6
@@ -488,6 +545,42 @@ release-genericesp32: check_bt_buffers
zip --junk-paths ./Release/rnode_firmware_esp32_generic.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_esp32_generic.boot_app0 build/rnode_firmware_esp32_generic.bin build/rnode_firmware_esp32_generic.bootloader build/rnode_firmware_esp32_generic.partitions
rm -r build
release-meshadventurer_s3: check_bt_buffers
arduino-cli compile --fqbn esp32:esp32:esp32s3 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF2\""
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_meshadventurer_s3.boot_app0
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_meshadventurer_s3.bin
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_meshadventurer_s3.bootloader
cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_meshadventurer_s3.partitions
zip --junk-paths ./Release/rnode_firmware_meshadventurer_s3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_meshadventurer_s3.boot_app0 build/rnode_firmware_meshadventurer_s3.bin build/rnode_firmware_meshadventurer_s3.bootloader build/rnode_firmware_meshadventurer_s3.partitions
rm -r build
release-meshadventurer: check_bt_buffers
arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF4\""
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_meshadventurer.boot_app0
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bin build/rnode_firmware_meshadventurer.bin
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_meshadventurer.bootloader
cp build/esp32.esp32.esp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_meshadventurer.partitions
zip --junk-paths ./Release/rnode_firmware_meshadventurer.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_meshadventurer.boot_app0 build/rnode_firmware_meshadventurer.bin build/rnode_firmware_meshadventurer.bootloader build/rnode_firmware_meshadventurer.partitions
rm -r build
release-diy_v1: check_bt_buffers
arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF6\""
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_diy_v1.boot_app0
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bin build/rnode_firmware_diy_v1.bin
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_diy_v1.bootloader
cp build/esp32.esp32.esp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_diy_v1.partitions
zip --junk-paths ./Release/rnode_firmware_diy_v1.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_diy_v1.boot_app0 build/rnode_firmware_diy_v1.bin build/rnode_firmware_diy_v1.bootloader build/rnode_firmware_diy_v1.partitions
rm -r build
release-aethernode: check_bt_buffers
arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\""
cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_aethernode.boot_app0
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bin build/rnode_firmware_aethernode.bin
cp build/esp32.esp32.esp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_aethernode.bootloader
cp build/esp32.esp32.esp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_aethernode.partitions
zip --junk-paths ./Release/rnode_firmware_aethernode.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_aethernode.boot_app0 build/rnode_firmware_aethernode.bin build/rnode_firmware_aethernode.bootloader build/rnode_firmware_aethernode.partitions
rm -r build
release-mega2560:
arduino-cli compile --fqbn arduino:avr:mega -e --build-property "compiler.cpp.extra_flags=\"-DMODEM=0x01\""
cp build/arduino.avr.mega/RNode_Firmware.ino.hex Release/rnode_firmware_m2560.hex
@@ -503,6 +596,11 @@ release-heltec_t114:
cp build/Heltec_nRF52.Heltec_nRF52.HT-n5262/RNode_Firmware.ino.hex build/rnode_firmware_heltec_t114.hex
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_heltec_t114.hex Release/rnode_firmware_heltec_t114.zip
release-promicro:
arduino-cli compile --log --fqbn promicro:nrf52:nicenanov2:softdevice=s140v6 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0xF5\""
cp build/promicro.nrf52.nicenanov2/RNode_Firmware.ino.hex build/rnode_firmware_promicro.hex
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_promicro.hex Release/rnode_firmware_promicro.zip
release-techo:
arduino-cli compile --log --fqbn adafruit:nrf52:pca10056 -e --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x44\""
cp build/adafruit.nrf52.pca10056/RNode_Firmware.ino.hex build/rnode_firmware_techo.hex

23
Power.h
View File

@@ -164,6 +164,22 @@ float pmu_temperature = PMU_TEMP_MIN-1;
bool bat_voltage_dropping = false;
float bat_delay_v = 0;
float bat_state_change_v = 0;
#elif BOARD_MODEL == BOARD_PROMICRO
#define BAT_V_MIN 3.15
#define BAT_V_MAX 4.165
#define BAT_V_CHG 4.48
#define BAT_V_FLOAT 4.33
#define BAT_SAMPLES 7
const uint8_t pin_vbat = 17;
float bat_p_samples[BAT_SAMPLES];
float bat_v_samples[BAT_SAMPLES];
uint8_t bat_samples_count = 0;
int bat_discharging_samples = 0;
int bat_charging_samples = 0;
int bat_charged_samples = 0;
bool bat_voltage_dropping = false;
float bat_delay_v = 0;
float bat_state_change_v = 0;
#elif BOARD_MODEL == BOARD_TECHO
#define BAT_V_MIN 3.15
#define BAT_V_MAX 4.16
@@ -202,7 +218,7 @@ void measure_temperature() {
}
void measure_battery() {
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TECHO
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_PROMICRO || BOARD_MODEL == BOARD_TECHO
battery_installed = true;
#if BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4
battery_indeterminate = false;
@@ -218,6 +234,8 @@ void measure_battery() {
float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*6.7828;
#elif BOARD_MODEL == BOARD_HELTEC_T114
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.017165;
#elif BOARD_MODEL == BOARD_PROMICRO
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.017165;
#elif BOARD_MODEL == BOARD_TECHO
float battery_measurement = (float)(analogRead(pin_vbat)) * 0.007067;
#else
@@ -225,6 +243,7 @@ void measure_battery() {
#endif
bat_v_samples[bat_samples_count%BAT_SAMPLES] = battery_measurement;
bat_p_samples[bat_samples_count%BAT_SAMPLES] = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0;
bat_samples_count++;
@@ -411,7 +430,7 @@ bool init_pmu() {
pmu_temp_sensor_ready = true;
#endif
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_TECHO
#if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_TECHO || BOARD_MODEL == BOARD_PROMICRO
pinMode(pin_vbat, INPUT);
return true;
#elif BOARD_MODEL == BOARD_HELTEC32_V3

View File

@@ -1,3 +1,5 @@
*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.*
***Important!** This repository is currently functioning as a stable reference for the default RNode Firmware, and only receives bugfix and security updates. Further development, new features and expanded board support is now happening at the [RNode Firmware Community Edition](https://github.com/liberatedsystems/RNode_Firmware_CE) repository, and is maintained by [Liberated Embedded Systems](https://github.com/liberatedsystems). Thanks for all contributions so far!*
# RNode Firmware
@@ -75,6 +77,7 @@ The RNode Firmware supports the following boards:
- LilyGO T-Echo devices
- Heltec LoRa32 v2 devices
- Heltec LoRa32 v3 devices
- Heltec LoRa32 v4 devices
- Heltec T114 devices
- RAK4631 devices
- SeeedStudio XIAO ESP32S3 devices (with Wio-SX1262)
@@ -122,12 +125,14 @@ You can help support the continued development of open, free and private communi
```
- Bitcoin
```
bc1p4a6axuvl7n9hpapfj8sv5reqj8kz6uxa67d5en70vzrttj0fmcusgxsfk5
bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6
```
- Ethereum
```
0xae89F3B94fC4AD6563F0864a55F9a697a90261ff
0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E
```
- Liberapay: https://liberapay.com/Reticulum/
- Ko-Fi: https://ko-fi.com/markqvist

View File

@@ -89,6 +89,12 @@ void setup() {
pinMode(PIN_LED_BLUE, OUTPUT);
delay(200);
#endif
#if BOARD_MODEL == BOARD_PROMICRO
delay(200);
pinMode(PIN_VEXT_EN, OUTPUT);
digitalWrite(PIN_VEXT_EN, HIGH);
delay(200);
#endif
if (!eeprom_begin()) { Serial.write("EEPROM initialisation failed.\r\n"); }
#endif
@@ -129,7 +135,7 @@ void setup() {
boot_seq();
#endif
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_MESHADVENTURER_S3 && BOARD_MODEL != BOARD_PROMICRO && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4
// Some boards need to wait until the hardware UART is set up before booting
// the full firmware. In the case of the RAK4631 and Heltec T114, the line below will wait
// until a serial connection is actually established with a master. Thus, it
@@ -535,7 +541,9 @@ bool startRadio() {
// Flash an info pattern to indicate
// that the radio is now on
kiss_indicate_radiostate();
led_indicate_info(3);
if (!display_blanked) {
led_indicate_info(3);
}
return true;
}
@@ -575,7 +583,7 @@ volatile bool queue_flushing = false;
void flush_queue(void) {
if (!queue_flushing) {
queue_flushing = true;
led_tx_on();
if (!display_blanked) { led_tx_on(); }
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
while (!fifo16_isempty(&packet_starts)) {
@@ -596,7 +604,7 @@ void flush_queue(void) {
}
}
lora_receive(); led_tx_off();
lora_receive(); if (!display_blanked) { led_tx_off(); }
}
queue_height = 0;
@@ -615,7 +623,8 @@ void flush_queue(void) {
void pop_queue() {
if (!queue_flushing) {
queue_flushing = true; led_tx_on();
queue_flushing = true;
if (!display_blanked) { led_tx_on(); }
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
if (!fifo16_isempty(&packet_starts)) {
@@ -637,7 +646,8 @@ void pop_queue() {
queued_bytes -= length;
}
lora_receive(); led_tx_off();
lora_receive();
if (!display_blanked) { led_tx_off(); }
}
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
@@ -751,7 +761,7 @@ void transmit(uint16_t size) {
add_airtime(written);
} else {
led_tx_on(); uint16_t written = 0;
if (!display_blanked) { led_tx_on(); } uint16_t written = 0;
if (size > SINGLE_MTU) { size = SINGLE_MTU; }
if (!implicit) { LoRa->beginPacket(); }
else { LoRa->beginPacket(size); }
@@ -984,7 +994,7 @@ void serial_callback(uint8_t sbyte) {
} else if (command == CMD_RADIO_LOCK) {
update_radio_lock();
kiss_indicate_radio_lock();
} else if (command == CMD_BLINK) {
} else if (command == CMD_BLINK && !display_blanked) {
led_indicate_info(sbyte);
} else if (command == CMD_RANDOM) {
kiss_indicate_random(getRandom());
@@ -1358,20 +1368,26 @@ int noise_floor_buffer[NOISE_FLOOR_SAMPLES] = {0};
void update_noise_floor() {
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
if (!dcd) {
#if BOARD_MODEL != BOARD_HELTEC32_V4
if (!noise_floor_sampled || current_rssi < noise_floor + CSMA_INFR_THRESHOLD_DB) {
#else
if ((!noise_floor_sampled || current_rssi < noise_floor + CSMA_INFR_THRESHOLD_DB) || (noise_floor_sampled && (noise_floor < LNA_GD_THRSHLD && current_rssi <= LNA_GD_LIMIT))) {
#endif
#if HAS_LORA_LNA
// Discard invalid samples due to gain variance
// during LoRa LNA re-calibration
if (current_rssi < noise_floor-LORA_LNA_GVT) { return; }
#endif
bool sum_noise_floor = false;
noise_floor_buffer[noise_floor_sample] = current_rssi;
noise_floor_sample = noise_floor_sample+1;
if (noise_floor_sample >= NOISE_FLOOR_SAMPLES) {
noise_floor_sample %= NOISE_FLOOR_SAMPLES;
noise_floor_sampled = true;
sum_noise_floor = true;
}
if (noise_floor_sampled) {
if (noise_floor_sampled && sum_noise_floor) {
noise_floor = 0;
for (int ni = 0; ni < NOISE_FLOOR_SAMPLES; ni++) { noise_floor += noise_floor_buffer[ni]; }
noise_floor /= NOISE_FLOOR_SAMPLES;
@@ -1402,7 +1418,13 @@ void update_modem_status() {
portEXIT_CRITICAL();
#endif
interference_detected = !carrier_detected && (current_rssi > (noise_floor+CSMA_INFR_THRESHOLD_DB));
#if BOARD_MODEL == BOARD_HELTEC32_V4
if (noise_floor > LNA_GD_THRSHLD) { interference_detected = !carrier_detected && (current_rssi > (noise_floor+CSMA_INFR_THRESHOLD_DB)); }
else { interference_detected = !carrier_detected && (current_rssi > LNA_GD_LIMIT); }
#else
interference_detected = !carrier_detected && (current_rssi > (noise_floor+CSMA_INFR_THRESHOLD_DB));
#endif
if (interference_detected) { if (led_id_filter < LED_ID_TRIG) { led_id_filter += 1; } }
else { if (led_id_filter > 0) {led_id_filter -= 1; } }
@@ -1410,25 +1432,24 @@ void update_modem_status() {
// LNA recalibration, antenna swap, moving into new RF
// environment or similar.
if (interference_detected && current_rssi < CSMA_RFENV_RECAL_LIMIT_DB) {
if (!interference_persists) {
interference_persists = true; interference_start = millis();
} else {
if (!interference_persists) { interference_persists = true; interference_start = millis(); }
else {
if (millis()-interference_start >= CSMA_RFENV_RECAL_MS) { noise_floor_sampled = false; interference_persists = false; }
}
} else {
interference_persists = false;
}
} else { interference_persists = false; }
if (carrier_detected) { dcd = true; } else { dcd = false; }
dcd_led = dcd;
if (dcd_led) { led_rx_on(); }
if (!display_blanked && dcd_led) { led_rx_on(); }
else {
if (interference_detected) {
if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled) { led_id_on(); }
if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled && !display_blanked) { led_id_on(); }
} else {
if (airtime_lock) { led_indicate_airtime_lock(); }
else { led_rx_off(); led_id_off(); }
if (airtime_lock && !display_blanked) { led_indicate_airtime_lock(); }
else {
if (!display_blanked) { led_rx_off(); led_id_off(); }
}
}
}
}
@@ -1698,7 +1719,9 @@ void loop() {
console_loop();
#endif
} else {
led_indicate_standby();
if (!display_blanked) {
led_indicate_standby();
}
}
} else {

View File

@@ -1,10 +0,0 @@
# Precompiled Firmware
You can download and flash the firmware to supported boards using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil). All firmware releases are now handled and installed directly through `rnodeconf`, which is inclueded in the `rns` package. It can be installed via `pip`:
```
# Install rnodeconf via rns package
pip install rns --upgrade
# Install the firmware on a board with the install guide
rnodeconf --autoinstall
```

Binary file not shown.

View File

@@ -1,595 +0,0 @@
#!/usr/bin/env python
#
# ESP32 partition table generation tool
#
# Converts partition tables to/from CSV and binary formats.
#
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html
# for explanation of partition table structure and uses.
#
# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import division, print_function, unicode_literals
import argparse
import binascii
import errno
import hashlib
import os
import re
import struct
import sys
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
MD5_PARTITION_BEGIN = b'\xEB\xEB' + b'\xFF' * 14 # The first 2 bytes are like magic numbers for MD5 sum
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
MIN_PARTITION_SUBTYPE_APP_OTA = 0x10
NUM_PARTITION_SUBTYPE_APP_OTA = 16
__version__ = '1.2'
APP_TYPE = 0x00
DATA_TYPE = 0x01
TYPES = {
'app': APP_TYPE,
'data': DATA_TYPE,
}
def get_ptype_as_int(ptype):
""" Convert a string which might be numeric or the name of a partition type to an integer """
try:
return TYPES[ptype]
except KeyError:
try:
return int(ptype, 0)
except TypeError:
return ptype
# Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h
SUBTYPES = {
APP_TYPE: {
'factory': 0x00,
'test': 0x20,
},
DATA_TYPE: {
'ota': 0x00,
'phy': 0x01,
'nvs': 0x02,
'coredump': 0x03,
'nvs_keys': 0x04,
'efuse': 0x05,
'undefined': 0x06,
'esphttpd': 0x80,
'fat': 0x81,
'spiffs': 0x82,
},
}
def get_subtype_as_int(ptype, subtype):
""" Convert a string which might be numeric or the name of a partition subtype to an integer """
try:
return SUBTYPES[get_ptype_as_int(ptype)][subtype]
except KeyError:
try:
return int(subtype, 0)
except TypeError:
return subtype
ALIGNMENT = {
APP_TYPE: 0x10000,
DATA_TYPE: 0x1000,
}
def get_alignment_for_type(ptype):
return ALIGNMENT.get(ptype, ALIGNMENT[DATA_TYPE])
def get_partition_type(ptype):
if ptype == 'app':
return APP_TYPE
if ptype == 'data':
return DATA_TYPE
raise InputError('Invalid partition type')
def add_extra_subtypes(csv):
for line_no in csv:
try:
fields = [line.strip() for line in line_no.split(',')]
for subtype, subtype_values in SUBTYPES.items():
if (int(fields[2], 16) in subtype_values.values() and subtype == get_partition_type(fields[0])):
raise ValueError('Found duplicate value in partition subtype')
SUBTYPES[TYPES[fields[0]]][fields[1]] = int(fields[2], 16)
except InputError as err:
raise InputError('Error parsing custom subtypes: %s' % err)
quiet = False
md5sum = True
secure = False
offset_part_table = 0
def status(msg):
""" Print status message to stderr """
if not quiet:
critical(msg)
def critical(msg):
""" Print critical message to stderr """
sys.stderr.write(msg)
sys.stderr.write('\n')
class PartitionTable(list):
def __init__(self):
super(PartitionTable, self).__init__(self)
@classmethod
def from_file(cls, f):
data = f.read()
data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES
if data_is_binary:
status('Parsing binary partition input...')
return cls.from_binary(data), True
data = data.decode()
status('Parsing CSV input...')
return cls.from_csv(data), False
@classmethod
def from_csv(cls, csv_contents):
res = PartitionTable()
lines = csv_contents.splitlines()
def expand_vars(f):
f = os.path.expandvars(f)
m = re.match(r'(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)', f)
if m:
raise InputError("unknown variable '%s'" % m.group(1))
return f
for line_no in range(len(lines)):
line = expand_vars(lines[line_no]).strip()
if line.startswith('#') or len(line) == 0:
continue
try:
res.append(PartitionDefinition.from_csv(line, line_no + 1))
except InputError as err:
raise InputError('Error at line %d: %s\nPlease check extra_partition_subtypes.inc file in build/config directory' % (line_no + 1, err))
except Exception:
critical('Unexpected error parsing CSV line %d: %s' % (line_no + 1, line))
raise
# fix up missing offsets & negative sizes
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
for e in res:
if e.offset is not None and e.offset < last_end:
if e == res[0]:
raise InputError('CSV Error at line %d: Partitions overlap. Partition sets offset 0x%x. '
'But partition table occupies the whole sector 0x%x. '
'Use a free offset 0x%x or higher.'
% (e.line_no, e.offset, offset_part_table, last_end))
else:
raise InputError('CSV Error at line %d: Partitions overlap. Partition sets offset 0x%x. Previous partition ends 0x%x'
% (e.line_no, e.offset, last_end))
if e.offset is None:
pad_to = get_alignment_for_type(e.type)
if last_end % pad_to != 0:
last_end += pad_to - (last_end % pad_to)
e.offset = last_end
if e.size < 0:
e.size = -e.size - e.offset
last_end = e.offset + e.size
return res
def __getitem__(self, item):
""" Allow partition table access via name as well as by
numeric index. """
if isinstance(item, str):
for x in self:
if x.name == item:
return x
raise ValueError("No partition entry named '%s'" % item)
else:
return super(PartitionTable, self).__getitem__(item)
def find_by_type(self, ptype, subtype):
""" Return a partition by type & subtype, returns
None if not found """
# convert ptype & subtypes names (if supplied this way) to integer values
ptype = get_ptype_as_int(ptype)
subtype = get_subtype_as_int(ptype, subtype)
for p in self:
if p.type == ptype and p.subtype == subtype:
yield p
return
def find_by_name(self, name):
for p in self:
if p.name == name:
return p
return None
def verify(self):
# verify each partition individually
for p in self:
p.verify()
# check on duplicate name
names = [p.name for p in self]
duplicates = set(n for n in names if names.count(n) > 1)
# print sorted duplicate partitions by name
if len(duplicates) != 0:
critical('A list of partitions that have the same name:')
for p in sorted(self, key=lambda x:x.name):
if len(duplicates.intersection([p.name])) != 0:
critical('%s' % (p.to_csv()))
raise InputError('Partition names must be unique')
# check for overlaps
last = None
for p in sorted(self, key=lambda x:x.offset):
if p.offset < offset_part_table + PARTITION_TABLE_SIZE:
raise InputError('Partition offset 0x%x is below 0x%x' % (p.offset, offset_part_table + PARTITION_TABLE_SIZE))
if last is not None and p.offset < last.offset + last.size:
raise InputError('Partition at 0x%x overlaps 0x%x-0x%x' % (p.offset, last.offset, last.offset + last.size - 1))
last = p
# check that otadata should be unique
otadata_duplicates = [p for p in self if p.type == TYPES['data'] and p.subtype == SUBTYPES[DATA_TYPE]['ota']]
if len(otadata_duplicates) > 1:
for p in otadata_duplicates:
critical('%s' % (p.to_csv()))
raise InputError('Found multiple otadata partitions. Only one partition can be defined with type="data"(1) and subtype="ota"(0).')
if len(otadata_duplicates) == 1 and otadata_duplicates[0].size != 0x2000:
p = otadata_duplicates[0]
critical('%s' % (p.to_csv()))
raise InputError('otadata partition must have size = 0x2000')
def flash_size(self):
""" Return the size that partitions will occupy in flash
(ie the offset the last partition ends at)
"""
try:
last = sorted(self, reverse=True)[0]
except IndexError:
return 0 # empty table!
return last.offset + last.size
def verify_size_fits(self, flash_size_bytes: int) -> None:
""" Check that partition table fits into the given flash size.
Raises InputError otherwise.
"""
table_size = self.flash_size()
if flash_size_bytes < table_size:
mb = 1024 * 1024
raise InputError('Partitions tables occupies %.1fMB of flash (%d bytes) which does not fit in configured '
"flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." %
(table_size / mb, table_size, flash_size_bytes / mb))
@classmethod
def from_binary(cls, b):
md5 = hashlib.md5()
result = cls()
for o in range(0,len(b),32):
data = b[o:o + 32]
if len(data) != 32:
raise InputError('Partition table length must be a multiple of 32 bytes')
if data == b'\xFF' * 32:
return result # got end marker
if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: # check only the magic number part
if data[16:] == md5.digest():
continue # the next iteration will check for the end marker
else:
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
else:
md5.update(data)
result.append(PartitionDefinition.from_binary(data))
raise InputError('Partition table is missing an end-of-table marker')
def to_binary(self):
result = b''.join(e.to_binary() for e in self)
if md5sum:
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
if len(result) >= MAX_PARTITION_LENGTH:
raise InputError('Binary partition table length (%d) longer than max' % len(result))
result += b'\xFF' * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing
return result
def to_csv(self, simple_formatting=False):
rows = ['# ESP-IDF Partition Table',
'# Name, Type, SubType, Offset, Size, Flags']
rows += [x.to_csv(simple_formatting) for x in self]
return '\n'.join(rows) + '\n'
class PartitionDefinition(object):
MAGIC_BYTES = b'\xAA\x50'
# dictionary maps flag name (as used in CSV flags list, property name)
# to bit set in flags words in binary format
FLAGS = {
'encrypted': 0
}
# add subtypes for the 16 OTA slot values ("ota_XX, etc.")
for ota_slot in range(NUM_PARTITION_SUBTYPE_APP_OTA):
SUBTYPES[TYPES['app']]['ota_%d' % ota_slot] = MIN_PARTITION_SUBTYPE_APP_OTA + ota_slot
def __init__(self):
self.name = ''
self.type = None
self.subtype = None
self.offset = None
self.size = None
self.encrypted = False
@classmethod
def from_csv(cls, line, line_no):
""" Parse a line from the CSV """
line_w_defaults = line + ',,,,' # lazy way to support default fields
fields = [f.strip() for f in line_w_defaults.split(',')]
res = PartitionDefinition()
res.line_no = line_no
res.name = fields[0]
res.type = res.parse_type(fields[1])
res.subtype = res.parse_subtype(fields[2])
res.offset = res.parse_address(fields[3])
res.size = res.parse_address(fields[4])
if res.size is None:
raise InputError("Size field can't be empty")
flags = fields[5].split(':')
for flag in flags:
if flag in cls.FLAGS:
setattr(res, flag, True)
elif len(flag) > 0:
raise InputError("CSV flag column contains unknown flag '%s'" % (flag))
return res
def __eq__(self, other):
return self.name == other.name and self.type == other.type \
and self.subtype == other.subtype and self.offset == other.offset \
and self.size == other.size
def __repr__(self):
def maybe_hex(x):
return '0x%x' % x if x is not None else 'None'
return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0,
maybe_hex(self.offset), maybe_hex(self.size))
def __str__(self):
return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1)
def __cmp__(self, other):
return self.offset - other.offset
def __lt__(self, other):
return self.offset < other.offset
def __gt__(self, other):
return self.offset > other.offset
def __le__(self, other):
return self.offset <= other.offset
def __ge__(self, other):
return self.offset >= other.offset
def parse_type(self, strval):
if strval == '':
raise InputError("Field 'type' can't be left empty.")
return parse_int(strval, TYPES)
def parse_subtype(self, strval):
if strval == '':
if self.type == TYPES['app']:
raise InputError('App partition cannot have an empty subtype')
return SUBTYPES[DATA_TYPE]['undefined']
return parse_int(strval, SUBTYPES.get(self.type, {}))
def parse_address(self, strval):
if strval == '':
return None # PartitionTable will fill in default
return parse_int(strval)
def verify(self):
if self.type is None:
raise ValidationError(self, 'Type field is not set')
if self.subtype is None:
raise ValidationError(self, 'Subtype field is not set')
if self.offset is None:
raise ValidationError(self, 'Offset field is not set')
align = get_alignment_for_type(self.type)
if self.offset % align:
raise ValidationError(self, 'Offset 0x%x is not aligned to 0x%x' % (self.offset, align))
if self.size % align and secure and self.type == APP_TYPE:
raise ValidationError(self, 'Size 0x%x is not aligned to 0x%x' % (self.size, align))
if self.size is None:
raise ValidationError(self, 'Size field is not set')
if self.name in TYPES and TYPES.get(self.name, '') != self.type:
critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's "
'type (0x%x). Mistake in partition table?' % (self.name, self.type))
all_subtype_names = []
for names in (t.keys() for t in SUBTYPES.values()):
all_subtype_names += names
if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, '') != self.subtype:
critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has "
'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype))
STRUCT_FORMAT = b'<2sBBLL16sL'
@classmethod
def from_binary(cls, b):
if len(b) != 32:
raise InputError('Partition definition length must be exactly 32 bytes. Got %d bytes.' % len(b))
res = cls()
(magic, res.type, res.subtype, res.offset,
res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b)
if b'\x00' in res.name: # strip null byte padding from name string
res.name = res.name[:res.name.index(b'\x00')]
res.name = res.name.decode()
if magic != cls.MAGIC_BYTES:
raise InputError('Invalid magic bytes (%r) for partition definition' % magic)
for flag,bit in cls.FLAGS.items():
if flags & (1 << bit):
setattr(res, flag, True)
flags &= ~(1 << bit)
if flags != 0:
critical('WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?' % flags)
return res
def get_flags_list(self):
return [flag for flag in self.FLAGS.keys() if getattr(self, flag)]
def to_binary(self):
flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list())
return struct.pack(self.STRUCT_FORMAT,
self.MAGIC_BYTES,
self.type, self.subtype,
self.offset, self.size,
self.name.encode(),
flags)
def to_csv(self, simple_formatting=False):
def addr_format(a, include_sizes):
if not simple_formatting and include_sizes:
for (val, suffix) in [(0x100000, 'M'), (0x400, 'K')]:
if a % val == 0:
return '%d%s' % (a // val, suffix)
return '0x%x' % a
def lookup_keyword(t, keywords):
for k,v in keywords.items():
if simple_formatting is False and t == v:
return k
return '%d' % t
def generate_text_flags():
""" colon-delimited list of flags """
return ':'.join(self.get_flags_list())
return ','.join([self.name,
lookup_keyword(self.type, TYPES),
lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})),
addr_format(self.offset, False),
addr_format(self.size, True),
generate_text_flags()])
def parse_int(v, keywords={}):
"""Generic parser for integer fields - int(x,0) with provision for
k/m/K/M suffixes and 'keyword' value lookup.
"""
try:
for letter, multiplier in [('k', 1024), ('m', 1024 * 1024)]:
if v.lower().endswith(letter):
return parse_int(v[:-1], keywords) * multiplier
return int(v, 0)
except ValueError:
if len(keywords) == 0:
raise InputError('Invalid field value %s' % v)
try:
return keywords[v.lower()]
except KeyError:
raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ', '.join(keywords)))
def main():
global quiet
global md5sum
global offset_part_table
global secure
parser = argparse.ArgumentParser(description='ESP32 partition table utility')
parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash',
nargs='?', choices=['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB'])
parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true')
parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true')
parser.add_argument('--verify', '-v', help='Verify partition table fields (deprecated, this behaviour is '
'enabled by default and this flag does nothing.', action='store_true')
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
parser.add_argument('--secure', help='Require app partitions to be suitable for secure boot', action='store_true')
parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*')
parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb'))
parser.add_argument('output', help='Path to output converted binary or CSV file. Will use stdout if omitted.',
nargs='?', default='-')
args = parser.parse_args()
quiet = args.quiet
md5sum = not args.disable_md5sum
secure = args.secure
offset_part_table = int(args.offset, 0)
if args.extra_partition_subtypes:
add_extra_subtypes(args.extra_partition_subtypes)
table, input_is_binary = PartitionTable.from_file(args.input)
if not args.no_verify:
status('Verifying table...')
table.verify()
if args.flash_size:
size_mb = int(args.flash_size.replace('MB', ''))
table.verify_size_fits(size_mb * 1024 * 1024)
# Make sure that the output directory is created
output_dir = os.path.abspath(os.path.dirname(args.output))
if not os.path.exists(output_dir):
try:
os.makedirs(output_dir)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
if input_is_binary:
output = table.to_csv()
with sys.stdout if args.output == '-' else open(args.output, 'w') as f:
f.write(output)
else:
output = table.to_binary()
try:
stdout_binary = sys.stdout.buffer # Python 3
except AttributeError:
stdout_binary = sys.stdout
with stdout_binary if args.output == '-' else open(args.output, 'wb') as f:
f.write(output)
class InputError(RuntimeError):
def __init__(self, e):
super(InputError, self).__init__(e)
class ValidationError(InputError):
def __init__(self, partition, message):
super(ValidationError, self).__init__(
'Partition %s invalid: %s' % (partition.name, message))
if __name__ == '__main__':
try:
main()
except InputError as e:
print(e, file=sys.stderr)
sys.exit(2)

View File

@@ -1,593 +0,0 @@
#!/usr/bin/env python
#
# spiffsgen is a tool used to generate a spiffs image from a directory
#
# SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import division, print_function
import argparse
import io
import math
import os
import struct
try:
import typing
TSP = typing.TypeVar('TSP', bound='SpiffsObjPageWithIdx')
ObjIdsItem = typing.Tuple[int, typing.Type[TSP]]
except ImportError:
pass
SPIFFS_PH_FLAG_USED_FINAL_INDEX = 0xF8
SPIFFS_PH_FLAG_USED_FINAL = 0xFC
SPIFFS_PH_FLAG_LEN = 1
SPIFFS_PH_IX_SIZE_LEN = 4
SPIFFS_PH_IX_OBJ_TYPE_LEN = 1
SPIFFS_TYPE_FILE = 1
# Based on typedefs under spiffs_config.h
SPIFFS_OBJ_ID_LEN = 2 # spiffs_obj_id
SPIFFS_SPAN_IX_LEN = 2 # spiffs_span_ix
SPIFFS_PAGE_IX_LEN = 2 # spiffs_page_ix
SPIFFS_BLOCK_IX_LEN = 2 # spiffs_block_ix
class SpiffsBuildConfig(object):
def __init__(self,
page_size, # type: int
page_ix_len, # type: int
block_size, # type: int
block_ix_len, # type: int
meta_len, # type: int
obj_name_len, # type: int
obj_id_len, # type: int
span_ix_len, # type: int
packed, # type: bool
aligned, # type: bool
endianness, # type: str
use_magic, # type: bool
use_magic_len, # type: bool
aligned_obj_ix_tables # type: bool
):
if block_size % page_size != 0:
raise RuntimeError('block size should be a multiple of page size')
self.page_size = page_size
self.block_size = block_size
self.obj_id_len = obj_id_len
self.span_ix_len = span_ix_len
self.packed = packed
self.aligned = aligned
self.obj_name_len = obj_name_len
self.meta_len = meta_len
self.page_ix_len = page_ix_len
self.block_ix_len = block_ix_len
self.endianness = endianness
self.use_magic = use_magic
self.use_magic_len = use_magic_len
self.aligned_obj_ix_tables = aligned_obj_ix_tables
self.PAGES_PER_BLOCK = self.block_size // self.page_size
self.OBJ_LU_PAGES_PER_BLOCK = int(math.ceil(self.block_size / self.page_size * self.obj_id_len / self.page_size))
self.OBJ_USABLE_PAGES_PER_BLOCK = self.PAGES_PER_BLOCK - self.OBJ_LU_PAGES_PER_BLOCK
self.OBJ_LU_PAGES_OBJ_IDS_LIM = self.page_size // self.obj_id_len
self.OBJ_DATA_PAGE_HEADER_LEN = self.obj_id_len + self.span_ix_len + SPIFFS_PH_FLAG_LEN
pad = 4 - (4 if self.OBJ_DATA_PAGE_HEADER_LEN % 4 == 0 else self.OBJ_DATA_PAGE_HEADER_LEN % 4)
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED = self.OBJ_DATA_PAGE_HEADER_LEN + pad
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD = pad
self.OBJ_DATA_PAGE_CONTENT_LEN = self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN
self.OBJ_INDEX_PAGES_HEADER_LEN = (self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED + SPIFFS_PH_IX_SIZE_LEN +
SPIFFS_PH_IX_OBJ_TYPE_LEN + self.obj_name_len + self.meta_len)
if aligned_obj_ix_tables:
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = (self.OBJ_INDEX_PAGES_HEADER_LEN + SPIFFS_PAGE_IX_LEN - 1) & ~(SPIFFS_PAGE_IX_LEN - 1)
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED - self.OBJ_INDEX_PAGES_HEADER_LEN
else:
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = self.OBJ_INDEX_PAGES_HEADER_LEN
self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = 0
self.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM = (self.page_size - self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED) // self.block_ix_len
self.OBJ_INDEX_PAGES_OBJ_IDS_LIM = (self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED) // self.block_ix_len
class SpiffsFullError(RuntimeError):
pass
class SpiffsPage(object):
_endianness_dict = {
'little': '<',
'big': '>'
}
_len_dict = {
1: 'B',
2: 'H',
4: 'I',
8: 'Q'
}
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
self.build_config = build_config
self.bix = bix
def to_binary(self): # type: () -> bytes
raise NotImplementedError()
class SpiffsObjPageWithIdx(SpiffsPage):
def __init__(self, obj_id, build_config): # type: (int, SpiffsBuildConfig) -> None
super(SpiffsObjPageWithIdx, self).__init__(0, build_config)
self.obj_id = obj_id
def to_binary(self): # type: () -> bytes
raise NotImplementedError()
class SpiffsObjLuPage(SpiffsPage):
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
SpiffsPage.__init__(self, bix, build_config)
self.obj_ids_limit = self.build_config.OBJ_LU_PAGES_OBJ_IDS_LIM
self.obj_ids = list() # type: typing.List[ObjIdsItem]
def _calc_magic(self, blocks_lim): # type: (int) -> int
# Calculate the magic value mirroring computation done by the macro SPIFFS_MAGIC defined in
# spiffs_nucleus.h
magic = 0x20140529 ^ self.build_config.page_size
if self.build_config.use_magic_len:
magic = magic ^ (blocks_lim - self.bix)
# narrow the result to build_config.obj_id_len bytes
mask = (2 << (8 * self.build_config.obj_id_len)) - 1
return magic & mask
def register_page(self, page): # type: (TSP) -> None
if not self.obj_ids_limit > 0:
raise SpiffsFullError()
obj_id = (page.obj_id, page.__class__)
self.obj_ids.append(obj_id)
self.obj_ids_limit -= 1
def to_binary(self): # type: () -> bytes
img = b''
for (obj_id, page_type) in self.obj_ids:
if page_type == SpiffsObjIndexPage:
obj_id ^= (1 << ((self.build_config.obj_id_len * 8) - 1))
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
SpiffsPage._len_dict[self.build_config.obj_id_len], obj_id)
assert len(img) <= self.build_config.page_size
img += b'\xFF' * (self.build_config.page_size - len(img))
return img
def magicfy(self, blocks_lim): # type: (int) -> None
# Only use magic value if no valid obj id has been written to the spot, which is the
# spot taken up by the last obj id on last lookup page. The parent is responsible
# for determining which is the last lookup page and calling this function.
remaining = self.obj_ids_limit
empty_obj_id_dict = {
1: 0xFF,
2: 0xFFFF,
4: 0xFFFFFFFF,
8: 0xFFFFFFFFFFFFFFFF
}
if remaining >= 2:
for i in range(remaining):
if i == remaining - 2:
self.obj_ids.append((self._calc_magic(blocks_lim), SpiffsObjDataPage))
break
else:
self.obj_ids.append((empty_obj_id_dict[self.build_config.obj_id_len], SpiffsObjDataPage))
self.obj_ids_limit -= 1
class SpiffsObjIndexPage(SpiffsObjPageWithIdx):
def __init__(self, obj_id, span_ix, size, name, build_config
): # type: (int, int, int, str, SpiffsBuildConfig) -> None
super(SpiffsObjIndexPage, self).__init__(obj_id, build_config)
self.span_ix = span_ix
self.name = name
self.size = size
if self.span_ix == 0:
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM
else:
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_LIM
self.pages = list() # type: typing.List[int]
def register_page(self, page): # type: (SpiffsObjDataPage) -> None
if not self.pages_lim > 0:
raise SpiffsFullError
self.pages.append(page.offset)
self.pages_lim -= 1
def to_binary(self): # type: () -> bytes
obj_id = self.obj_id ^ (1 << ((self.build_config.obj_id_len * 8) - 1))
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
SpiffsPage._len_dict[self.build_config.obj_id_len] +
SpiffsPage._len_dict[self.build_config.span_ix_len] +
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
obj_id,
self.span_ix,
SPIFFS_PH_FLAG_USED_FINAL_INDEX)
# Add padding before the object index page specific information
img += b'\xFF' * self.build_config.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD
# If this is the first object index page for the object, add filname, type
# and size information
if self.span_ix == 0:
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
SpiffsPage._len_dict[SPIFFS_PH_IX_SIZE_LEN] +
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
self.size,
SPIFFS_TYPE_FILE)
img += self.name.encode() + (b'\x00' * (
(self.build_config.obj_name_len - len(self.name))
+ self.build_config.meta_len
+ self.build_config.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD))
# Finally, add the page index of daa pages
for page in self.pages:
page = page >> int(math.log(self.build_config.page_size, 2))
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
SpiffsPage._len_dict[self.build_config.page_ix_len], page)
assert len(img) <= self.build_config.page_size
img += b'\xFF' * (self.build_config.page_size - len(img))
return img
class SpiffsObjDataPage(SpiffsObjPageWithIdx):
def __init__(self, offset, obj_id, span_ix, contents, build_config
): # type: (int, int, int, bytes, SpiffsBuildConfig) -> None
super(SpiffsObjDataPage, self).__init__(obj_id, build_config)
self.span_ix = span_ix
self.contents = contents
self.offset = offset
def to_binary(self): # type: () -> bytes
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
SpiffsPage._len_dict[self.build_config.obj_id_len] +
SpiffsPage._len_dict[self.build_config.span_ix_len] +
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
self.obj_id,
self.span_ix,
SPIFFS_PH_FLAG_USED_FINAL)
img += self.contents
assert len(img) <= self.build_config.page_size
img += b'\xFF' * (self.build_config.page_size - len(img))
return img
class SpiffsBlock(object):
def _reset(self): # type: () -> None
self.cur_obj_index_span_ix = 0
self.cur_obj_data_span_ix = 0
self.cur_obj_id = 0
self.cur_obj_idx_page = None # type: typing.Optional[SpiffsObjIndexPage]
def __init__(self, bix, build_config): # type: (int, SpiffsBuildConfig) -> None
self.build_config = build_config
self.offset = bix * self.build_config.block_size
self.remaining_pages = self.build_config.OBJ_USABLE_PAGES_PER_BLOCK
self.pages = list() # type: typing.List[SpiffsPage]
self.bix = bix
lu_pages = list()
for i in range(self.build_config.OBJ_LU_PAGES_PER_BLOCK):
page = SpiffsObjLuPage(self.bix, self.build_config)
lu_pages.append(page)
self.pages.extend(lu_pages)
self.lu_page_iter = iter(lu_pages)
self.lu_page = next(self.lu_page_iter)
self._reset()
def _register_page(self, page): # type: (TSP) -> None
if isinstance(page, SpiffsObjDataPage):
assert self.cur_obj_idx_page is not None
self.cur_obj_idx_page.register_page(page) # can raise SpiffsFullError
try:
self.lu_page.register_page(page)
except SpiffsFullError:
self.lu_page = next(self.lu_page_iter)
try:
self.lu_page.register_page(page)
except AttributeError: # no next lookup page
# Since the amount of lookup pages is pre-computed at every block instance,
# this should never occur
raise RuntimeError('invalid attempt to add page to a block when there is no more space in lookup')
self.pages.append(page)
def begin_obj(self, obj_id, size, name, obj_index_span_ix=0, obj_data_span_ix=0
): # type: (int, int, str, int, int) -> None
if not self.remaining_pages > 0:
raise SpiffsFullError()
self._reset()
self.cur_obj_id = obj_id
self.cur_obj_index_span_ix = obj_index_span_ix
self.cur_obj_data_span_ix = obj_data_span_ix
page = SpiffsObjIndexPage(obj_id, self.cur_obj_index_span_ix, size, name, self.build_config)
self._register_page(page)
self.cur_obj_idx_page = page
self.remaining_pages -= 1
self.cur_obj_index_span_ix += 1
def update_obj(self, contents): # type: (bytes) -> None
if not self.remaining_pages > 0:
raise SpiffsFullError()
page = SpiffsObjDataPage(self.offset + (len(self.pages) * self.build_config.page_size),
self.cur_obj_id, self.cur_obj_data_span_ix, contents, self.build_config)
self._register_page(page)
self.cur_obj_data_span_ix += 1
self.remaining_pages -= 1
def end_obj(self): # type: () -> None
self._reset()
def is_full(self): # type: () -> bool
return self.remaining_pages <= 0
def to_binary(self, blocks_lim): # type: (int) -> bytes
img = b''
if self.build_config.use_magic:
for (idx, page) in enumerate(self.pages):
if idx == self.build_config.OBJ_LU_PAGES_PER_BLOCK - 1:
assert isinstance(page, SpiffsObjLuPage)
page.magicfy(blocks_lim)
img += page.to_binary()
else:
for page in self.pages:
img += page.to_binary()
assert len(img) <= self.build_config.block_size
img += b'\xFF' * (self.build_config.block_size - len(img))
return img
class SpiffsFS(object):
def __init__(self, img_size, build_config): # type: (int, SpiffsBuildConfig) -> None
if img_size % build_config.block_size != 0:
raise RuntimeError('image size should be a multiple of block size')
self.img_size = img_size
self.build_config = build_config
self.blocks = list() # type: typing.List[SpiffsBlock]
self.blocks_lim = self.img_size // self.build_config.block_size
self.remaining_blocks = self.blocks_lim
self.cur_obj_id = 1 # starting object id
def _create_block(self): # type: () -> SpiffsBlock
if self.is_full():
raise SpiffsFullError('the image size has been exceeded')
block = SpiffsBlock(len(self.blocks), self.build_config)
self.blocks.append(block)
self.remaining_blocks -= 1
return block
def is_full(self): # type: () -> bool
return self.remaining_blocks <= 0
def create_file(self, img_path, file_path): # type: (str, str) -> None
if len(img_path) > self.build_config.obj_name_len:
raise RuntimeError("object name '%s' too long" % img_path)
name = img_path
with open(file_path, 'rb') as obj:
contents = obj.read()
stream = io.BytesIO(contents)
try:
block = self.blocks[-1]
block.begin_obj(self.cur_obj_id, len(contents), name)
except (IndexError, SpiffsFullError):
block = self._create_block()
block.begin_obj(self.cur_obj_id, len(contents), name)
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
while contents_chunk:
try:
block = self.blocks[-1]
try:
# This can fail because either (1) all the pages in block have been
# used or (2) object index has been exhausted.
block.update_obj(contents_chunk)
except SpiffsFullError:
# If its (1), use the outer exception handler
if block.is_full():
raise SpiffsFullError
# If its (2), write another object index page
block.begin_obj(self.cur_obj_id, len(contents), name,
obj_index_span_ix=block.cur_obj_index_span_ix,
obj_data_span_ix=block.cur_obj_data_span_ix)
continue
except (IndexError, SpiffsFullError):
# All pages in the block have been exhausted. Create a new block, copying
# the previous state of the block to a new one for the continuation of the
# current object
prev_block = block
block = self._create_block()
block.cur_obj_id = prev_block.cur_obj_id
block.cur_obj_idx_page = prev_block.cur_obj_idx_page
block.cur_obj_data_span_ix = prev_block.cur_obj_data_span_ix
block.cur_obj_index_span_ix = prev_block.cur_obj_index_span_ix
continue
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
block.end_obj()
self.cur_obj_id += 1
def to_binary(self): # type: () -> bytes
img = b''
all_blocks = []
for block in self.blocks:
all_blocks.append(block.to_binary(self.blocks_lim))
bix = len(self.blocks)
if self.build_config.use_magic:
# Create empty blocks with magic numbers
while self.remaining_blocks > 0:
block = SpiffsBlock(bix, self.build_config)
all_blocks.append(block.to_binary(self.blocks_lim))
self.remaining_blocks -= 1
bix += 1
else:
# Just fill remaining spaces FF's
all_blocks.append(b'\xFF' * (self.img_size - len(all_blocks) * self.build_config.block_size))
img += b''.join([blk for blk in all_blocks])
return img
class CustomHelpFormatter(argparse.HelpFormatter):
"""
Similar to argparse.ArgumentDefaultsHelpFormatter, except it
doesn't add the default value if "(default:" is already present.
This helps in the case of options with action="store_false", like
--no-magic or --no-magic-len.
"""
def _get_help_string(self, action): # type: (argparse.Action) -> str
if action.help is None:
return ''
if '%(default)' not in action.help and '(default:' not in action.help:
if action.default is not argparse.SUPPRESS:
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
return action.help + ' (default: %(default)s)'
return action.help
def main(): # type: () -> None
parser = argparse.ArgumentParser(description='SPIFFS Image Generator',
formatter_class=CustomHelpFormatter)
parser.add_argument('image_size',
help='Size of the created image')
parser.add_argument('base_dir',
help='Path to directory from which the image will be created')
parser.add_argument('output_file',
help='Created image output file path')
parser.add_argument('--page-size',
help='Logical page size. Set to value same as CONFIG_SPIFFS_PAGE_SIZE.',
type=int,
default=256)
parser.add_argument('--block-size',
help="Logical block size. Set to the same value as the flash chip's sector size (g_rom_flashchip.sector_size).",
type=int,
default=4096)
parser.add_argument('--obj-name-len',
help='File full path maximum length. Set to value same as CONFIG_SPIFFS_OBJ_NAME_LEN.',
type=int,
default=32)
parser.add_argument('--meta-len',
help='File metadata length. Set to value same as CONFIG_SPIFFS_META_LENGTH.',
type=int,
default=4)
parser.add_argument('--use-magic',
dest='use_magic',
help='Use magic number to create an identifiable SPIFFS image. Specify if CONFIG_SPIFFS_USE_MAGIC.',
action='store_true')
parser.add_argument('--no-magic',
dest='use_magic',
help='Inverse of --use-magic (default: --use-magic is enabled)',
action='store_false')
parser.add_argument('--use-magic-len',
dest='use_magic_len',
help='Use position in memory to create different magic numbers for each block. Specify if CONFIG_SPIFFS_USE_MAGIC_LENGTH.',
action='store_true')
parser.add_argument('--no-magic-len',
dest='use_magic_len',
help='Inverse of --use-magic-len (default: --use-magic-len is enabled)',
action='store_false')
parser.add_argument('--follow-symlinks',
help='Take into account symbolic links during partition image creation.',
action='store_true')
parser.add_argument('--big-endian',
help='Specify if the target architecture is big-endian. If not specified, little-endian is assumed.',
action='store_true')
parser.add_argument('--aligned-obj-ix-tables',
action='store_true',
help='Use aligned object index tables. Specify if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES is set.')
parser.set_defaults(use_magic=True, use_magic_len=True)
args = parser.parse_args()
if not os.path.exists(args.base_dir):
raise RuntimeError('given base directory %s does not exist' % args.base_dir)
with open(args.output_file, 'wb') as image_file:
image_size = int(args.image_size, 0)
spiffs_build_default = SpiffsBuildConfig(args.page_size, SPIFFS_PAGE_IX_LEN,
args.block_size, SPIFFS_BLOCK_IX_LEN, args.meta_len,
args.obj_name_len, SPIFFS_OBJ_ID_LEN, SPIFFS_SPAN_IX_LEN,
True, True, 'big' if args.big_endian else 'little',
args.use_magic, args.use_magic_len, args.aligned_obj_ix_tables)
spiffs = SpiffsFS(image_size, spiffs_build_default)
for root, dirs, files in os.walk(args.base_dir, followlinks=args.follow_symlinks):
for f in files:
full_path = os.path.join(root, f)
spiffs.create_file('/' + os.path.relpath(full_path, args.base_dir).replace('\\', '/'), full_path)
image = spiffs.to_binary()
image_file.write(image)
if __name__ == '__main__':
main()

View File

@@ -135,7 +135,7 @@ uint8_t boot_vector = 0x00;
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, pin_np, NEO_GRB + NEO_KHZ800);
uint8_t npr = 0;
uint8_t npr = 0;
uint8_t npg = 0;
uint8_t npb = 0;
float npi = NP_M;
@@ -340,6 +340,34 @@ uint8_t boot_vector = 0x00;
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_MESHADVENTURER
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_DIY_V1
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_AETHERNODE
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#endif
#elif MCU_VARIANT == MCU_NRF52
#if HAS_NP == true
@@ -356,7 +384,14 @@ uint8_t boot_vector = 0x00;
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_HELTEC_T114
#elif BOARD_MODEL == BOARD_PROMICRO
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_HELTEC_T114
// Heltec T114 pulls pins LOW to turn on
void led_rx_on() { digitalWrite(pin_led_rx, LOW); }
void led_rx_off() { digitalWrite(pin_led_rx, HIGH); }
@@ -1340,7 +1375,7 @@ void setTXPower() {
if (model == MODEL_12) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
if (model == MODEL_C6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
if (model == MODEL_C7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
if (model == MODEL_C7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
if (model == MODEL_A1) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
if (model == MODEL_A2) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
@@ -1653,6 +1688,16 @@ bool eeprom_model_valid() {
if (model == MODEL_FF) {
#elif BOARD_MODEL == BOARD_GENERIC_ESP32
if (model == MODEL_FF || model == MODEL_FE) {
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
if (model == MODEL_FF || model == MODEL_FE) {
#elif BOARD_MODEL == BOARD_MESHADVENTURER
if (model == MODEL_FF || model == MODEL_FE) {
#elif BOARD_MODEL == BOARD_DIY_V1
if (model == MODEL_FF || model == MODEL_FE) {
#elif BOARD_MODEL == BOARD_AETHERNODE
if (model == MODEL_FF || model == MODEL_FE) {
#elif BOARD_MODEL == BOARD_PROMICRO
if (model == MODEL_FF || model == MODEL_FE) {
#else
if (false) {
#endif

View File

@@ -4,4 +4,7 @@ board_manager:
- https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json
- https://github.com/HelTecAutomation/Heltec_nRF52/releases/download/1.7.0/package_heltec_nrf_index.json
- https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
- http://unsigned.io/arduino/package_unsignedio_UnsignedBoards_index.json
## proMicro
# - https://raw.githubusercontent.com/pdcook/nRFMicro-Arduino-Core/main/package_nRFMicro_index.json #can't use it bc the name has spaces, i created the file below
- https://gist.githubusercontent.com/gargomoma/b5cffc40e5df88462f2b488492feb6ca/raw/198c5706289014dd64be31607495e02f056d52a3/nrf_cli_fix.json
- https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

133
energysave.patch Normal file
View File

@@ -0,0 +1,133 @@
diff --git a/Display.h b/Display.h
index 7d903b9..882af8d 100644
--- a/Display.h
+++ b/Display.h
@@ -1071,6 +1071,7 @@ void update_display(bool blank = false) {
#if BOARD_MODEL == BOARD_HELTEC_T114
display.clear();
display.display();
+ digitalWrite(PIN_T114_TFT_BLGT, HIGH);
#elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO
display.clearDisplay();
display.display();
@@ -1128,6 +1129,9 @@ void update_display(bool blank = false) {
void display_unblank() {
last_unblank_event = millis();
+ #if BOARD_MODEL == BOARD_HELTEC_T114
+ digitalWrite(PIN_T114_TFT_BLGT, LOW);
+ #endif
}
void ext_fb_enable() {
diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino
index 5649206..ba157e6 100644
--- a/RNode_Firmware.ino
+++ b/RNode_Firmware.ino
@@ -535,7 +535,9 @@ bool startRadio() {
// Flash an info pattern to indicate
// that the radio is now on
kiss_indicate_radiostate();
- led_indicate_info(3);
+ if (!display_blanked) {
+ led_indicate_info(3);
+ }
return true;
}
@@ -575,7 +577,7 @@ volatile bool queue_flushing = false;
void flush_queue(void) {
if (!queue_flushing) {
queue_flushing = true;
- led_tx_on();
+ if (!display_blanked) { led_tx_on(); }
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
while (!fifo16_isempty(&packet_starts)) {
@@ -596,7 +598,7 @@ void flush_queue(void) {
}
}
- lora_receive(); led_tx_off();
+ lora_receive(); if (!display_blanked) { led_tx_off(); }
}
queue_height = 0;
@@ -615,7 +617,8 @@ void flush_queue(void) {
void pop_queue() {
if (!queue_flushing) {
- queue_flushing = true; led_tx_on();
+ queue_flushing = true;
+ if (!display_blanked) { led_tx_on(); }
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
if (!fifo16_isempty(&packet_starts)) {
@@ -637,7 +640,8 @@ void pop_queue() {
queued_bytes -= length;
}
- lora_receive(); led_tx_off();
+ lora_receive();
+ if (!display_blanked) { led_tx_off(); }
}
#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
@@ -751,7 +755,7 @@ void transmit(uint16_t size) {
add_airtime(written);
} else {
- led_tx_on(); uint16_t written = 0;
+ if (!display_blanked) { led_tx_on(); } uint16_t written = 0;
if (size > SINGLE_MTU) { size = SINGLE_MTU; }
if (!implicit) { LoRa->beginPacket(); }
else { LoRa->beginPacket(size); }
@@ -984,7 +988,7 @@ void serial_callback(uint8_t sbyte) {
} else if (command == CMD_RADIO_LOCK) {
update_radio_lock();
kiss_indicate_radio_lock();
- } else if (command == CMD_BLINK) {
+ } else if (command == CMD_BLINK && !display_blanked) {
led_indicate_info(sbyte);
} else if (command == CMD_RANDOM) {
kiss_indicate_random(getRandom());
@@ -1431,13 +1435,15 @@ void update_modem_status() {
if (carrier_detected) { dcd = true; } else { dcd = false; }
dcd_led = dcd;
- if (dcd_led) { led_rx_on(); }
+ if (!display_blanked && dcd_led) { led_rx_on(); }
else {
if (interference_detected) {
- if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled) { led_id_on(); }
+ if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled && !display_blanked) { led_id_on(); }
} else {
- if (airtime_lock) { led_indicate_airtime_lock(); }
- else { led_rx_off(); led_id_off(); }
+ if (airtime_lock && !display_blanked) { led_indicate_airtime_lock(); }
+ else {
+ if (!display_blanked) { led_rx_off(); led_id_off(); }
+ }
}
}
}
@@ -1707,7 +1713,9 @@ void loop() {
console_loop();
#endif
} else {
- led_indicate_standby();
+ if (!display_blanked) {
+ led_indicate_standby();
+ }
}
} else {
diff --git a/arduino-cli.yaml b/arduino-cli.yaml
index 6dd5f8d..d358070 100644
--- a/arduino-cli.yaml
+++ b/arduino-cli.yaml
@@ -4,4 +4,3 @@ board_manager:
- https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json
- https://github.com/HelTecAutomation/Heltec_nRF52/releases/download/1.7.0/package_heltec_nrf_index.json
- https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
- - http://unsigned.io/arduino/package_unsignedio_UnsignedBoards_index.json

View File

@@ -87,7 +87,7 @@
#define FREQ_DIV_6X (double)pow(2.0, 25.0)
#define FREQ_STEP_6X (double)(XTAL_FREQ_6X / FREQ_DIV_6X)
#if BOARD_MODEL == BOARD_TECHO
#if BOARD_MODEL == BOARD_TECHO || BOARD_MODEL == BOARD_PROMICRO
SPIClass spim3 = SPIClass(NRF_SPIM3, pin_miso, pin_sclk, pin_mosi) ;
#define SPI spim3
@@ -125,7 +125,7 @@ bool sx126x::preInit() {
pinMode(_ss, OUTPUT);
digitalWrite(_ss, HIGH);
#if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3
#if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_GENERIC_ESP32 || BOARD_MODEL == BOARD_MESHADVENTURER_S3 || BOARD_MODEL == BOARD_MESHADVENTURER || BOARD_MODEL == BOARD_DIY_V1 || BOARD_MODEL == BOARD_AETHERNODE
SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs);
#elif BOARD_MODEL == BOARD_TECHO
SPI.setPins(pin_miso, pin_sclk, pin_mosi);
@@ -285,6 +285,22 @@ void sx126x::reset(void) {
}
}
void sx126x::setDCDCRegulator(void) {
// Documentation
// 5. Power Distribution -> 5.1 Selecting DC-DC Converter or LDO Regulation
// 13.1.11 SetRegulatorMode
uint8_t mode_byte = MODE_STDBY_RC_6X;
executeOpcode(OP_STANDBY_6X, &mode_byte, 1);
// Enable DC-DC regulator for high power operation
uint8_t reg_mode = 0x01; // 0x00 = LDO, 0x01 = DC-DC
executeOpcode(OP_REGULATOR_MODE_6X, &reg_mode, 1);
delay(5);
waitOnBusy();
}
void sx126x::calibrate(void) {
// Put in STDBY_RC mode before calibration
uint8_t mode_byte = MODE_STDBY_RC_6X;
@@ -316,9 +332,19 @@ int sx126x::begin(long frequency) {
if (!_preinit_done) { if (!preInit()) { return false; } }
if (_rxen != -1) { pinMode(_rxen, OUTPUT); }
//TODO: if it works, make it optional
//#ifdef SX1262_USE_DCDC_REGULATOR
setDCDCRegulator();
//#endif
calibrate();
calibrate_image(frequency);
enableTCXO();
#if HAS_TCXO
enableTCXO();
//13.1.15 SetRxTxFallbackMode to STDBY_XOSC
uint8_t fallback_mode = 0x30; // STDBY_XOSC after TX/RX
executeOpcode(OP_RX_TX_FALLBACK_MODE_6X, &fallback_mode, 1);
#endif
loraMode();
standby();
@@ -400,6 +426,9 @@ int sx126x::beginPacket(int implicitHeader) {
int sx126x::endPacket() {
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
if (_rxen != -1) { digitalWrite(_rxen, LOW); } //Set RXen low when transmitting
uint8_t timeout[3] = {0}; // Put in single TX mode
executeOpcode(OP_TX_6X, timeout, 3);
@@ -642,6 +671,18 @@ void sx126x::enableTCXO() {
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_HELTEC32_V4
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_GENERIC_ESP32
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_MESHADVENTURER
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_DIY_V1
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_AETHERNODE
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_PROMICRO
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#endif
executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4);
#endif

View File

@@ -74,6 +74,7 @@ public:
void disableCrc();
void enableTCXO();
void disableTCXO();
void setDCDCRegulator();
void rxAntEnable();
void loraMode();