diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e34d8b..ceecfec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,9 @@ on: pull_request: branches: [main] +env: + ARDUINO_CLI_VERSION: 1.4.1 + jobs: build: runs-on: ubuntu-latest @@ -57,8 +60,40 @@ jobs: MANIFEST cd ratdeck-firmware && zip -r ../ratdeck-firmware.zip . && cd .. + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Install Arduino CLI + run: | + mkdir -p "$RUNNER_TEMP/arduino-cli" + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \ + | BINDIR="$RUNNER_TEMP/arduino-cli" sh -s "$ARDUINO_CLI_VERSION" + echo "$RUNNER_TEMP/arduino-cli" >> "$GITHUB_PATH" + + - name: Install Python tools + run: pip install platformio esptool + + - name: Prepare RNode Arduino dependencies + run: make prep-tdeck + + - name: Build dual-boot package + run: make package + + - name: Upload firmware artifacts + uses: actions/upload-artifact@v7 + with: + name: rsdeck-dist + path: dist/* + release: - needs: build + needs: [build, package] if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: @@ -110,6 +145,19 @@ jobs: MANIFEST cd ratdeck-firmware && zip -r ../ratdeck-firmware.zip . && cd .. + - name: Install Arduino CLI + run: | + mkdir -p "$RUNNER_TEMP/arduino-cli" + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \ + | BINDIR="$RUNNER_TEMP/arduino-cli" sh -s "$ARDUINO_CLI_VERSION" + echo "$RUNNER_TEMP/arduino-cli" >> "$GITHUB_PATH" + + - name: Prepare RNode Arduino dependencies + run: make prep-tdeck + + - name: Build dual-boot package + run: make package + - name: Create Release env: GH_TOKEN: ${{ github.token }} @@ -119,8 +167,9 @@ jobs: release_args+=(--prerelease) fi + assets=(ratdeck-firmware.zip ratdeck-merged.bin dist/*) if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then - gh release upload "$GITHUB_REF_NAME" ratdeck-firmware.zip ratdeck-merged.bin --clobber + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber else - gh release create "$GITHUB_REF_NAME" ratdeck-firmware.zip ratdeck-merged.bin "${release_args[@]}" + gh release create "$GITHUB_REF_NAME" "${assets[@]}" "${release_args[@]}" fi diff --git a/.gitignore b/.gitignore index 3dc6376..06c4216 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,10 @@ .vscode/launch.json .vscode/ipch build/ +dist/ *.bin *.elf +*.map __pycache__ *.pyc .DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1ef1bf0 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +STANDALONE_ENV ?= ratdeck_915 +RNODE_DIR ?= vendor/rnode_firmware +LAUNCHER_DIR ?= launcher +BUILD_DIR ?= build +DIST_DIR ?= dist + +PARTITION_CSV := partitions/rsdeck_16mb_dual.csv +PARTITIONS_BIN := $(BUILD_DIR)/rsdeck_16mb_dual_partitions.bin + +FULL_NAME := rsdeck-full +STANDALONE_NAME := rsdeck-standalone +RNODE_ONLY_NAME := rsdeck-rnode +APP_STANDALONE_NAME := rsdeck-standalone-app +APP_RNODE_NAME := rsdeck-rnode-app + +FULL_BIN := $(BUILD_DIR)/$(FULL_NAME).bin +STANDALONE_BIN := $(BUILD_DIR)/$(STANDALONE_NAME).bin +RNODE_ONLY_BIN := $(BUILD_DIR)/$(RNODE_ONLY_NAME).bin + +LAUNCHER_BIN := $(LAUNCHER_DIR)/.pio/build/tdeck_launcher/firmware.bin +STANDALONE_APP_BIN := .pio/build/$(STANDALONE_ENV)/firmware.bin +STANDALONE_FACTORY_BIN := ratdeck-merged.bin + +RNODE_OUTPUT := $(RNODE_DIR)/build/tdeck.esp32.esp32s3 +RNODE_BIN := $(RNODE_OUTPUT)/RNode_Firmware.ino.bin +RNODE_BOOTLOADER_BIN := $(RNODE_OUTPUT)/RNode_Firmware.ino.bootloader.bin +RNODE_PARTITIONS_BIN := $(RNODE_OUTPUT)/RNode_Firmware.ino.partitions.bin + +BOOTLOADER_BIN := .pio/build/$(STANDALONE_ENV)/bootloader.bin +PLATFORMIO_ARDUINO ?= $(HOME)/.platformio/packages/framework-arduinoespressif32 +ARDUINO15_ESP32 ?= $(HOME)/Library/Arduino15/packages/esp32/hardware/esp32/2.0.17 +GEN_ESPPART ?= $(if $(wildcard $(PLATFORMIO_ARDUINO)/tools/gen_esp32part.py),$(PLATFORMIO_ARDUINO)/tools/gen_esp32part.py,$(ARDUINO15_ESP32)/tools/gen_esp32part.py) +BOOT_APP0_BIN ?= $(if $(wildcard $(PLATFORMIO_ARDUINO)/tools/partitions/boot_app0.bin),$(PLATFORMIO_ARDUINO)/tools/partitions/boot_app0.bin,$(ARDUINO15_ESP32)/tools/partitions/boot_app0.bin) + +PORT ?= $(port) +ifeq ($(PORT),) +PORT := /dev/ttyACM0 +endif + +.PHONY: all build prep-tdeck build-standalone build-launcher build-rnode check bundle full-image standalone-image rnode-only-image package release flash clean + +all: bundle + +build: package + +prep-tdeck: + $(MAKE) -C $(RNODE_DIR) prep-esp32 + +build-standalone: + python3 -m platformio run -e $(STANDALONE_ENV) + +build-launcher: + python3 -m platformio run -d $(LAUNCHER_DIR) -e tdeck_launcher + +build-rnode: + $(MAKE) -C $(RNODE_DIR) firmware-tdeck + +$(PARTITIONS_BIN): $(PARTITION_CSV) + mkdir -p $(BUILD_DIR) + python3 $(GEN_ESPPART) $(PARTITION_CSV) $(PARTITIONS_BIN) + +check: build-launcher build-standalone build-rnode + python3 tools/check_image_fit.py --launcher $(LAUNCHER_BIN) --standalone $(STANDALONE_APP_BIN) --rnode $(RNODE_BIN) + +full-image: check $(PARTITIONS_BIN) + python3 tools/make_dual_image.py \ + --bootloader $(BOOTLOADER_BIN) \ + --partitions $(PARTITIONS_BIN) \ + --boot-app0 $(BOOT_APP0_BIN) \ + --launcher $(LAUNCHER_BIN) \ + --standalone $(STANDALONE_APP_BIN) \ + --rnode $(RNODE_BIN) \ + --output $(FULL_BIN) + +standalone-image: build-standalone + mkdir -p $(BUILD_DIR) + cp $(STANDALONE_FACTORY_BIN) $(STANDALONE_BIN) + +rnode-only-image: build-rnode + mkdir -p $(BUILD_DIR) + python3 -m esptool --chip esp32s3 merge-bin \ + --flash-mode qio --flash-size 16MB \ + --output $(RNODE_ONLY_BIN) \ + 0x0000 $(RNODE_BOOTLOADER_BIN) \ + 0x8000 $(RNODE_PARTITIONS_BIN) \ + 0xe000 $(BOOT_APP0_BIN) \ + 0x10000 $(RNODE_BIN) + +bundle: full-image + +package: full-image standalone-image rnode-only-image + mkdir -p $(DIST_DIR) + python3 tools/package_merged_zip.py --image $(FULL_BIN) --name $(FULL_NAME) --output $(DIST_DIR)/$(FULL_NAME).zip + python3 tools/package_merged_zip.py --image $(STANDALONE_BIN) --name $(STANDALONE_NAME) --output $(DIST_DIR)/$(STANDALONE_NAME).zip + python3 tools/package_merged_zip.py --image $(RNODE_ONLY_BIN) --name $(RNODE_ONLY_NAME) --output $(DIST_DIR)/$(RNODE_ONLY_NAME).zip + cp $(STANDALONE_APP_BIN) $(DIST_DIR)/$(APP_STANDALONE_NAME).bin + cp $(RNODE_BIN) $(DIST_DIR)/$(APP_RNODE_NAME).bin + +release: package + +flash: bundle + python3 -m esptool --chip esp32s3 --port $(PORT) --baud 460800 --before default_reset --after hard_reset write-flash 0x0 $(FULL_BIN) + +clean: + rm -rf $(BUILD_DIR) $(DIST_DIR) diff --git a/docs/DUAL-BOOT.md b/docs/DUAL-BOOT.md new file mode 100644 index 0000000..ef23703 --- /dev/null +++ b/docs/DUAL-BOOT.md @@ -0,0 +1,52 @@ +# rsDeck Dual-Boot + +The dual-boot layout lets one flash carry three apps on the T-Deck Plus's 16MB +flash, with a boot-time chooser: + +| Partition | Slot | Offset | Size | Contents | +|------------|-------|----------|---------|----------| +| launcher | ota_0 | 0x10000 | 1MB | Boot chooser UI | +| standalone | ota_1 | 0x110000 | 4MB | Full Ratdeck messenger | +| rnode | ota_2 | 0x510000 | 3MB | RNode (KISS over USB CDC + BLE) | +| littlefs | — | 0x810000 | ~7.9MB | User data (unchanged from single-app layout) | + +The littlefs and coredump regions are byte-identical to the legacy +single-app layout (`partitions_16MB.csv`), so migrating to the dual image +preserves identity, messages, contacts, and settings. + +## Boot flow + +- The ESP32 ROM bootloader reads `otadata` and starts the selected slot. +- The launcher (ota_0) shows Standalone/RNode, remembers the last choice in + NVS (`rslaunch/last`), auto-boots it after 7 seconds, and writes the choice + via `esp_ota_set_boot_partition()`. +- Both Standalone and RNode re-arm the launcher at every boot + (`rs_deck::returnToLauncherNextBoot()`), so any reset returns to the chooser. + On the legacy single-app layout that call self-selects ota_0 and is a no-op. + +## Launcher controls + +Trackball up/down + click, or keys: `W`/`S` select, `Enter` boot, +`R` = Standalone now, `N` = RNode now. Any input cancels auto-boot. + +## RNode mode + +The vendored RNode firmware (`vendor/rnode_firmware`, `make firmware-tdeck`) +self-provisions EEPROM on first boot (PRODUCT_TDECK_V1 / MODEL_D9, 915 MHz +defaults) and enables BLE pairing on first run when no bonds exist. The T-Deck +RNode build is headless — the screen stays off; reset to return to the +launcher. + +## Build + +```bash +make prep-tdeck # once: arduino-cli esp32 core + libs +make package # dist/: rsdeck-full.zip, rsdeck-standalone.zip, + # rsdeck-rnode.zip, rsdeck-*-app.bin +make flash port=/dev/cu.usbmodemXXXX # flash full image +``` + +`rsdeck-full.bin` flashes at offset 0x0 and contains all three apps. +`rsdeck-standalone.zip` repackages the legacy single-app `ratdeck-merged.bin` +(old partition table, no launcher). The `*-app.bin` files are bare app images +for external launchers or manual OTA-slot flashing. diff --git a/launcher/platformio.ini b/launcher/platformio.ini new file mode 100644 index 0000000..cf69d0c --- /dev/null +++ b/launcher/platformio.ini @@ -0,0 +1,25 @@ +[env:tdeck_launcher] +platform = espressif32@6.7.0 +board = esp32-s3-devkitc-1 +framework = arduino + +board_build.flash_size = 16MB +board_build.flash_mode = qio +board_build.partitions = ../partitions/rsdeck_16mb_dual.csv +board_upload.flash_size = 16MB + +build_flags = + -std=gnu++17 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 + -I../src/platform + +build_unflags = + -std=gnu++11 + +lib_deps = + lovyan03/LovyanGFX@~1.1.16 + +monitor_speed = 115200 +upload_speed = 460800 +upload_protocol = esptool diff --git a/launcher/src/main.cpp b/launcher/src/main.cpp new file mode 100644 index 0000000..f33ea68 --- /dev/null +++ b/launcher/src/main.cpp @@ -0,0 +1,377 @@ +// rsDeck boot launcher — picks Standalone or RNode at power-on. +// Runs from ota_0; writes the choice to otadata and restarts. Both target +// firmwares re-arm this launcher on boot, so any reset returns here. + +#include +#include +#include +#include + +#include "RsDeckModeSwitch.h" + +namespace { + +// Pins — keep in sync with src/config/BoardConfig.h +constexpr int kPowerPin = 10; +constexpr int kSpiSck = 40; +constexpr int kSpiMiso = 38; +constexpr int kSpiMosi = 41; +constexpr int kTftCs = 12; +constexpr int kTftDc = 11; +constexpr int kTftBl = 42; +constexpr int kLoraCs = 9; +constexpr int kSdCs = 39; +constexpr int kI2cSda = 18; +constexpr int kI2cScl = 8; +constexpr uint8_t kKbAddr = 0x55; +constexpr int kTballUp = 3; +constexpr int kTballDown = 2; +constexpr int kTballClick = 0; + +constexpr uint16_t kBg = 0x0841; +constexpr uint16_t kPanel = 0x1082; +constexpr uint16_t kText = 0xF7BE; +constexpr uint16_t kMuted = 0x8C71; +constexpr uint16_t kAccent = 0x06D7; +constexpr uint16_t kWarn = 0xFBA0; +constexpr uint32_t kAutoBootMs = 7000; +constexpr char kPrefsNamespace[] = "rslaunch"; +constexpr char kLastChoiceKey[] = "last"; + +class LGFX_TDeckLauncher : public lgfx::LGFX_Device { + lgfx::Panel_ST7789 _panel; + lgfx::Bus_SPI _bus; + lgfx::Light_PWM _light; + +public: + LGFX_TDeckLauncher() { + auto cfg_bus = _bus.config(); + cfg_bus.spi_host = SPI2_HOST; + cfg_bus.spi_mode = 0; + cfg_bus.freq_write = 27000000; + cfg_bus.freq_read = 16000000; + cfg_bus.pin_sclk = kSpiSck; + cfg_bus.pin_miso = kSpiMiso; + cfg_bus.pin_mosi = kSpiMosi; + cfg_bus.pin_dc = kTftDc; + _bus.config(cfg_bus); + _panel.setBus(&_bus); + + auto cfg_panel = _panel.config(); + cfg_panel.pin_cs = kTftCs; + cfg_panel.pin_rst = -1; + cfg_panel.panel_width = 240; + cfg_panel.panel_height = 320; + cfg_panel.invert = true; + cfg_panel.rgb_order = false; + cfg_panel.memory_width = 240; + cfg_panel.memory_height = 320; + _panel.config(cfg_panel); + + auto cfg_light = _light.config(); + cfg_light.pin_bl = kTftBl; + cfg_light.invert = false; + cfg_light.freq = 12000; + cfg_light.pwm_channel = 0; + _light.config(cfg_light); + _panel.setLight(&_light); + + setPanel(&_panel); + } +}; + +LGFX_TDeckLauncher display; + +enum class Choice : uint8_t { + Standalone = 0, + RNode = 1, +}; + +Choice selected = Choice::Standalone; +uint32_t bootStarted = 0; +uint32_t lastRemain = UINT32_MAX; +bool booting = false; +bool autoBootEnabled = true; + +bool tballUpLast = true; +bool tballDownLast = true; +bool tballClickLast = true; +uint32_t lastTballEdge = 0; +uint32_t lastClickEdge = 0; + +uint8_t choiceValue(Choice choice) { + return choice == Choice::RNode ? 1 : 0; +} + +Choice choiceFromValue(uint8_t value) { + return value == 1 ? Choice::RNode : Choice::Standalone; +} + +Choice loadLastChoice() { + Preferences prefs; + Choice choice = Choice::Standalone; + if (prefs.begin(kPrefsNamespace, true)) { + choice = choiceFromValue(prefs.getUChar(kLastChoiceKey, choiceValue(choice))); + prefs.end(); + } + return choice; +} + +void saveLastChoice(Choice choice) { + Preferences prefs; + if (prefs.begin(kPrefsNamespace, false)) { + prefs.putUChar(kLastChoiceKey, choiceValue(choice)); + prefs.end(); + } +} + +void drawOption(int y, const char *title, const char *subtitle, bool active) { + uint16_t fill = active ? kAccent : kPanel; + uint16_t fg = active ? TFT_BLACK : kText; + uint16_t sub = active ? 0x2104 : kMuted; + + display.fillRoundRect(24, y, 272, 52, 6, fill); + display.setTextColor(fg, fill); + display.setTextSize(2); + display.setCursor(40, y + 9); + display.print(title); + display.setTextColor(sub, fill); + display.setTextSize(1); + display.setCursor(40, y + 32); + display.print(subtitle); +} + +uint32_t remainingSeconds() { + uint32_t elapsed = millis() - bootStarted; + if (elapsed >= kAutoBootMs) { + return 0; + } + return (kAutoBootMs - elapsed + 999) / 1000; +} + +void drawCountdown(bool force = false) { + if (!autoBootEnabled) { + display.fillRect(280, 8, 34, 24, kBg); + lastRemain = UINT32_MAX; + return; + } + + uint32_t remain = remainingSeconds(); + if (!force && remain == lastRemain) { + return; + } + lastRemain = remain; + + display.fillRect(280, 8, 34, 24, kBg); + display.fillRoundRect(286, 9, 26, 22, 6, kPanel); + display.setTextSize(2); + display.setTextColor(kText, kPanel); + display.setCursor(remain >= 10 ? 289 : 294, 13); + display.print(static_cast(remain)); +} + +void drawScreen() { + display.fillScreen(kBg); + + display.setTextSize(3); + display.setTextColor(kText, kBg); + display.setCursor(24, 14); + display.print("rsDeck"); + + display.setTextSize(1); + display.setTextColor(kMuted, kBg); + display.setCursor(26, 44); + display.print("T-Deck Plus"); + + drawOption(66, "Standalone", "On-device messenger", selected == Choice::Standalone); + drawOption(126, "RNode", "BLE / USB radio", selected == Choice::RNode); + + display.setTextSize(1); + display.setTextColor(kMuted, kBg); + display.setCursor(24, 196); + display.print("Trackball: move + click Keys: W/S, Enter"); + display.setCursor(24, 212); + display.print("R = Standalone now N = RNode now"); + + drawCountdown(true); +} + +void selectChoice(Choice choice) { + if (selected == choice) { + return; + } + selected = choice; + drawScreen(); +} + +void pauseAutoBoot() { + if (!autoBootEnabled) { + return; + } + autoBootEnabled = false; + drawCountdown(true); +} + +void showBooting(const char *label, bool rnode) { + booting = true; + display.fillScreen(kBg); + display.setTextSize(3); + display.setTextColor(kAccent, kBg); + display.setCursor(28, 70); + display.print(label); + display.setTextSize(1); + display.setTextColor(kMuted, kBg); + display.setCursor(30, 110); + display.print("Starting..."); + if (rnode) { + display.setCursor(30, 134); + display.print("Screen stays off in RNode mode."); + display.setCursor(30, 148); + display.print("Reset the device to return here."); + } +} + +void showError(const char *message) { + booting = false; + display.fillScreen(kBg); + display.setTextSize(2); + display.setTextColor(kWarn, kBg); + display.setCursor(24, 60); + display.print("Boot error"); + display.setTextSize(1); + display.setTextColor(kText, kBg); + display.setCursor(24, 96); + display.print(message); +} + +void startChoice(Choice choice) { + using namespace rs_deck; + + FirmwareMode mode = choice == Choice::Standalone ? FirmwareMode::Standalone : FirmwareMode::RNode; + showBooting(mode_name(mode), mode == FirmwareMode::RNode); + SwitchResult result = set_next_boot(mode); + if (!result.ok) { + showError(result.message); + return; + } + saveLastChoice(choice); + delay(mode == FirmwareMode::RNode ? 1500 : 50); + esp_restart(); +} + +void handleKey(char key) { + pauseAutoBoot(); + + if (key == '\r' || key == '\n') { + startChoice(selected); + return; + } + if (key == 'w' || key == 'W' || key == ';') { + selectChoice(Choice::Standalone); + return; + } + if (key == 's' || key == 'S' || key == '.') { + selectChoice(Choice::RNode); + return; + } + if (key == 'r' || key == 'R') { + startChoice(Choice::Standalone); + return; + } + if (key == 'n' || key == 'N') { + startChoice(Choice::RNode); + return; + } +} + +char pollKeyboard() { + Wire.requestFrom(kKbAddr, (uint8_t)1); + if (Wire.available()) { + char key = (char)Wire.read(); + if (key != 0) { + return key; + } + } + return 0; +} + +void pollTrackball() { + uint32_t now = millis(); + + bool up = digitalRead(kTballUp); + bool down = digitalRead(kTballDown); + bool click = digitalRead(kTballClick); + + if (!up && tballUpLast && now - lastTballEdge > 5) { + lastTballEdge = now; + pauseAutoBoot(); + selectChoice(Choice::Standalone); + } + if (!down && tballDownLast && now - lastTballEdge > 5) { + lastTballEdge = now; + pauseAutoBoot(); + selectChoice(Choice::RNode); + } + if (!click && tballClickLast && now - lastClickEdge > 50) { + lastClickEdge = now; + pauseAutoBoot(); + startChoice(selected); + } + + tballUpLast = up; + tballDownLast = down; + tballClickLast = click; +} + +} // namespace + +void setup() { + // Peripheral power must come up before anything touches the display or I2C. + pinMode(kPowerPin, OUTPUT); + digitalWrite(kPowerPin, HIGH); + delay(50); + + // Park the other chip selects on the shared SPI bus. + pinMode(kLoraCs, OUTPUT); + digitalWrite(kLoraCs, HIGH); + pinMode(kSdCs, OUTPUT); + digitalWrite(kSdCs, HIGH); + + Serial.begin(115200); + + Wire.begin(kI2cSda, kI2cScl, 400000); + + pinMode(kTballUp, INPUT_PULLUP); + pinMode(kTballDown, INPUT_PULLUP); + pinMode(kTballClick, INPUT_PULLUP); + + display.init(); + display.setRotation(1); + display.setBrightness(200); + + selected = loadLastChoice(); + bootStarted = millis(); + drawScreen(); +} + +void loop() { + if (booting) { + delay(20); + return; + } + + char key = pollKeyboard(); + if (key != 0) { + handleKey(key); + } + + pollTrackball(); + drawCountdown(); + + if (autoBootEnabled && millis() - bootStarted >= kAutoBootMs) { + startChoice(selected); + return; + } + + delay(10); +} diff --git a/partitions/rsdeck_16mb_dual.csv b/partitions/rsdeck_16mb_dual.csv new file mode 100644 index 0000000..145cb6b --- /dev/null +++ b/partitions/rsdeck_16mb_dual.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +launcher, app, ota_0, 0x10000, 0x100000, +standalone,app, ota_1, 0x110000, 0x400000, +rnode, app, ota_2, 0x510000, 0x300000, +littlefs, data, spiffs, 0x810000, 0x7E0000, +coredump, data, coredump,0xFF0000, 0x10000, diff --git a/src/main.cpp b/src/main.cpp index b7a8631..1bfdf97 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "config/BoardConfig.h" #include "config/Config.h" +#include "platform/RsDeckModeSwitch.h" #include "hal/Display.h" #include "hal/TouchInput.h" #include "hal/Trackball.h" @@ -1073,6 +1074,12 @@ void setup() { Serial.printf("[BOOT] Reset: %s (%d)\n", reasonStr, (int)reason); Serial.printf("[BOOT] Heap: %lu PSRAM: %lu\n", (unsigned long)ESP.getFreeHeap(), (unsigned long)ESP.getPsramSize()); + + // Dual-boot layout: re-arm the launcher so the next reset shows the chooser. + auto launcherBoot = rs_deck::returnToLauncherNextBoot(); + if (!launcherBoot.ok) { + Serial.printf("[BOOT] Launcher return unavailable: %s\n", launcherBoot.message); + } if (!psramFound() || heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) < 1024 * 1024) { Serial.printf("[BOOT] FATAL: PSRAM unavailable or too fragmented (largest=%lu)\n", (unsigned long)heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)); diff --git a/src/platform/RsDeckModeSwitch.h b/src/platform/RsDeckModeSwitch.h new file mode 100644 index 0000000..fc29f8e --- /dev/null +++ b/src/platform/RsDeckModeSwitch.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include "esp_ota_ops.h" +#include "esp_partition.h" + +// Boot-mode selection for the rsDeck dual-boot layout (launcher/standalone/rnode +// in ota_0/1/2). On the legacy single-app layout ota_0 is the running standalone, +// so re-arming the launcher is a harmless self-select. +namespace rs_deck { + +enum class FirmwareMode : uint8_t { + Launcher, + Standalone, + RNode, +}; + +struct SwitchResult { + bool ok; + const char* message; +}; + +inline esp_partition_subtype_t modeSubtype(FirmwareMode mode) { + switch (mode) { + case FirmwareMode::Launcher: + return ESP_PARTITION_SUBTYPE_APP_OTA_0; + case FirmwareMode::Standalone: + return ESP_PARTITION_SUBTYPE_APP_OTA_1; + case FirmwareMode::RNode: + return ESP_PARTITION_SUBTYPE_APP_OTA_2; + } + return ESP_PARTITION_SUBTYPE_APP_OTA_0; +} + +inline const esp_partition_t* modePartition(FirmwareMode mode) { + return esp_partition_find_first(ESP_PARTITION_TYPE_APP, modeSubtype(mode), nullptr); +} + +inline const char* modeName(FirmwareMode mode) { + switch (mode) { + case FirmwareMode::Launcher: + return "Launcher"; + case FirmwareMode::Standalone: + return "Standalone"; + case FirmwareMode::RNode: + return "RNode"; + } + return "Unknown"; +} + +inline SwitchResult setNextBoot(FirmwareMode mode) { + const esp_partition_t* target = modePartition(mode); + if (!target) { + return {false, "target partition not found"}; + } + + esp_err_t err = esp_ota_set_boot_partition(target); + if (err != ESP_OK) { + return {false, esp_err_to_name(err)}; + } + + return {true, "boot partition updated"}; +} + +inline SwitchResult returnToLauncherNextBoot() { + return setNextBoot(FirmwareMode::Launcher); +} + +// Snake_case aliases for the launcher and RNode helper. +inline esp_partition_subtype_t mode_subtype(FirmwareMode mode) { return modeSubtype(mode); } +inline const esp_partition_t* mode_partition(FirmwareMode mode) { return modePartition(mode); } +inline const char* mode_name(FirmwareMode mode) { return modeName(mode); } +inline SwitchResult set_next_boot(FirmwareMode mode) { return setNextBoot(mode); } +inline SwitchResult return_to_launcher_next_boot() { return returnToLauncherNextBoot(); } + +} // namespace rs_deck diff --git a/tools/check_image_fit.py b/tools/check_image_fit.py new file mode 100644 index 0000000..521e14d --- /dev/null +++ b/tools/check_image_fit.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Check that launcher, Standalone, and RNode app images fit their OTA slots.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + + +def parse_int(value: str) -> int: + return int(value, 0) + + +def image_size(path: Path) -> int: + if not path.exists(): + raise FileNotFoundError(path) + return path.stat().st_size + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--launcher", required=True, type=Path) + parser.add_argument("--launcher-slot-size", default="0x100000", type=parse_int) + parser.add_argument("--standalone", required=True, type=Path) + parser.add_argument("--standalone-slot-size", default="0x400000", type=parse_int) + parser.add_argument("--rnode", required=True, type=Path) + parser.add_argument("--rnode-slot-size", default="0x300000", type=parse_int) + args = parser.parse_args() + + failed = False + checks = ( + ("Launcher", args.launcher, args.launcher_slot_size), + ("Standalone", args.standalone, args.standalone_slot_size), + ("RNode", args.rnode, args.rnode_slot_size), + ) + + for name, path, slot_size in checks: + size = image_size(path) + margin = slot_size - size + print(f"{name}: {size} bytes, slot {slot_size} bytes, margin {margin} bytes") + if margin < 0: + failed = True + + if failed: + print("error: at least one image is too large for its OTA slot") + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/make_dual_image.py b/tools/make_dual_image.py new file mode 100644 index 0000000..c23677f --- /dev/null +++ b/tools/make_dual_image.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Build a merged rsDeck image with launcher, Standalone, and RNode.""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from pathlib import Path + + +LAUNCHER_OFFSET = "0x10000" +STANDALONE_OFFSET = "0x110000" +RNODE_OFFSET = "0x510000" + + +def parse_int(value: str) -> int: + return int(value, 0) + + +def require_file(path: Path, label: str) -> None: + if not path.exists(): + raise FileNotFoundError(f"{label} not found: {path}") + if not path.is_file(): + raise FileNotFoundError(f"{label} is not a file: {path}") + + +def check_slot(path: Path, label: str, slot_size: int) -> None: + size = path.stat().st_size + if size > slot_size: + raise ValueError(f"{label} image is {size} bytes, exceeds slot size {slot_size}") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--bootloader", required=True, type=Path) + parser.add_argument("--partitions", required=True, type=Path) + parser.add_argument("--boot-app0", required=True, type=Path) + parser.add_argument("--launcher", required=True, type=Path) + parser.add_argument("--standalone", required=True, type=Path) + parser.add_argument("--rnode", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--launcher-slot-size", default="0x100000", type=parse_int) + parser.add_argument("--standalone-slot-size", default="0x400000", type=parse_int) + parser.add_argument("--rnode-slot-size", default="0x300000", type=parse_int) + args = parser.parse_args() + + for label, path in ( + ("bootloader", args.bootloader), + ("partition table", args.partitions), + ("boot_app0", args.boot_app0), + ("launcher app", args.launcher), + ("Standalone app", args.standalone), + ("RNode app", args.rnode), + ): + require_file(path, label) + + check_slot(args.launcher, "Launcher", args.launcher_slot_size) + check_slot(args.standalone, "Standalone", args.standalone_slot_size) + check_slot(args.rnode, "RNode", args.rnode_slot_size) + args.output.parent.mkdir(parents=True, exist_ok=True) + + cmd = [ + sys.executable, + "-m", + "esptool", + "--chip", + "esp32s3", + "merge-bin", + "--flash-mode", + "qio", + "--flash-size", + "16MB", + "--output", + str(args.output), + "0x0000", + str(args.bootloader), + "0x8000", + str(args.partitions), + "0xe000", + str(args.boot_app0), + LAUNCHER_OFFSET, + str(args.launcher), + STANDALONE_OFFSET, + str(args.standalone), + RNODE_OFFSET, + str(args.rnode), + ] + subprocess.run(cmd, check=True) + print(f"dual firmware image written to {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/package_merged_zip.py b/tools/package_merged_zip.py new file mode 100644 index 0000000..ee8cac9 --- /dev/null +++ b/tools/package_merged_zip.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Package a merged ESP32-S3 image with a web-flasher manifest.""" + +from __future__ import annotations + +import argparse +import json +import zipfile +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--image", required=True, type=Path) + parser.add_argument("--name", required=True) + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args() + + if not args.image.is_file(): + raise FileNotFoundError(args.image) + + image_name = f"{args.name}.bin" + manifest = { + "chipFamily": "ESP32-S3", + "flashSize": "16MB", + "flashMode": "qio", + "flashFreq": "80m", + "parts": [ + {"path": image_name, "offset": "0x0000"}, + ], + } + + args.output.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(args.output, "w", compression=zipfile.ZIP_DEFLATED) as zf: + zf.write(args.image, image_name) + zf.writestr("manifest.json", json.dumps(manifest, indent=2) + "\n") + + print(f"firmware package written to {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/vendor/rnode_firmware/.gitignore b/vendor/rnode_firmware/.gitignore new file mode 100644 index 0000000..c585a06 --- /dev/null +++ b/vendor/rnode_firmware/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +*.hex +*.pyc +TODO +Release/*.hex +Release/*.zip +Release/*.json +Console/build +build/* diff --git a/vendor/rnode_firmware/BLESerial.cpp b/vendor/rnode_firmware/BLESerial.cpp new file mode 100644 index 0000000..64fedc3 --- /dev/null +++ b/vendor/rnode_firmware/BLESerial.cpp @@ -0,0 +1,176 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Boards.h" + +#if PLATFORM != PLATFORM_NRF52 +#if HAS_BLE + +#include "BLESerial.h" + +uint32_t bt_passkey_callback(); +void bt_passkey_notify_callback(uint32_t passkey); +bool bt_security_request_callback(); +void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result); +bool bt_confirm_pin_callback(uint32_t pin); +void bt_connect_callback(BLEServer *server); +void bt_disconnect_callback(BLEServer *server); +bool bt_client_authenticated(); + +uint32_t BLESerial::onPassKeyRequest() { return bt_passkey_callback(); } +void BLESerial::onPassKeyNotify(uint32_t passkey) { bt_passkey_notify_callback(passkey); } +bool BLESerial::onSecurityRequest() { return bt_security_request_callback(); } +void BLESerial::onAuthenticationComplete(esp_ble_auth_cmpl_t auth_result) { bt_authentication_complete_callback(auth_result); } +void BLESerial::onConnect(BLEServer *server) { bt_connect_callback(server); } +void BLESerial::onDisconnect(BLEServer *server) { bt_disconnect_callback(server); ble_server->startAdvertising(); } +bool BLESerial::onConfirmPIN(uint32_t pin) { return bt_confirm_pin_callback(pin); }; +bool BLESerial::connected() { return ble_server->getConnectedCount() > 0; } + +int BLESerial::read() { + int result = this->rx_buffer.pop(); + if (result == '\n') { this->numAvailableLines--; } + return result; +} + +size_t BLESerial::readBytes(uint8_t *buffer, size_t bufferSize) { + int i = 0; + while (i < bufferSize && available()) { buffer[i] = (uint8_t)this->rx_buffer.pop(); i++; } + return i; +} + +int BLESerial::peek() { + if (this->rx_buffer.getLength() == 0) return -1; + return this->rx_buffer.get(0); +} + +int BLESerial::available() { return this->rx_buffer.getLength(); } + +size_t BLESerial::print(const char *str) { + if (ble_server->getConnectedCount() <= 0) return 0; + size_t written = 0; for (size_t i = 0; str[i] != '\0'; i++) { written += this->write(str[i]); } + flush(); + + return written; +} + +size_t BLESerial::write(const uint8_t *buffer, size_t bufferSize) { + if (ble_server->getConnectedCount() <= 0) { return 0; } else { + size_t written = 0; for (int i = 0; i < bufferSize; i++) { written += this->write(buffer[i]); } + flush(); + + return written; + } +} + +size_t BLESerial::write(uint8_t byte) { + if (bt_client_authenticated()) { + if (ble_server->getConnectedCount() <= 0) { return 0; } else { + this->transmitBuffer[this->transmitBufferLength] = byte; + this->transmitBufferLength++; + if (this->transmitBufferLength == maxTransferSize) { flush(); } + return 1; + } + } else { + return 0; + } +} + +void BLESerial::flush() { + if (this->transmitBufferLength > 0) { + TxCharacteristic->setValue(this->transmitBuffer, this->transmitBufferLength); + this->transmitBufferLength = 0; + this->lastFlushTime = millis(); + TxCharacteristic->notify(true); + } +} + +void BLESerial::disconnect() { + if (ble_server->getConnectedCount() > 0) { + uint16_t conn_id = ble_server->getConnId(); + // Serial.printf("Have connected: %d\n", conn_id); + ble_server->disconnect(conn_id); + // Serial.println("Disconnected"); + } else { + // Serial.println("No connected"); + } +} + +void BLESerial::begin(const char *name) { + ConnectedDeviceCount = 0; + BLEDevice::init(name); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + const esp_power_level_t ble_power = ESP_PWR_LVL_P6; + #else + const esp_power_level_t ble_power = ESP_PWR_LVL_P9; + #endif + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ble_power); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ble_power); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ble_power); + + ble_server = BLEDevice::createServer(); + ble_server->setCallbacks(this); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); + BLEDevice::setSecurityCallbacks(this); + + SetupSerialService(); + this->startAdvertising(); +} + +void BLESerial::startAdvertising() { + ble_adv = BLEDevice::getAdvertising(); + ble_adv->addServiceUUID(BLE_SERIAL_SERVICE_UUID); + ble_adv->setMinPreferred(0x20); + ble_adv->setMaxPreferred(0x40); + ble_adv->setScanResponse(true); + ble_adv->start(); +} + +void BLESerial::stopAdvertising() { + ble_adv = BLEDevice::getAdvertising(); + ble_adv->stop(); +} + +void BLESerial::end() { BLEDevice::deinit(); } + +void BLESerial::onWrite(BLECharacteristic *characteristic) { + if (characteristic->getUUID().toString() == BLE_RX_UUID) { + auto value = characteristic->getValue(); + for (int i = 0; i < value.length(); i++) { rx_buffer.push(value[i]); } + } +} + +void BLESerial::SetupSerialService() { + SerialService = ble_server->createService(BLE_SERIAL_SERVICE_UUID); + + RxCharacteristic = SerialService->createCharacteristic(BLE_RX_UUID, BLECharacteristic::PROPERTY_WRITE); + RxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM); + RxCharacteristic->addDescriptor(new BLE2902()); + RxCharacteristic->setWriteProperty(true); + RxCharacteristic->setCallbacks(this); + + TxCharacteristic = SerialService->createCharacteristic(BLE_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY); + TxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); + TxCharacteristic->addDescriptor(new BLE2902()); + TxCharacteristic->setNotifyProperty(true); + TxCharacteristic->setReadProperty(true); + + SerialService->start(); +} + +BLESerial::BLESerial() { } + +#endif +#endif diff --git a/vendor/rnode_firmware/BLESerial.h b/vendor/rnode_firmware/BLESerial.h new file mode 100644 index 0000000..f845b56 --- /dev/null +++ b/vendor/rnode_firmware/BLESerial.h @@ -0,0 +1,136 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Boards.h" + +#if PLATFORM != PLATFORM_NRF52 +#if HAS_BLE + +#include + +#include +#include +#include +#include + +template +class BLEFIFO { +private: + uint8_t buffer[n]; + int head = 0; + int tail = 0; + +public: + void push(uint8_t value) { + buffer[head] = value; + head = (head + 1) % n; + if (head == tail) { tail = (tail + 1) % n; } + } + + int pop() { + if (head == tail) { + return -1; + } else { + uint8_t value = buffer[tail]; + tail = (tail + 1) % n; + return value; + } + } + + void clear() { head = 0; tail = 0; } + + int get(size_t index) { + if (index >= this->getLength()) { + return -1; + } else { + return buffer[(tail + index) % n]; + } + } + + size_t getLength() { + if (head >= tail) { + return head - tail; + } else { + return n - tail + head; + } + } +}; + +#define RX_BUFFER_SIZE 6144 +#define BLE_BUFFER_SIZE 512 // Must fit in max GATT attribute length +#define MIN_MTU 50 + +class BLESerial : public BLECharacteristicCallbacks, public BLEServerCallbacks, public BLESecurityCallbacks, public Stream { +public: + BLESerial(); + + void begin(const char *name); + void end(); + void disconnect(); + void startAdvertising(); + void stopAdvertising(); + void onWrite(BLECharacteristic *characteristic); + int available(); + int peek(); + int read(); + size_t readBytes(uint8_t *buffer, size_t bufferSize); + size_t write(uint8_t byte); + size_t write(const uint8_t *buffer, size_t bufferSize); + size_t print(const char *value); + void flush(); + void onConnect(BLEServer *server); + void onDisconnect(BLEServer *server); + + uint32_t onPassKeyRequest(); + void onPassKeyNotify(uint32_t passkey); + bool onSecurityRequest(); + void onAuthenticationComplete(esp_ble_auth_cmpl_t); + bool onConfirmPIN(uint32_t pin); + + bool connected(); + + BLEServer *ble_server; + BLEAdvertising *ble_adv; + BLEService *SerialService; + BLECharacteristic *TxCharacteristic; + BLECharacteristic *RxCharacteristic; + size_t transmitBufferLength; + unsigned long long lastFlushTime; + +private: + BLESerial(BLESerial const &other) = delete; + void operator=(BLESerial const &other) = delete; + + BLEFIFO rx_buffer; + size_t numAvailableLines; + uint8_t transmitBuffer[BLE_BUFFER_SIZE]; + + int ConnectedDeviceCount; + void SetupSerialService(); + + uint16_t peerMTU; + uint16_t maxTransferSize = BLE_BUFFER_SIZE; + + bool checkMTU(); + + const char *BLE_SERIAL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; + const char *BLE_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; + const char *BLE_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; + + bool started = false; +}; + +#endif +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/Bluetooth.h b/vendor/rnode_firmware/Bluetooth.h new file mode 100644 index 0000000..af81e6b --- /dev/null +++ b/vendor/rnode_firmware/Bluetooth.h @@ -0,0 +1,649 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#if MCU_VARIANT == MCU_ESP32 + +#elif MCU_VARIANT == MCU_NRF52 +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if HAS_BLUETOOTH == true + #include "BluetoothSerial.h" + #include "esp_bt_main.h" + #include "esp_bt_device.h" + BluetoothSerial SerialBT; + #elif HAS_BLE == true + #include "esp_bt_main.h" + #include "esp_bt_device.h" + #include "BLESerial.h" + BLESerial SerialBT; + #endif + +#elif MCU_VARIANT == MCU_NRF52 + #include + #include + #define BLE_RX_BUF 6144 + BLEUart SerialBT(BLE_RX_BUF); + BLEDis bledis; + BLEBas blebas; + bool SerialBT_init = false; +#endif + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + #define BT_PAIRING_TIMEOUT 90000 +#else + #define BT_PAIRING_TIMEOUT 35000 +#endif +#define BLE_FLUSH_TIMEOUT 20 +uint32_t bt_pairing_started = 0; + +#define BT_DEV_ADDR_LEN 6 +#define BT_DEV_HASH_LEN 16 +uint8_t dev_bt_mac[BT_DEV_ADDR_LEN]; +char bt_da[BT_DEV_ADDR_LEN]; +char bt_dh[BT_DEV_HASH_LEN]; +char bt_devname[11]; + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + extern void stopRadio(); + + void cardputer_adv_ble_host_detached() { + if (radio_online) stopRadio(); + cable_state = CABLE_STATE_DISCONNECTED; + current_rssi = -292; + last_rssi = -292; + last_rssi_raw = 0x00; + last_snr_raw = 0x80; + } +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if HAS_BLUETOOTH == true + + void bt_confirm_pairing(uint32_t numVal) { + bt_ssp_pin = numVal; + kiss_indicate_btpin(); + if (bt_allow_pairing) { + SerialBT.confirmReply(true); + } else { + SerialBT.confirmReply(false); + } + } + + void bt_stop() { + display_unblank(); + if (bt_state != BT_STATE_OFF) { + SerialBT.end(); + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + } + } + + void bt_start() { + display_unblank(); + if (bt_state == BT_STATE_OFF) { + SerialBT.begin(bt_devname); + bt_state = BT_STATE_ON; + } + } + + void bt_enable_pairing() { + display_unblank(); + if (bt_state == BT_STATE_OFF) bt_start(); + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + } + + void bt_disable_pairing() { + display_unblank(); + bt_allow_pairing = false; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_pairing_complete(boolean success) { + display_unblank(); + if (success) { + bt_disable_pairing(); + } else { + bt_ssp_pin = 0; + } + } + + void bt_connection_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) { + display_unblank(); + if(event == ESP_SPP_SRV_OPEN_EVT) { + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + } + + if(event == ESP_SPP_CLOSE_EVT ){ + bt_state = BT_STATE_ON; + } + } + + bool bt_setup_hw() { + if (!bt_ready) { + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + bt_enabled = true; + } else { + bt_enabled = false; + } + if (btStart()) { + if (esp_bluedroid_init() == ESP_OK) { + if (esp_bluedroid_enable() == ESP_OK) { + const uint8_t* bda_ptr = esp_bt_dev_get_address(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = bda_ptr[i]; + } + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + SerialBT.enableSSP(); + SerialBT.onConfirmRequest(bt_confirm_pairing); + SerialBT.onAuthComplete(bt_pairing_complete); + SerialBT.register_callback(bt_connection_callback); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } else { return false; } + } else { return false; } + } + + bool bt_init() { + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + } + + #elif HAS_BLE == true + bool bt_setup_hw(); void bt_security_setup(); + BLESecurity *ble_security = new BLESecurity(); + bool ble_authenticated = false; + uint32_t pairing_pin = 0; + + void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flush(); } } + + void bt_start() { + // Serial.println("BT start"); + display_unblank(); + if (bt_state == BT_STATE_OFF) { + bt_state = BT_STATE_ON; + SerialBT.begin(bt_devname); + SerialBT.setTimeout(10); + } + } + + void bt_stop() { + // Serial.println("BT stop"); + display_unblank(); + if (bt_state != BT_STATE_OFF) { + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + SerialBT.end(); + } + } + + bool bt_init() { + // Serial.println("BT init"); + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void bt_debond_all() { + // Serial.println("Debonding all"); + int dev_num = esp_ble_get_bond_device_num(); + if (dev_num <= 0) return; + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + if (dev_list == NULL) return; + esp_ble_get_bond_device_list(&dev_num, dev_list); + for (int i = 0; i < dev_num; i++) { esp_ble_remove_bond_device(dev_list[i].bd_addr); } + free(dev_list); + } + + int bt_bond_count() { + return esp_ble_get_bond_device_num(); + } + + void bt_enable_pairing(bool show_pin) { + // Serial.println("BT enable pairing"); + display_unblank(); + if (bt_state == BT_STATE_OFF) bt_start(); + + bt_security_setup(); + + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + bt_ssp_pin = show_pin ? pairing_pin : 0; + } + + void bt_enable_pairing() { + bt_enable_pairing(true); + } + + void bt_enable_pairing_on_request() { + bt_enable_pairing(false); + } + + void bt_disable_pairing() { + // Serial.println("BT disable pairing"); + display_unblank(); + bt_allow_pairing = false; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_passkey_notify_callback(uint32_t passkey) { + // Serial.printf("Got passkey notification: %d\n", passkey); + if (bt_allow_pairing) { + bt_ssp_pin = passkey; + bt_pairing_started = millis(); + kiss_indicate_btpin(); + } else { + // Serial.println("Pairing not allowed, re-init"); + SerialBT.disconnect(); + } + } + + bool bt_confirm_pin_callback(uint32_t pin) { + // Serial.printf("Confirm PIN callback: %d\n", pin); + return true; + } + + void bt_update_passkey() { + // Serial.println("Updating passkey"); + pairing_pin = random(899999)+100000; + bt_ssp_pin = pairing_pin; + } + + uint32_t bt_passkey_callback() { + // Serial.println("API passkey request"); + if (pairing_pin == 0) { bt_update_passkey(); } + return pairing_pin; + } + + bool bt_client_authenticated() { + return ble_authenticated; + } + + bool bt_security_request_callback() { + if (bt_allow_pairing) { + // Serial.println("Accepting security request"); + return true; + } else { + // Serial.println("Rejecting security request"); + return false; + } + } + + void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result) { + if (auth_result.success == true) { + // Serial.println("Authentication success"); + ble_authenticated = true; + if (bt_state == BT_STATE_PAIRING) { + // Serial.println("Pairing complete, disconnecting"); + delay(2000); SerialBT.disconnect(); + } else { bt_state = BT_STATE_CONNECTED; } + } else { + // Serial.println("Authentication fail"); + ble_authenticated = false; + bt_state = BT_STATE_ON; + bt_update_passkey(); + bt_security_setup(); + } + bt_allow_pairing = false; + bt_ssp_pin = 0; + } + + void bt_connect_callback(BLEServer *server) { + uint16_t conn_id = server->getConnId(); + // Serial.printf("Connected: %d\n", conn_id); + display_unblank(); + ble_authenticated = false; + if (bt_state != BT_STATE_PAIRING) { bt_state = BT_STATE_CONNECTED; } + cable_state = CABLE_STATE_DISCONNECTED; + } + + void bt_disconnect_callback(BLEServer *server) { + uint16_t conn_id = server->getConnId(); + // Serial.printf("Disconnected: %d\n", conn_id); + display_unblank(); + ble_authenticated = false; + bt_state = BT_STATE_ON; + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + cardputer_adv_ble_host_detached(); + #endif + } + + bool bt_setup_hw() { + // Serial.println("BT setup hw"); + if (!bt_ready) { + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + bt_enabled = true; + } else { + bt_enabled = false; + } + if (btStart()) { + if (esp_bluedroid_init() == ESP_OK) { + if (esp_bluedroid_enable() == ESP_OK) { + const uint8_t* bda_ptr = esp_bt_dev_get_address(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = bda_ptr[i]; + } + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + bt_security_setup(); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } else { return false; } + } else { return false; } + } + + void bt_security_setup() { + // Serial.println("Executing BT security setup"); + if (pairing_pin == 0) { bt_update_passkey(); } + uint32_t passkey = pairing_pin; + // Serial.printf("Passkey is %d\n", passkey); + + uint8_t key_size = 16; + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; + uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + + esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; + + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); + } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + if (bt_state == BT_STATE_CONNECTED && millis()-SerialBT.lastFlushTime >= BLE_FLUSH_TIMEOUT) { + if (SerialBT.transmitBufferLength > 0) { + bt_flush(); + } + } + } + #endif + +#elif MCU_VARIANT == MCU_NRF52 + uint32_t pairing_pin = 0; + + uint8_t eeprom_read(uint32_t mapped_addr); + + void bt_stop() { + // Serial.println("BT Stop"); + if (bt_state != BT_STATE_OFF) { + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + } + } + + void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flushTXD(); } } + + void bt_disable_pairing() { + // Serial.println("BT Disable pairing"); + bt_allow_pairing = false; + pairing_pin = 0; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_pairing_complete(uint16_t conn_handle, uint8_t auth_status) { + // Serial.println("BT pairing complete"); + BLEConnection* connection = Bluefruit.Connection(conn_handle); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + ble_gap_conn_sec_mode_t security = connection->getSecureMode(); + // Serial.println("Bonding success"); + + // On the NRF52 it is not possible with the Arduino library to reject + // requests from devices with no IO capabilities, which would allow + // bypassing pin entry through pairing using the "just works" mode. + // Therefore, we must check the security level of the connection after + // pairing to ensure "just works" has not been used. If it has, we need + // to disconnect, unpair and delete any bonding information immediately. + // Settings on the SerialBT service should prevent unauthorised access to + // the serial port anyway, but this is still wise to do regardless. + // + // Note: It may be nice to have this done in the BLESecurity class in the + // future, but as it stands right now I'd have to fork the BSP to do + // that, which I don't fancy doing. Impact on security is likely minimal. + // Requires investigation. + + if (security.sm == 1 && security.lv >= 3) { + // Serial.println("Auth level success"); + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + connection->disconnect(); + bt_disable_pairing(); + } else { + // Serial.println("Auth level failure, debonding"); + if (connection->bonded()) { connection->removeBondKey(); } + connection->disconnect(); + bt_disable_pairing(); + } + } else { + // Serial.println("Bonding failure"); + connection->disconnect(); + bt_disable_pairing(); + } + } + + bool bt_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { + // Serial.println("Passkey callback"); + if (bt_allow_pairing) { + return true; + } + return false; + } + + void bt_connect_callback(uint16_t conn_handle) { + // Serial.println("Connect callback"); + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + + BLEConnection* conn = Bluefruit.Connection(conn_handle); + conn->requestPHY(BLE_GAP_PHY_2MBPS); + conn->requestMtuExchange(512+3); + conn->requestDataLengthUpdate(); + } + + void bt_disconnect_callback(uint16_t conn_handle, uint8_t reason) { + // Serial.println("Disconnect callback"); + if (reason != BLE_GAP_SEC_STATUS_SUCCESS) { + bt_state = BT_STATE_ON; + } + } + + void bt_update_passkey() { + // Serial.println("Update passkey"); + pairing_pin = random(899999)+100000; + bt_ssp_pin = pairing_pin; + } + + uint32_t bt_get_passkey() { + // Serial.println("API passkey request"); + if (pairing_pin == 0) { bt_update_passkey(); } + return pairing_pin; + } + + bool bt_setup_hw() { + // Serial.println("Setup HW"); + if (!bt_ready) { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + #else + if (eeprom_read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + #endif + bt_enabled = true; + } else { + bt_enabled = false; + } + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.autoConnLed(false); + if (Bluefruit.begin()) { + uint32_t pin = bt_get_passkey(); + char pin_char[6]; + sprintf(pin_char,"%lu", pin); + + Bluefruit.setTxPower(8); // Check bluefruit.h for supported values + Bluefruit.Security.setIOCaps(true, false, false); // display, yes; yes / no, no; keyboard, no + // This device is indeed capable of yes / no through the pairing mode + // being set, but I have chosen to set it thus to force the input of the + // pin on the device initiating the pairing. + + Bluefruit.Security.setMITM(true); + Bluefruit.Security.setPairPasskeyCallback(bt_passkey_callback); + Bluefruit.Security.setSecuredCallback(bt_connect_callback); + Bluefruit.Security.setPIN(pin_char); + Bluefruit.Periph.setDisconnectCallback(bt_disconnect_callback); + Bluefruit.Security.setPairCompleteCallback(bt_pairing_complete); + Bluefruit.Periph.setConnInterval(6, 12); // 7.5 - 15 ms + + const ble_gap_addr_t gap_addr = Bluefruit.getAddr(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = gap_addr.addr[i]; + } + #if HAS_EEPROM + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + #else + data[BT_DEV_ADDR_LEN] = eeprom_read(eeprom_addr(ADDR_SIGNATURE)); + #endif + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } + + void bt_start() { + // Serial.println("BT Start"); + if (bt_state == BT_STATE_OFF) { + Bluefruit.setName(bt_devname); + bledis.setManufacturer(BLE_MANUFACTURER); + bledis.setModel(BLE_MODEL); + // start device information service + bledis.begin(); + blebas.begin(); + + // Guard to ensure SerialBT service is not duplicated through BT being power cycled + if (!SerialBT_init) { + SerialBT.bufferTXD(true); // enable buffering + + SerialBT.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // enable encryption for BLE serial + SerialBT.begin(); + SerialBT_init = true; + } + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include bleuart 128-bit uuid + Bluefruit.Advertising.addService(SerialBT); + + // There is no room for Name in Advertising packet + // Use Scan response for Name + Bluefruit.ScanResponse.addName(); + + Bluefruit.Advertising.start(0); + + bt_state = BT_STATE_ON; + } + } + + bool bt_init() { + // Serial.println("BT init"); + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void bt_enable_pairing() { + // Serial.println("BT enable pairing"); + if (bt_state == BT_STATE_OFF) bt_start(); + + uint32_t pin = bt_get_passkey(); + char pin_char[6]; + sprintf(pin_char,"%lu", pin); + Bluefruit.Security.setPIN(pin_char); + + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + kiss_indicate_btpin(); + } + + void bt_debond_all() { } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + } +#endif diff --git a/vendor/rnode_firmware/Boards.h b/vendor/rnode_firmware/Boards.h new file mode 100644 index 0000000..6dcabf7 --- /dev/null +++ b/vendor/rnode_firmware/Boards.h @@ -0,0 +1,996 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Modem.h" + +#ifndef BOARDS_H + #define BOARDS_H + + #define PLATFORM_AVR 0x90 + #define PLATFORM_ESP32 0x80 + #define PLATFORM_NRF52 0x70 + + #define MCU_1284P 0x91 + #define MCU_2560 0x92 + #define MCU_ESP32 0x81 + #define MCU_NRF52 0x71 + + // Products, boards and models //// + #define PRODUCT_RNODE 0x03 // RNode devices + #define BOARD_RNODE 0x31 // Original v1.0 RNode + #define MODEL_A4 0xA4 // RNode v1.0, 433 MHz + #define MODEL_A9 0xA9 // RNode v1.0, 868 MHz + + #define BOARD_RNODE_NG_20 0x40 // RNode hardware revision v2.0 + #define MODEL_A3 0xA3 // RNode v2.0, 433 MHz + #define MODEL_A8 0xA8 // RNode v2.0, 868 MHz + + #define BOARD_RNODE_NG_21 0x41 // RNode hardware revision v2.1 + #define MODEL_A2 0xA2 // RNode v2.1, 433 MHz + #define MODEL_A7 0xA7 // RNode v2.1, 868 MHz + + #define BOARD_T3S3 0x42 // T3S3 devices + #define MODEL_A1 0xA1 // T3S3, 433 MHz with SX1268 + #define MODEL_A5 0xA5 // T3S3, 433 MHz with SX1278 + #define MODEL_A6 0xA6 // T3S3, 868 MHz with SX1262 + #define MODEL_AA 0xAA // T3S3, 868 MHz with SX1276 + #define MODEL_AC 0xAC // T3S3, 2.4 GHz with SX1280 and PA + + #define PRODUCT_TBEAM 0xE0 // T-Beam devices + #define BOARD_TBEAM 0x33 + #define MODEL_E4 0xE4 // T-Beam SX1278, 433 Mhz + #define MODEL_E9 0xE9 // T-Beam SX1276, 868 Mhz + #define MODEL_E3 0xE3 // T-Beam SX1268, 433 Mhz + #define MODEL_E8 0xE8 // T-Beam SX1262, 868 Mhz + + #define PRODUCT_TDECK_V1 0xD0 + #define BOARD_TDECK 0x3B + #define MODEL_D4 0xD4 // LilyGO T-Deck, 433 MHz + #define MODEL_D9 0xD9 // LilyGO T-Deck, 868 MHz + + #define PRODUCT_TBEAM_S_V1 0xEA + #define BOARD_TBEAM_S_V1 0x3D + #define MODEL_DB 0xDB // LilyGO T-Beam Supreme, 433 MHz + #define MODEL_DC 0xDC // LilyGO T-Beam Supreme, 868 MHz + + #define PRODUCT_XIAO_S3 0xEB + #define BOARD_XIAO_S3 0x3E + #define MODEL_DE 0xDE // Xiao ESP32S3 with Wio-SX1262 module, 433 MHz + #define MODEL_DD 0xDD // Xiao ESP32S3 with Wio-SX1262 module, 868 MHz + + #define PRODUCT_T32_10 0xB2 + #define BOARD_LORA32_V1_0 0x39 + #define MODEL_BA 0xBA // LilyGO T3 v1.0, 433 MHz + #define MODEL_BB 0xBB // LilyGO T3 v1.0, 868 MHz + + #define PRODUCT_T32_20 0xB0 + #define BOARD_LORA32_V2_0 0x36 + #define MODEL_B3 0xB3 // LilyGO T3 v2.0, 433 MHz + #define MODEL_B8 0xB8 // LilyGO T3 v2.0, 868 MHz + + #define PRODUCT_T32_21 0xB1 + #define BOARD_LORA32_V2_1 0x37 + #define MODEL_B4 0xB4 // LilyGO T3 v2.1, 433 MHz + #define MODEL_B9 0xB9 // LilyGO T3 v2.1, 868 MHz + + #define PRODUCT_H32_V2 0xC0 // Board code 0x38 + #define BOARD_HELTEC32_V2 0x38 + #define MODEL_C4 0xC4 // Heltec Lora32 v2, 433 MHz + #define MODEL_C9 0xC9 // Heltec Lora32 v2, 868 MHz + + #define PRODUCT_H32_V3 0xC1 + #define BOARD_HELTEC32_V3 0x3A + #define MODEL_C5 0xC5 // Heltec Lora32 v3, 433 MHz + #define MODEL_CA 0xCA // Heltec Lora32 v3, 868 MHz + + #define PRODUCT_H32_V4 0xC3 + #define BOARD_HELTEC32_V4 0x3F + #define MODEL_C8 0xC8 // Heltec Lora32 v3, 850-950 MHz, 28dBm + + #define PRODUCT_HELTEC_T114 0xC2 // Heltec Mesh Node T114 + #define BOARD_HELTEC_T114 0x3C + #define MODEL_C6 0xC6 // Heltec Mesh Node T114, 470-510 MHz + #define MODEL_C7 0xC7 // Heltec Mesh Node T114, 863-928 MHz + + #define PRODUCT_TECHO 0x15 // LilyGO T-Echo devices + #define BOARD_TECHO 0x44 + #define MODEL_16 0x16 // T-Echo 433 MHz + #define MODEL_17 0x17 // T-Echo 868/915 MHz + + #define PRODUCT_CARDPUTER_ADV 0xEC // M5Stack Cardputer Adv + Cap LoRa-1262 + #define BOARD_CARDPUTER_ADV 0x43 + #define MODEL_EC 0xEC // Cardputer Adv, 850-950 MHz + + #define PRODUCT_RAK4631 0x10 + #define BOARD_RAK4631 0x51 + #define MODEL_11 0x11 // RAK4631, 433 Mhz + #define MODEL_12 0x12 // RAK4631, 868 Mhz + + #define PRODUCT_HMBRW 0xF0 + #define BOARD_HMBRW 0x32 + #define BOARD_HUZZAH32 0x34 + #define BOARD_GENERIC_ESP32 0x35 + #define BOARD_GENERIC_NRF52 0x50 + #define MODEL_FE 0xFE // Homebrew board, max 17dBm output power + #define MODEL_FF 0xFF // Homebrew board, max 14dBm output power + + #if defined(__AVR_ATmega1284P__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_1284P + #elif defined(__AVR_ATmega2560__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_2560 + #elif defined(ESP32) + #define PLATFORM PLATFORM_ESP32 + #define MCU_VARIANT MCU_ESP32 + #elif defined(NRF52840_XXAA) + #include + #define PLATFORM PLATFORM_NRF52 + #define MCU_VARIANT MCU_NRF52 + #else + #error "The firmware cannot be compiled for the selected MCU variant" + #endif + + #ifndef MODEM + #if BOARD_MODEL == BOARD_RAK4631 + #define MODEM SX1262 + #elif BOARD_MODEL == BOARD_GENERIC_NRF52 + #define MODEM SX1262 + #else + #define MODEM SX1276 + #endif + #endif + + #define LORA_PA_UNKNOWN 0x00 + #define LORA_PA_GC1109 0x01 + #define LORA_PA_KCT8103L 0x02 + + #define HAS_DISPLAY false + #define HAS_BLUETOOTH false + #define HAS_BLE false + #define HAS_WIFI false + #define HAS_TCXO false + #define HAS_PMU false + #define HAS_NP false + #define HAS_EEPROM false + #define HAS_INPUT false + #define HAS_SLEEP false + #define HAS_LORA_PA false + #define HAS_LORA_LNA false + #define PIN_DISP_SLEEP -1 + #define VALIDATE_FIRMWARE true + + #if defined(ENABLE_TCXO) + #define HAS_TCXO true + #endif + + #if MCU_VARIANT == MCU_1284P + const int pin_cs = 4; + const int pin_reset = 3; + const int pin_dio = 2; + const int pin_led_rx = 12; + const int pin_led_tx = 13; + + #define BOARD_MODEL BOARD_RNODE + #define HAS_EEPROM true + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 4096 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #elif MCU_VARIANT == MCU_2560 + const int pin_cs = 5; + const int pin_reset = 4; + const int pin_dio = 2; + const int pin_led_rx = 12; + const int pin_led_tx = 13; + + #define BOARD_MODEL BOARD_HMBRW + #define HAS_EEPROM true + #define CONFIG_UART_BUFFER_SIZE 768 + #define CONFIG_QUEUE_SIZE 5120 + #define CONFIG_QUEUE_MAX_LENGTH 24 + #define EEPROM_SIZE 4096 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #elif MCU_VARIANT == MCU_ESP32 + + // Board models for ESP32 based builds are + // defined by the build target in the makefile. + // If you are not using make to compile this + // firmware, you can manually define model here. + // + // #define BOARD_MODEL BOARD_GENERIC_ESP32 + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + + #define EEPROM_SIZE 1024 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define CONFIG_OFFSET 0 + + #define GPS_BAUD_RATE 9600 + #define PIN_GPS_TX 12 + #define PIN_GPS_RX 34 + + #if BOARD_MODEL == BOARD_GENERIC_ESP32 + #define HAS_BLUETOOTH 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; + + #elif BOARD_MODEL == BOARD_TBEAM + #define HAS_DISPLAY true + #define HAS_PMU true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_SD false + #define HAS_EEPROM true + #define I2C_SDA 21 + #define I2C_SCL 22 + #define PMU_IRQ 35 + + #define HAS_INPUT true + const int pin_btn_usr1 = 38; + + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_led_rx = 2; + const int pin_led_tx = 4; + + #if MODEM == SX1262 + #define HAS_TCXO true + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + #define OCP_TUNED 0x28 + const int pin_busy = 32; + const int pin_dio = 33; + const int pin_tcxo_enable = -1; + #else + const int pin_dio = 26; + #endif + + #elif BOARD_MODEL == BOARD_HUZZAH32 + #define HAS_BLUETOOTH 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; + + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 14; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 25; + const int pin_led_tx = 2; + #else + const int pin_led_rx = 2; + const int pin_led_tx = 2; + #endif + + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 12; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 2; + const int pin_led_tx = 0; + #else + const int pin_led_rx = 22; + const int pin_led_tx = 22; + #endif + + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_dio = 26; + #if HAS_TCXO == true + const int pin_tcxo_enable = 33; + #endif + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 15; + const int pin_led_tx = 4; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 0; + + const int pin_cs = 18; + const int pin_reset = 14; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 36; + const int pin_led_tx = 37; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_WIFI true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + #define OCP_TUNED 0x28 + + const int pin_btn_usr1 = 0; + + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 13; + const int pin_led_tx = 14; + #else + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #endif + + #define MODEM SX1262 + #define HAS_TCXO true + const int pin_tcxo_enable = -1; + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + + // Following pins are for the SX1262 + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_WIFI true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define HAS_LORA_PA true + #define HAS_LORA_LNA true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + #define OCP_TUNED 0x28 + #define Vext GPIO_NUM_36 + #define LORA_PA_MODEL LORA_PA_UNKNOWN; + + const int pin_btn_usr1 = 0; + + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 13; + const int pin_led_tx = 14; + #else + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #endif + + #define MODEM SX1262 + #define HAS_TCXO true + 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 + #define LORA_PA_PWR_EN 7 + #define LORA_PA_CSD 2 // Same pin on GC1109 + #define LORA_PA_CPS 46 // Same pin on GC1109 + #define LORA_PA_CTX 5 // Only used on KCT8103 + + #define PA_MAX_OUTPUT 28 + #define PA_GAIN_POINTS 22 + + #define LORA_LNA_KCT8103L_GAIN 21 + const int PA_GC1109_VALUES[PA_GAIN_POINTS] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7}; + const int PA_KCT8103L_VALUES[PA_GAIN_POINTS] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7}; + + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + #elif BOARD_MODEL == BOARD_RNODE_NG_20 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_NP true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 12; + const int pin_dio = 26; + const int pin_np = 4; + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 2; + const int pin_led_tx = 0; + #else + const int pin_led_rx = 22; + const int pin_led_tx = 22; + #endif + #endif + + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_PMU true + #define HAS_NP true + #define HAS_SD false + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_dio = 26; + const int pin_np = 12; + const int pin_dac = 25; + const int pin_adc = 34; + const int SD_MISO = 2; + const int SD_MOSI = 15; + const int SD_CLK = 14; + const int SD_CS = 13; + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 12; + const int pin_led_tx = 4; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + #endif + + #elif BOARD_MODEL == BOARD_T3S3 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_CONSOLE true + #define HAS_WIFI true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + const int pin_btn_usr1 = 0; + + const int pin_cs = 7; + const int pin_reset = 8; + const int pin_sclk = 5; + const int pin_mosi = 6; + const int pin_miso = 3; + + #if MODEM == SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + const int pin_busy = 34; + const int pin_dio = 33; + const int pin_tcxo_enable = -1; + #elif MODEM == SX1280 + #define CONFIG_QUEUE_SIZE 6144 + #define DIO2_AS_RF_SWITCH false + #define HAS_BUSY true + #define HAS_TCXO true + #define HAS_PA true + const int pa_max_input = 3; + + #define HAS_RF_SWITCH_RX_TX true + const int pin_rxen = 21; + const int pin_txen = 10; + + const int pin_busy = 36; + const int pin_dio = 9; + const int pin_tcxo_enable = -1; + #else + const int pin_dio = 9; + #endif + + const int pin_np = 38; + const int pin_dac = 25; + const int pin_adc = 1; + + const int SD_MISO = 2; + const int SD_MOSI = 11; + const int SD_CLK = 14; + const int SD_CS = 13; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 37; + const int pin_led_tx = 37; + #else + const int pin_led_rx = 37; + const int pin_led_tx = 37; + #endif + #endif + + #elif BOARD_MODEL == BOARD_TDECK + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + + #define HAS_DISPLAY false + #define HAS_CONSOLE false + #define HAS_WIFI true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_poweron = 10; + const int pin_btn_usr1 = 0; + + const int pin_cs = 9; + const int pin_reset = 17; + const int pin_sclk = 40; + const int pin_mosi = 41; + const int pin_miso = 38; + const int pin_tcxo_enable = -1; + const int pin_dio = 45; + const int pin_busy = 13; + + const int SD_MISO = 38; + const int SD_MOSI = 41; + const int SD_CLK = 40; + const int SD_CS = 39; + + const int DISPLAY_DC = 11; + const int DISPLAY_CS = 12; + const int DISPLAY_MISO = 38; + const int DISPLAY_MOSI = 41; + const int DISPLAY_CLK = 40; + const int DISPLAY_BL_PIN = 42; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #else + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #endif + #endif + + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + #define OCP_TUNED 0x28 + + #define HAS_DISPLAY true + #define HAS_CONSOLE true + #define HAS_WIFI true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP false + + #define PMU_IRQ 40 + #define I2C_SCL 41 + #define I2C_SDA 42 + + const int pin_btn_usr1 = 0; + + const int pin_cs = 10; + const int pin_reset = 5; + const int pin_sclk = 12; + const int pin_mosi = 11; + const int pin_miso = 13; + const int pin_tcxo_enable = -1; + const int pin_dio = 1; + const int pin_busy = 4; + + const int SD_MISO = 37; + const int SD_MOSI = 35; + const int SD_CLK = 36; + const int SD_CS = 47; + + const int IMU_CS = 34; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #else + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #endif + #endif + + #elif BOARD_MODEL == BOARD_XIAO_S3 + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + + #define HAS_DISPLAY false + #define HAS_CONSOLE true + #define HAS_WIFI true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_21 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 21; + const int pin_cs = 41; + const int pin_reset = 42; + const int pin_sclk = 7; + const int pin_mosi = 9; + const int pin_miso = 8; + const int pin_tcxo_enable = -1; + const int pin_dio = 39; + const int pin_busy = 40; + + #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_CARDPUTER_ADV + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + + #define HAS_DISPLAY true + #define DISPLAY_SCALE 1 + #define HAS_CONSOLE true + #define HAS_WIFI false + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 0; + + const int pin_cs = 5; + const int pin_reset = 3; + const int pin_sclk = 40; + const int pin_mosi = 14; + const int pin_miso = 39; + const int pin_tcxo_enable = -1; + const int pin_dio = 4; + const int pin_busy = 6; + #define CARDPUTER_ADV_I2C_SDA 8 + #define CARDPUTER_ADV_I2C_SCL 9 + #define CARDPUTER_ADV_CAP_IOE_ADDR 0x43 + #define CARDPUTER_ADV_CAP_RF_SW_PIN 0 + + const int SD_MISO = -1; + const int SD_MOSI = -1; + const int SD_CLK = -1; + const int SD_CS = -1; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = -1; + const int pin_led_tx = -1; + #else + const int pin_led_rx = -1; + const int pin_led_tx = -1; + #endif + #endif + + #else + #error An unsupported ESP32 board was selected. Cannot compile RNode firmware. + #endif + + #elif MCU_VARIANT == MCU_NRF52 + #if BOARD_MODEL == BOARD_RAK4631 + #define HAS_EEPROM false + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_CONSOLE false + #define HAS_PMU false + #define HAS_NP false + #define HAS_SD false + #define HAS_TCXO true + #define HAS_RF_SWITCH_RX_TX true + #define HAS_BUSY true + #define HAS_INPUT true + #define DIO2_AS_RF_SWITCH true + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define BLE_MANUFACTURER "RAK Wireless" + #define BLE_MODEL "RAK4640" + + const int pin_btn_usr1 = 9; + + // Following pins are for the sx1262 + const int pin_rxen = 37; + const int pin_txen = -1; + const int pin_reset = 38; + const int pin_cs = 42; + const int pin_sclk = 43; + const int pin_mosi = 44; + const int pin_miso = 45; + const int pin_busy = 46; + const int pin_dio = 47; + const int pin_led_rx = LED_BLUE; + const int pin_led_tx = LED_GREEN; + const int pin_tcxo_enable = -1; + + #elif BOARD_MODEL == BOARD_TECHO + #define _PINNUM(port, pin) ((port) * 32 + (pin)) + #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_INPUT true + #define HAS_SLEEP true + #define BLE_MANUFACTURER "LilyGO" + #define BLE_MODEL "T-Echo" + + #define HAS_INPUT true + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #define CONFIG_UART_BUFFER_SIZE 32768 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + + #define HAS_DISPLAY true + #define HAS_BACKLIGHT true + #define DISPLAY_SCALE 1 + + #define LED_ON LOW + #define LED_OFF HIGH + #define PIN_LED_GREEN _PINNUM(1, 1) + #define PIN_LED_RED _PINNUM(1, 3) + #define PIN_LED_BLUE _PINNUM(0, 14) + #define PIN_VEXT_EN _PINNUM(0, 12) + + const int pin_disp_cs = 30; + const int pin_disp_dc = 28; + const int pin_disp_reset = 2; + const int pin_disp_busy = 3; + const int pin_disp_en = -1; + const int pin_disp_sck = 31; + const int pin_disp_mosi = 29; + const int pin_disp_miso = -1; + const int pin_backlight = 43; + + const int pin_btn_usr1 = _PINNUM(1, 10); + const int pin_btn_touch = _PINNUM(0, 11); + + const int pin_reset = 25; + const int pin_cs = 24; + const int pin_sclk = 19; + const int pin_mosi = 22; + const int pin_miso = 23; + const int pin_busy = 17; + const int pin_dio = 20; + const int pin_tcxo_enable = 21; + const int pin_led_rx = PIN_LED_BLUE; + const int pin_led_tx = PIN_LED_RED; + + #elif BOARD_MODEL == BOARD_HELTEC_T114 + #define MODEM SX1262 + #define HAS_EEPROM false + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_CONSOLE false + #define HAS_PMU true + #define HAS_NP true + #define HAS_SD false + #define HAS_TCXO true + #define HAS_BUSY true + #define HAS_INPUT true + #define HAS_SLEEP true + #define DIO2_AS_RF_SWITCH true + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define BLE_MANUFACTURER "Heltec" + #define BLE_MODEL "T114" + + #define PIN_T114_ADC_EN 6 + #define PIN_VEXT_EN 21 + + // LED + #define LED_T114_GREEN 3 + #define PIN_T114_LED 14 + #define NP_M 1 + const int pin_np = PIN_T114_LED; + + // SPI + #define PIN_T114_MOSI 22 + #define PIN_T114_MISO 23 + #define PIN_T114_SCK 19 + #define PIN_T114_SS 24 + + // SX1262 + #define PIN_T114_RST 25 + #define PIN_T114_DIO1 20 + #define PIN_T114_BUSY 17 + + // TFT + #define DISPLAY_SCALE 2 + #define PIN_T114_TFT_MOSI 9 + #define PIN_T114_TFT_MISO 11 // not connected + #define PIN_T114_TFT_SCK 8 + #define PIN_T114_TFT_SS 11 + #define PIN_T114_TFT_DC 12 + #define PIN_T114_TFT_RST 2 + #define PIN_T114_TFT_EN 3 + #define PIN_T114_TFT_BLGT 15 + + // pins for buttons on Heltec T114 + const int pin_btn_usr1 = 42; + + // pins for sx1262 on Heltec T114 + const int pin_reset = PIN_T114_RST; + const int pin_cs = PIN_T114_SS; + const int pin_sclk = PIN_T114_SCK; + const int pin_mosi = PIN_T114_MOSI; + const int pin_miso = PIN_T114_MISO; + const int pin_busy = PIN_T114_BUSY; + const int pin_dio = PIN_T114_DIO1; + const int pin_led_rx = 35; + const int pin_led_tx = 35; + const int pin_tcxo_enable = -1; + + // pins for ST7789 display on Heltec T114 + const int DISPLAY_DC = PIN_T114_TFT_DC; + const int DISPLAY_CS = PIN_T114_TFT_SS; + const int DISPLAY_MISO = PIN_T114_TFT_MISO; + const int DISPLAY_MOSI = PIN_T114_TFT_MOSI; + const int DISPLAY_CLK = PIN_T114_TFT_SCK; + const int DISPLAY_BL_PIN = PIN_T114_TFT_BLGT; + const int DISPLAY_RST = PIN_T114_TFT_RST; + + #else + #error An unsupported nRF board was selected. Cannot compile RNode firmware. + #endif + + #endif + + #ifndef DISPLAY_SCALE + #define DISPLAY_SCALE 1 + #endif + + #ifndef HAS_RF_SWITCH_RX_TX + const int pin_rxen = -1; + const int pin_txen = -1; + #endif + + #ifndef HAS_BUSY + const int pin_busy = -1; + #endif + + #ifndef LED_ON + #define LED_ON HIGH + #endif + + #ifndef LED_OFF + #define LED_OFF LOW + #endif + + #ifndef DIO2_AS_RF_SWITCH + #define DIO2_AS_RF_SWITCH false + #endif + + // Default OCP value if not specified + // in board configuration + #ifndef OCP_TUNED + #define OCP_TUNED 0x28 + #endif + + #ifndef NP_M + #define NP_M 0.15 + #endif + +#endif diff --git a/vendor/rnode_firmware/CARDPUTER_ADV_PORT.md b/vendor/rnode_firmware/CARDPUTER_ADV_PORT.md new file mode 100644 index 0000000..e4fe01c --- /dev/null +++ b/vendor/rnode_firmware/CARDPUTER_ADV_PORT.md @@ -0,0 +1,84 @@ +# Cardputer Adv RNode Firmware Port + +Base: upstream `markqvist/RNode_Firmware` tag `1.86` (`9b39b6c`). + +Old reference branch: `ratspeak/rnode_firmware_ce` `cardputer-adv-support` +(`0cd0f64`). This repo ports the Cardputer Adv support to upstream firmware +instead of carrying the CE fork. + +## Progress + +- [x] Create local repo from upstream 1.86. +- [x] Add Cardputer Adv board/product/model IDs and pin map. +- [x] Add SX1262 custom SPI and Cardputer TCXO voltage handling. +- [x] Add M5 display rendering. +- [x] Add Cardputer keyboard shortcuts. +- [x] Add build/release targets. +- [x] Add Cardputer Adv first-boot EEPROM self-provisioning. +- [x] Add repo-local firmware hash writer and Makefile targets. +- [x] Add Cardputer Adv firmware-hash self-trust on first mismatched boot. +- [x] Keep Cardputer Adv RF idle until a USB/BLE host starts it. +- [x] Document Cardputer Adv USB CDC and BLE pairing behavior. +- [x] Port Ratcom Cardputer radio hardening: dedicated FSPI, cap RF switch, + LDO regulator init, TCXO fallback, and TX-timeout radio recovery. +- [x] Build firmware target. +- [x] Document flashing/provisioning and remaining upstream tool gaps. + +## Build Verification + +Command run: + +```sh +make firmware-cardputer_adv +make release-cardputer_adv +``` + +Result: + +- Sketch uses `1571665` bytes, `47%` of program storage. +- Global variables use `99492` bytes, `30%` of dynamic memory. +- Build artifacts are written under `build/esp32.esp32.esp32s3/`. +- Release package is written to `Release/rnode_firmware_cardputer_adv.zip`. + +## Hardware Notes + +- Board: M5Stack Cardputer Adv, ESP32-S3, 8MB flash. +- Radio: M5Stack Cap LoRa-1262, SX1262, 850-950 MHz. +- LoRa pins: NSS `GPIO5`, IRQ/DIO1 `GPIO4`, RST `GPIO3`, BUSY `GPIO6`, + SCK `GPIO40`, MOSI `GPIO14`, MISO `GPIO39`. +- TCXO: DIO3-controlled, 3.0V. +- DIO2 is used as the SX1262 RF switch control. +- SD is left disabled for this dumb RNode target because it shares the radio + SPI bus and is not needed by host-controlled RNode operation. + +## Tooling Gap + +`rnodeconf` upstream does not currently know `PRODUCT_CARDPUTER_ADV=0xEC` or +`MODEL_EC=0xEC`. The firmware now self-provisions a missing or invalid EEPROM +identity block and seeds a default 915 MHz radio config, so a fresh Cardputer +Adv should not stop at the stock "run rnodeconf" config error. The seed profile +is build-time configurable with `CARDPUTER_ADV_DEFAULT_*` make variables, and +host software can still overwrite and persist normal RNode radio settings after +boot. The Cardputer Adv target does not start the seeded config on boot; it stays +in host-controlled mode and leaves the SX1262 idle until USB or BLE KISS starts +the radio. + +USB CDC serial is enabled for the ESP32-S3 build and carries the normal RNode +KISS protocol. BLE is enabled by default; holding `p` or Enter/OK for three +seconds enters the pairing flow used by other RNode boards, holding `b` for +three seconds toggles BLE on or off, and pairing mode times out after 30 seconds +if no client connects. + +The Cardputer Adv radio path follows Ratcom's hardened Cap LoRa-1262 handling: +LoRa uses a dedicated ESP32-S3 FSPI bus, the PI4IOE5V6408 RF antenna switch is +enabled before radio start, SX1262 init uses LDO regulator mode with the TCXO +kept as the TX/RX fallback clock, stale IRQ state is cleared when RX starts, and +TX timeout recovery restarts only the radio instead of rebooting the whole +device. + +The repo-local `tools/cardputer_adv_firmware_hash.py` tool writes the expected +firmware hash with raw KISS. The firmware also self-stores the running ESP32 app +hash when the stored target hash does not match, so the Cardputer target does +not need upstream `rnodeconf` metadata to clear the stock firmware-corrupt +screen. First-class autoinstall and friendly `rnodeconf -i` output still need +matching Reticulum/rnodeconf metadata. diff --git a/vendor/rnode_firmware/Config.h b/vendor/rnode_firmware/Config.h new file mode 100644 index 0000000..0e0e6ad --- /dev/null +++ b/vendor/rnode_firmware/Config.h @@ -0,0 +1,242 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ROM.h" +#include "Boards.h" + +#ifndef CONFIG_H + #define CONFIG_H + + #define MAJ_VERS 0x01 + #define MIN_VERS 0x56 + + #define MODE_HOST 0x11 + #define MODE_TNC 0x12 + + #define CABLE_STATE_DISCONNECTED 0x00 + #define CABLE_STATE_CONNECTED 0x01 + uint8_t cable_state = CABLE_STATE_DISCONNECTED; + + #define BT_STATE_NA 0xff + #define BT_STATE_OFF 0x00 + #define BT_STATE_ON 0x01 + #define BT_STATE_PAIRING 0x02 + #define BT_STATE_CONNECTED 0x03 + uint8_t bt_state = BT_STATE_NA; + uint32_t bt_ssp_pin = 0; + bool bt_ready = false; + bool bt_enabled = false; + bool bt_allow_pairing = false; + + #define WR_CHANNEL_DEFAULT 1 + #define WR_WIFI_OFF 0x00 + #define WR_WIFI_STA 0x01 + #define WR_WIFI_AP 0x02 + #define WR_STATE_NA 0xff + #define WR_STATE_OFF 0x00 + #define WR_STATE_ON 0x01 + #define WR_STATE_CONNECTED 0x02 + uint8_t wr_state = WR_STATE_OFF; + uint8_t wr_channel = WR_CHANNEL_DEFAULT; + + #define M_FRQ_S 27388122 + #define M_FRQ_R 27388061 + bool console_active = false; + bool modem_installed = false; + + #define MTU 508 + #define SINGLE_MTU 255 + #define HEADER_L 1 + #define MIN_L 1 + #define CMD_L 64 + + bool mw_radio_online = false; + + #define eeprom_addr(a) (a+EEPROM_OFFSET) + #define config_addr(a) (a+CONFIG_OFFSET) + + #if (MODEM == SX1262 || MODEM == SX1280) && defined(NRF52840_XXAA) + SPIClass spiModem(NRF_SPIM2, pin_miso, pin_sclk, pin_mosi); + #endif + + // MCU independent configuration parameters + const long serial_baudrate = 115200; + + // SX1276 RSSI offset to get dBm value from + // packet RSSI register + const int rssi_offset = 157; + + // Default LoRa settings + #define PHY_HEADER_LORA_SYMBOLS 20 + #define PHY_CRC_LORA_BITS 16 + #define LORA_PREAMBLE_SYMBOLS_MIN 18 + #define LORA_PREAMBLE_TARGET_MS 24 + #define LORA_PREAMBLE_FAST_DELTA 18 + #define LORA_FAST_THRESHOLD_BPS 30E3 + #define LORA_LIMIT_THRESHOLD_BPS 60E3 + #define LORA_GUARD_THRESHOLD_BPS 14E3 + #define LORA_FAST_GUARD_MS 48 + long lora_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; + long lora_preamble_time_ms = 0; + long lora_header_time_ms = 0; + float lora_symbol_time_ms = 0.0; + float lora_symbol_rate = 0.0; + float lora_us_per_byte = 0.0; + bool lora_low_datarate = false; + bool lora_limit_rate = false; + bool lora_guard_rate = false; + + // CSMA Parameters + #define CSMA_SIFS_MS 0 + #define CSMA_POST_TX_YIELD_SLOTS 3 + #define CSMA_SLOT_MAX_MS 100 + #define CSMA_SLOT_MIN_MS 24 + #define CSMA_SLOT_MIN_FAST_DELTA 18 + #define CSMA_SLOT_SYMBOLS 12 + #define CSMA_CW_BANDS 4 + #define CSMA_CW_MIN 0 + #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 + #define CSMA_RFENV_RECAL_MS 2500 + #define CSMA_RFENV_RECAL_LIMIT_DB -83 + bool interference_detected = false; + bool avoid_interference = true; + int csma_slot_ms = CSMA_SLOT_MIN_MS; + unsigned long difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms; + unsigned long difs_wait_start = -1; + unsigned long cw_wait_start = -1; + unsigned long cw_wait_target = -1; + unsigned long cw_wait_passed = 0; + int csma_cw = -1; + uint8_t cw_band = 1; + uint8_t cw_min = 0; + uint8_t cw_max = CSMA_CW_PER_BAND_WINDOWS; + + // LoRa settings + int lora_sf = 0; + int lora_cr = 5; + int lora_txp = 0xFF; + uint32_t lora_bw = 0; + uint32_t lora_freq = 0; + uint32_t lora_bitrate = 0; + + // Operational variables + bool radio_locked = true; + bool radio_online = false; + bool community_fw = true; + bool hw_ready = false; + bool radio_error = false; + bool disp_ready = false; + bool pmu_ready = false; + bool promisc = false; + bool implicit = false; + bool memory_low = false; + uint8_t implicit_l = 0; + + uint8_t op_mode = MODE_HOST; + uint8_t model = 0x00; + uint8_t hwrev = 0x00; + + #define NOISE_FLOOR_SAMPLES 128 + int noise_floor = -292; + int current_rssi = -292; + int last_rssi = -292; + uint8_t last_rssi_raw = 0x00; + uint8_t last_snr_raw = 0x80; + uint8_t seq = 0xFF; + uint16_t read_len = 0; + uint16_t host_write_len = 0; + + // Incoming packet buffer + uint8_t pbuf[MTU]; + + // KISS command buffer + uint8_t cmdbuf[CMD_L]; + + // LoRa transmit buffer + uint8_t tbuf[MTU]; + + uint32_t stat_rx = 0; + uint32_t stat_tx = 0; + + #define STATUS_INTERVAL_MS 3 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #define DCD_SAMPLES 2500 + #define UTIL_UPDATE_INTERVAL_MS 1000 + #define UTIL_UPDATE_INTERVAL (UTIL_UPDATE_INTERVAL_MS/STATUS_INTERVAL_MS) + #define AIRTIME_LONGTERM 3600 + #define AIRTIME_LONGTERM_MS (AIRTIME_LONGTERM*1000) + #define AIRTIME_BINLEN_MS (STATUS_INTERVAL_MS*DCD_SAMPLES) + #define AIRTIME_BINS ((AIRTIME_LONGTERM*1000)/AIRTIME_BINLEN_MS) + bool util_samples[DCD_SAMPLES]; + uint16_t airtime_bins[AIRTIME_BINS]; + float longterm_bins[AIRTIME_BINS]; + int dcd_sample = 0; + float local_channel_util = 0.0; + float total_channel_util = 0.0; + float longterm_channel_util = 0.0; + float airtime = 0.0; + float longterm_airtime = 0.0; + #define current_airtime_bin(void) (millis()%AIRTIME_LONGTERM_MS)/AIRTIME_BINLEN_MS + #endif + float st_airtime_limit = 0.0; + float lt_airtime_limit = 0.0; + bool airtime_lock = false; + + bool stat_signal_detected = false; + bool stat_signal_synced = false; + bool stat_rx_ongoing = false; + bool dcd = false; + bool dcd_led = false; + bool dcd_waiting = false; + long dcd_wait_until = 0; + uint16_t dcd_count = 0; + uint16_t dcd_threshold = 2; + + uint32_t status_interval_ms = STATUS_INTERVAL_MS; + uint32_t last_status_update = 0; + uint32_t last_dcd = 0; + + // Power management + #define BATTERY_STATE_UNKNOWN 0x00 + #define BATTERY_STATE_DISCHARGING 0x01 + #define BATTERY_STATE_CHARGING 0x02 + #define BATTERY_STATE_CHARGED 0x03 + bool battery_installed = false; + bool battery_indeterminate = false; + bool external_power = false; + bool battery_ready = false; + float battery_voltage = 0.0; + float battery_percent = 0.0; + uint8_t battery_state = 0x00; + uint8_t display_intensity = 0xFF; + uint8_t display_addr = 0xFF; + volatile bool display_updating = false; + bool display_blanking_enabled = false; + bool display_diagnostics = true; + bool device_init_done = false; + bool eeprom_ok = false; + bool firmware_update_mode = false; + bool serial_in_frame = false; + + // Boot flags + #define START_FROM_BOOTLOADER 0x01 + #define START_FROM_POWERON 0x02 + #define START_FROM_BROWNOUT 0x03 + #define START_FROM_JTAG 0x04 + +#endif diff --git a/vendor/rnode_firmware/Console.h b/vendor/rnode_firmware/Console.h new file mode 100644 index 0000000..c59d348 --- /dev/null +++ b/vendor/rnode_firmware/Console.h @@ -0,0 +1,203 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "SD.h" +#include "SPI.h" + +#if HAS_SD + SPIClass *spi = NULL; +#endif + + +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32c3/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/rtc.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif + +WebServer server(80); + +void console_dbg(String msg) { + Serial.print("[Webserver] "); + Serial.println(msg); +} + +bool exists(String path){ + bool yes = false; + File file = SPIFFS.open(path, "r"); + if(!file.isDirectory()){ + yes = true; + } + file.close(); + return yes; +} + +String console_get_content_type(String filename) { + if (server.hasArg("download")) { + return "application/octet-stream"; + } else if (filename.endsWith(".htm")) { + return "text/html"; + } else if (filename.endsWith(".html")) { + return "text/html"; + } else if (filename.endsWith(".css")) { + return "text/css"; + } else if (filename.endsWith(".js")) { + return "application/javascript"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } else if (filename.endsWith(".jpg")) { + return "image/jpeg"; + } else if (filename.endsWith(".ico")) { + return "image/x-icon"; + } else if (filename.endsWith(".xml")) { + return "text/xml"; + } else if (filename.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (filename.endsWith(".zip")) { + return "application/x-zip"; + } else if (filename.endsWith(".gz")) { + return "application/x-gzip"; + } else if (filename.endsWith(".whl")) { + return "application/octet-stream"; + } + return "text/plain"; +} + +bool console_serve_file(String path) { + console_dbg("Request for: "+path); + if (path.endsWith("/")) { + path += "index.html"; + } + + if (path == "/r/manual/index.html") { + path = "/m.html"; + } + if (path == "/r/manual/Reticulum Manual.pdf") { + path = "/h.html"; + } + + + String content_type = console_get_content_type(path); + String pathWithGz = path + ".gz"; + if (exists(pathWithGz) || exists(path)) { + if (exists(pathWithGz)) { + path += ".gz"; + } + + File file = SPIFFS.open(path, "r"); + console_dbg("Serving file to client"); + server.streamFile(file, content_type); + file.close(); + + console_dbg("File serving done\n"); + return true; + } else { + int spos = pathWithGz.lastIndexOf('/'); + if (spos > 0) { + String remap_path = "/d"; + remap_path.concat(pathWithGz.substring(spos)); + Serial.println(remap_path); + + if (exists(remap_path)) { + File file = SPIFFS.open(remap_path, "r"); + console_dbg("Serving remapped file to client"); + server.streamFile(file, content_type); + console_dbg("Closing file"); + file.close(); + + console_dbg("File serving done\n"); + return true; + } + } + } + + console_dbg("Error: Could not open file for serving\n"); + return false; +} + +void console_register_pages() { + server.onNotFound([]() { + if (!console_serve_file(server.uri())) { + server.send(404, "text/plain", "Not Found"); + } + }); +} + +void console_start() { + Serial.println(""); + console_dbg("Starting Access Point..."); + WiFi.softAP(bt_devname); + delay(150); + IPAddress ip(10, 0, 0, 1); + IPAddress nm(255, 255, 255, 0); + WiFi.softAPConfig(ip, ip, nm); + + if(!SPIFFS.begin(true)){ + console_dbg("Error: Could not mount SPIFFS"); + return; + } else { + console_dbg("SPIFFS Ready"); + } + + #if HAS_SD + spi = new SPIClass(HSPI); + spi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS); + if(!SD.begin(SD_CS, *spi)){ + console_dbg("No SD card inserted"); + } else { + uint8_t cardType = SD.cardType(); + if(cardType == CARD_NONE){ + console_dbg("No SD card type"); + } else { + console_dbg("SD Card Type: "); + if(cardType == CARD_MMC){ + console_dbg("MMC"); + } else if(cardType == CARD_SD){ + console_dbg("SDSC"); + } else if(cardType == CARD_SDHC){ + console_dbg("SDHC"); + } else { + console_dbg("UNKNOWN"); + } + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + } + } + #endif + + console_register_pages(); + server.begin(); + led_indicate_console(); +} + +void console_loop(){ + server.handleClient(); + // Internally, this yields the thread and allows + // other tasks to run. + delay(2); +} \ No newline at end of file diff --git a/vendor/rnode_firmware/Console/Makefile b/vendor/rnode_firmware/Console/Makefile new file mode 100644 index 0000000..f5a674a --- /dev/null +++ b/vendor/rnode_firmware/Console/Makefile @@ -0,0 +1,47 @@ +PATH_RETICULUM_WEBSITE=../../sites/reticulum.network +PATH_PACKAGES=../../dist_archive + +clean: + @echo Cleaning... + @-rm -rf ./build + +dirs: + @mkdir -p ./build + @mkdir -p ./build/3d + @mkdir -p ./build/pkg + @mkdir -p ./build/css + @mkdir -p ./build/gfx + @mkdir -p ./build/images + +pages: + python ./build.py + +pages-debug: + python ./build.py --no-gz --no-remap + +sourcepack: + @echo Packing firmware sources... + zip --junk-paths -r build/pkg/rnode_firmware.zip ../arduino-cli.yaml ../BLESerial.cpp ../BLESerial.h ../Bluetooth.h ../Boards.h ../Config.h ../Console.h ../Device.h ../Display.h ../Framing.h ../Graphics.h ../LICENSE ../Makefile ../MD5.cpp ../MD5.h ../partition_hashes ../Power.h ../README.md ../release_hashes.py ../RNode_Firmware.ino ../ROM.h ../sx126x.cpp ../sx126x.h ../sx127x.cpp ../sx127x.h ../sx128x.cpp ../sx128x.h ../Utilities.h ../esp32_btbufs.py + +data: + @echo Including assets... + @cp assets/css/* build/css/ + @cp assets/gfx/* build/gfx/ + @cp assets/images/* build/images/ + @cp assets/stl/* build/3d/ + #@cp assets/pkg/* build/pkg/ + # @cp assets/scripts/* build/scripts/ + # @cp -r ../../Reticulum/docs/manual/* build/reticulum_manual/ + # @cp -r ../../Reticulum/docs/Reticulum\ Manual.pdf build/reticulum_manual/ + +external: + make -C $(PATH_RETICULUM_WEBSITE) clean website + -rm -r $(PATH_PACKAGES)/reticulum.network + cp -r $(PATH_RETICULUM_WEBSITE)/build $(PATH_PACKAGES)/reticulum.network + +site: clean external dirs data sourcepack pages + +local: clean external dirs data sourcepack pages-debug + +serve: + python -m http.server 7777 --bind 127.0.0.1 --directory ./build diff --git a/vendor/rnode_firmware/Console/assets/css/water.css b/vendor/rnode_firmware/Console/assets/css/water.css new file mode 100644 index 0000000..d8bd13e --- /dev/null +++ b/vendor/rnode_firmware/Console/assets/css/water.css @@ -0,0 +1,1009 @@ +:root { + --background-body: #2a2a2f; + --background: #161f27; + --background-alt: #1a242f; + --selection: #1c76c5; + --text-main: #c0c6cc; + --text-bright: #dbe0e3; + /*--text-bright: #fff;*/ + --text-muted: #a9b1ba; + --links: #7eb7e1; + --focus: #0096bfab; + --border: #526980; + --code: #ffbe85; + --animation-duration: 0.1s; + --button-base: #0c151c; + --button-hover: #040a0f; + --scrollbar-thumb: var(--button-hover); + --scrollbar-thumb-hover: rgb(0, 0, 0); + --form-placeholder: #a9a9a9; + --form-text: #fff; + --variable: #d941e2; + --highlight: #efdb43; + --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); +} + +@font-face { + font-family: yond; + src: url(yond.woff2); +} + +html { + text-size-adjust: none; + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; +} + +body { + font-size: 1.1em; +} + +body button { + margin-bottom: 0.8em; + margin-left: 0.2em; + margin-left: 0.2em; +} + +body li { + margin-top: 0.4em; + margin-bottom: 0.4em; +} + +span.menu a { + text-decoration: none; +} + +body .logo { + font-family: "yond", "monospace"; + font-size: 5em; + text-align: center; + width: 100%; + display: block; + line-height: 0.75em; + color: #fff; + color: var(--text-bright); +} + +body a.topic_link { + color: #dbdbdb; + color: var(--text-main); +} + +body .article_date { + font-family: "yond", "monospace"; + font-size: 1.75em; + text-align: right; + line-height: 0.5em; + margin-right: -0.33em; + margin-bottom: -0.35em; +} + +body .article_photo { + width: 100%; +} + +body .topic { + display: inline-block; + vertical-align: top; + width: 48%; + margin-bottom: 2.5em; +} + +body .topic:nth-child(even) {margin-left: 2%} +body .topic:nth-child(odd) {margin-right: 2%} + +body .topic .topic_image { + display: block; + width: 100%; +} + +/*body .topic .topic_image { border: 1px solid #666; }*/ + +body .topic .topic_title { + display: block; + font-weight: 600; + font-size: 1.2em; + margin-top: 0.35em; + margin-bottom: 0.2em; +} + +body .topic .topic_date { + display: block; + font-size: 0.85em; + font-weight: 400; + font-style: oblique; + text-align: left; + margin-bottom: 1em; +} + +table#loracalc { + /*border: 1px solid #888;*/ + min-width: 50%; + width: auto; +} + +table#loracalc tbody tr td { + vertical-align: middle; + font-weight: bold; +} + +table#loracalc tbody tr td.lcfield { + padding-left: 5em; +} + +table#loracalc tbody tr:nth-child(2n) { + background-color: transparent; +} + +table#loracalc input { + display: inline-block; +} + + +html { + scrollbar-color: #040a0f #202b38; + scrollbar-color: var(--scrollbar-thumb) var(--background-body); + scrollbar-width: thin; +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; + line-height: 1.4; + max-width: 800px; + margin: 20px auto; + padding: 0 10px; + word-wrap: break-word; + color: #dbdbdb; + color: var(--text-main); + background: #202b38; + background: var(--background-body); + text-rendering: optimizeLegibility; +} + +button { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +input { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +textarea { + transition: + background-color 0.1s linear, + border-color 0.1s linear, + color 0.1s linear, + box-shadow 0.1s linear, + transform 0.1s ease; + transition: + background-color var(--animation-duration) linear, + border-color var(--animation-duration) linear, + color var(--animation-duration) linear, + box-shadow var(--animation-duration) linear, + transform var(--animation-duration) ease; +} + +h1 { + font-size: 1.7em; + margin-top: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 12px; + margin-top: 24px; +} + +h1 { + color: #fff; + color: var(--text-bright); +} + +h2 { + font-size: 1.4em; + color: #fff; + color: var(--text-bright); +} + +h3 { + color: #fff; + color: var(--text-bright); +} + +h4 { + color: #fff; + color: var(--text-bright); +} + +h5 { + color: #fff; + color: var(--text-bright); +} + +h6 { + color: #fff; + color: var(--text-bright); +} + +strong { + color: #fff; + color: var(--text-bright); +} + +h1, +h2, +h3, +h4, +h5, +h6, +b, +strong, +th { + font-weight: 600; +} + +q::before { + content: none; +} + +q::after { + content: none; +} + +blockquote { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +q { + border-left: 4px solid #0096bfab; + border-left: 4px solid var(--focus); + margin: 1.5em 0; + padding: 0.5em 1em; + font-style: italic; +} + +blockquote > footer { + font-style: normal; + border: 0; +} + +blockquote cite { + font-style: normal; +} + +address { + font-style: normal; +} + +a[href^='mailto\:']::before { + content: '📧 '; +} + +a[href^='tel\:']::before { + content: '📞 '; +} + +a[href^='sms\:']::before { + content: '💬 '; +} + +mark { + background-color: #efdb43; + background-color: var(--highlight); + border-radius: 2px; + padding: 0 2px 0 2px; + color: #000; +} + +a > code, +a > strong { + color: inherit; +} + +button, +select, +input[type='submit'], +input[type='reset'], +input[type='button'], +input[type='checkbox'], +input[type='range'], +input[type='radio'] { + cursor: pointer; +} + +input, +select { + display: block; +} + +[type='checkbox'], +[type='radio'] { + display: initial; +} + +input { + color: #fff; + color: var(--form-text); + background-color: #161f27; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +button { + color: #fff; + color: var(--form-text); + background-color: #161f27; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +textarea { + color: #fff; + color: var(--form-text); + background-color: #161f27; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +select { + color: #fff; + color: var(--form-text); + background-color: #161f27; + background-color: var(--background); + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline: none; +} + +button { + background-color: #0c151c; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +input[type='submit'] { + background-color: #0c151c; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +input[type='reset'] { + background-color: #0c151c; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +input[type='button'] { + background-color: #0c151c; + background-color: var(--button-base); + padding-right: 30px; + padding-left: 30px; +} + +button:hover { + background: #040a0f; + background: var(--button-hover); +} + +input[type='submit']:hover { + background: #040a0f; + background: var(--button-hover); +} + +input[type='reset']:hover { + background: #040a0f; + background: var(--button-hover); +} + +input[type='button']:hover { + background: #040a0f; + background: var(--button-hover); +} + +input[type='color'] { + min-height: 2rem; + padding: 8px; + cursor: pointer; +} + +input[type='checkbox'], +input[type='radio'] { + height: 1em; + width: 1em; +} + +input[type='radio'] { + border-radius: 100%; +} + +input { + vertical-align: top; +} + +label { + vertical-align: middle; + margin-bottom: 4px; + display: inline-block; +} + +input:not([type='checkbox']):not([type='radio']), +input[type='range'], +select, +button, +textarea { + -webkit-appearance: none; +} + +textarea { + display: block; + margin-right: 0; + box-sizing: border-box; + resize: vertical; +} + +textarea:not([cols]) { + width: 100%; +} + +textarea:not([rows]) { + min-height: 40px; + height: 140px; +} + +select { + background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; + background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; + padding-right: 35px; +} + +select::-ms-expand { + display: none; +} + +select[multiple] { + padding-right: 10px; + background-image: none; + overflow-y: auto; +} + +input:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +select:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +button:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +textarea:focus { + box-shadow: 0 0 0 2px #0096bfab; + box-shadow: 0 0 0 2px var(--focus); +} + +input[type='checkbox']:active, +input[type='radio']:active, +input[type='submit']:active, +input[type='reset']:active, +input[type='button']:active, +input[type='range']:active, +button:active { + transform: translateY(2px); +} + +input:disabled, +select:disabled, +button:disabled, +textarea:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +::-moz-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); +} + +:-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); +} + +::-ms-input-placeholder { + color: #a9a9a9; + color: var(--form-placeholder); +} + +::placeholder { + color: #a9a9a9; + color: var(--form-placeholder); +} + +fieldset { + border: 1px #0096bfab solid; + border: 1px var(--focus) solid; + border-radius: 6px; + margin: 0; + margin-bottom: 12px; + padding: 10px; +} + +legend { + font-size: 0.9em; + font-weight: 600; +} + +input[type='range'] { + margin: 10px 0; + padding: 10px 0; + background: transparent; +} + +input[type='range']:focus { + outline: none; +} + +input[type='range']::-webkit-slider-runnable-track { + width: 100%; + height: 9.5px; + -webkit-transition: 0.2s; + transition: 0.2s; + background: #161f27; + background: var(--background); + border-radius: 3px; +} + +input[type='range']::-webkit-slider-thumb { + box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #526980; + background: var(--border); + -webkit-appearance: none; + margin-top: -7px; +} + +input[type='range']:focus::-webkit-slider-runnable-track { + background: #161f27; + background: var(--background); +} + +input[type='range']::-moz-range-track { + width: 100%; + height: 9.5px; + -moz-transition: 0.2s; + transition: 0.2s; + background: #161f27; + background: var(--background); + border-radius: 3px; +} + +input[type='range']::-moz-range-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + height: 20px; + width: 20px; + border-radius: 50%; + background: #526980; + background: var(--border); +} + +input[type='range']::-ms-track { + width: 100%; + height: 9.5px; + background: transparent; + border-color: transparent; + border-width: 16px 0; + color: transparent; +} + +input[type='range']::-ms-fill-lower { + background: #161f27; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +input[type='range']::-ms-fill-upper { + background: #161f27; + background: var(--background); + border: 0.2px solid #010101; + border-radius: 3px; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; +} + +input[type='range']::-ms-thumb { + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + border: 1px solid #000; + height: 20px; + width: 20px; + border-radius: 50%; + background: #526980; + background: var(--border); +} + +input[type='range']:focus::-ms-fill-lower { + background: #161f27; + background: var(--background); +} + +input[type='range']:focus::-ms-fill-upper { + background: #161f27; + background: var(--background); +} + +a { + text-decoration: underline; + color: #41adff; + color: var(--links); +} + +a:hover { + text-decoration: underline; +} + +code { + background: #161f27; + background: var(--background); + color: #ffbe85; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +samp { + background: #161f27; + background: var(--background); + color: #ffbe85; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +time { + background: #161f27; + background: var(--background); + color: #ffbe85; + color: var(--code); + padding: 2.5px 5px; + border-radius: 6px; + font-size: 1em; +} + +pre > code { + padding: 10px; + display: block; + overflow-x: auto; +} + +var { + color: #d941e2; + color: var(--variable); + font-style: normal; + font-family: monospace; +} + +kbd { + background: #161f27; + background: var(--background); + border: 1px solid #526980; + border: 1px solid var(--border); + border-radius: 2px; + color: #dbdbdb; + color: var(--text-main); + padding: 2px 4px 2px 4px; +} + +img, +video { + max-width: 100%; + height: auto; + display: block; + margin-left: auto; + margin-right: auto; +} + +hr { + border: none; + border-top: 1px solid #526980; + border-top: 1px solid var(--border); +} + +table { + border-collapse: collapse; + margin-bottom: 10px; + width: 100%; + table-layout: fixed; +} + +table caption { + text-align: left; +} + +td, +th { + padding: 6px; + text-align: left; + vertical-align: top; + word-wrap: break-word; +} + +thead { + border-bottom: 1px solid #526980; + border-bottom: 1px solid var(--border); +} + +tfoot { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); +} + +tbody tr:nth-child(even) { + background-color: #161f27; + background-color: var(--background); +} + +tbody tr:nth-child(even) button { + background-color: #1a242f; + background-color: var(--background-alt); +} + +tbody tr:nth-child(even) button:hover { + background-color: #202b38; + background-color: var(--background-body); +} + +::-webkit-scrollbar { + height: 10px; + width: 10px; +} + +::-webkit-scrollbar-track { + background: #161f27; + background: var(--background); + border-radius: 6px; +} + +::-webkit-scrollbar-thumb { + background: #040a0f; + background: var(--scrollbar-thumb); + border-radius: 6px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgb(0, 0, 0); + background: var(--scrollbar-thumb-hover); +} + +::-moz-selection { + background-color: #1c76c5; + background-color: var(--selection); + color: #fff; + color: var(--text-bright); +} + +::selection { + background-color: #1c76c5; + background-color: var(--selection); + color: #fff; + color: var(--text-bright); +} + +details { + display: flex; + flex-direction: column; + align-items: flex-start; + background-color: #1a242f; + background-color: var(--background-alt); + padding: 10px 10px 0; + margin: 1em 0; + border-radius: 6px; + overflow: hidden; +} + +details[open] { + padding: 10px; +} + +details > :last-child { + margin-bottom: 0; +} + +details[open] summary { + margin-bottom: 10px; +} + +summary { + display: list-item; + background-color: #161f27; + background-color: var(--background); + padding: 10px; + margin: -10px -10px 0; + cursor: pointer; + outline: none; +} + +summary:hover, +summary:focus { + text-decoration: underline; +} + +details > :not(summary) { + margin-top: 0; +} + +summary::-webkit-details-marker { + color: #dbdbdb; + color: var(--text-main); +} + +dialog { + background-color: #1a242f; + background-color: var(--background-alt); + color: #dbdbdb; + color: var(--text-main); + border: none; + border-radius: 6px; + border-color: #526980; + border-color: var(--border); + padding: 10px 30px; +} + +dialog > header:first-child { + background-color: #161f27; + background-color: var(--background); + border-radius: 6px 6px 0 0; + margin: -10px -30px 10px; + padding: 10px; + text-align: center; +} + +dialog::-webkit-backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +dialog::backdrop { + background: #0000009c; + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +footer { + border-top: 1px solid #526980; + border-top: 1px solid var(--border); + padding-top: 10px; + color: #a9b1ba; + color: var(--text-muted); +} + +body > footer { + margin-top: 40px; +} + +@media print { + body, + pre, + code, + summary, + details, + button, + input, + textarea { + background-color: #fff; + } + + button, + input, + textarea { + border: 1px solid #000; + } + + body, + h1, + h2, + h3, + h4, + h5, + h6, + pre, + code, + button, + input, + textarea, + footer, + summary, + strong { + color: #000; + } + + summary::marker { + color: #000; + } + + summary::-webkit-details-marker { + color: #000; + } + + tbody tr:nth-child(even) { + background-color: #f2f2f2; + } + + a { + color: #00f; + text-decoration: underline; + } +} + +#load_overlay { + display: none; +} diff --git a/vendor/rnode_firmware/Console/assets/css/yond.woff2 b/vendor/rnode_firmware/Console/assets/css/yond.woff2 new file mode 100644 index 0000000..88e8992 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/css/yond.woff2 differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/cs.webp b/vendor/rnode_firmware/Console/assets/gfx/cs.webp new file mode 100644 index 0000000..efa6f74 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/cs.webp differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/icon.png b/vendor/rnode_firmware/Console/assets/gfx/icon.png new file mode 100644 index 0000000..e8bf5d6 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/icon.png differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/nn.webp b/vendor/rnode_firmware/Console/assets/gfx/nn.webp new file mode 100644 index 0000000..27d19d6 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/nn.webp differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/ph.png b/vendor/rnode_firmware/Console/assets/gfx/ph.png new file mode 100644 index 0000000..b25b291 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/ph.png differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/rnode_iso.webp b/vendor/rnode_firmware/Console/assets/gfx/rnode_iso.webp new file mode 100644 index 0000000..d69331a Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/rnode_iso.webp differ diff --git a/vendor/rnode_firmware/Console/assets/gfx/sideband.webp b/vendor/rnode_firmware/Console/assets/gfx/sideband.webp new file mode 100644 index 0000000..308b9d1 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/gfx/sideband.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/3_conv.webp b/vendor/rnode_firmware/Console/assets/images/3_conv.webp new file mode 100644 index 0000000..85d0ecd Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/3_conv.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/an1.webp b/vendor/rnode_firmware/Console/assets/images/an1.webp new file mode 100644 index 0000000..d8ae21e Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/an1.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/bg1ds1.webp b/vendor/rnode_firmware/Console/assets/images/bg1ds1.webp new file mode 100644 index 0000000..d878ae1 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/bg1ds1.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/bg1ds2.webp b/vendor/rnode_firmware/Console/assets/images/bg1ds2.webp new file mode 100644 index 0000000..4e9cdc9 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/bg1ds2.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/bg_h_1.webp b/vendor/rnode_firmware/Console/assets/images/bg_h_1.webp new file mode 100644 index 0000000..9fa57e3 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/bg_h_1.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/bg_h_2.webp b/vendor/rnode_firmware/Console/assets/images/bg_h_2.webp new file mode 100644 index 0000000..3057d68 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/bg_h_2.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/g1p.webp b/vendor/rnode_firmware/Console/assets/images/g1p.webp new file mode 100644 index 0000000..e1465ee Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/g1p.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/g2p.webp b/vendor/rnode_firmware/Console/assets/images/g2p.webp new file mode 100644 index 0000000..d645739 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/g2p.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/g3p.webp b/vendor/rnode_firmware/Console/assets/images/g3p.webp new file mode 100644 index 0000000..e9dac9f Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/g3p.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/g4p.webp b/vendor/rnode_firmware/Console/assets/images/g4p.webp new file mode 100644 index 0000000..523c0cd Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/g4p.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/lora_rnodes.webp b/vendor/rnode_firmware/Console/assets/images/lora_rnodes.webp new file mode 100644 index 0000000..d475b43 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/lora_rnodes.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/nn_an.webp b/vendor/rnode_firmware/Console/assets/images/nn_an.webp new file mode 100644 index 0000000..63c4245 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/nn_an.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/nn_conv.webp b/vendor/rnode_firmware/Console/assets/images/nn_conv.webp new file mode 100644 index 0000000..c66d18a Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/nn_conv.webp differ diff --git a/vendor/rnode_firmware/Console/assets/images/nn_init.webp b/vendor/rnode_firmware/Console/assets/images/nn_init.webp new file mode 100644 index 0000000..3b3a46f Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/images/nn_init.webp differ diff --git a/vendor/rnode_firmware/Console/assets/stl/Handheld_RNode_Parts.7z b/vendor/rnode_firmware/Console/assets/stl/Handheld_RNode_Parts.7z new file mode 100644 index 0000000..df861b6 Binary files /dev/null and b/vendor/rnode_firmware/Console/assets/stl/Handheld_RNode_Parts.7z differ diff --git a/vendor/rnode_firmware/Console/build.py b/vendor/rnode_firmware/Console/build.py new file mode 100644 index 0000000..ec0fb7b --- /dev/null +++ b/vendor/rnode_firmware/Console/build.py @@ -0,0 +1,394 @@ +import markdown +import os +import sys +import shutil + +packages = { + "rns": "rns-1.1.9-py3-none-any.whl", + "nomadnet": "nomadnet-0.9.11-py3-none-any.whl", + "lxmf": "lxmf-0.9.6-py3-none-any.whl", + "rnsh": "rnsh-0.1.9-py3-none-any.whl", +} + +DEFAULT_TITLE = "RNode Bootstrap Console" +SOURCES_PATH="./source" +BUILD_PATH="./build" +PACKAGES_PATH = "../../dist_archive" +RNS_SOURCE_PATH = "../../Reticulum" +INPUT_ENCODING="utf-8" +OUTPUT_ENCODING="utf-8" + +LXMF_ADDRESS = "8dd57a738226809646089335a6b03695" + +document_start = """ + + + + + + +{PAGE_TITLE} + + + +
+ +{MENU}
""" + +document_end = """""" + +menu_md = """
[Start]({CONTENT_PATH}index.html) | [Replicate]({CONTENT_PATH}replicate.html) | [Software]({CONTENT_PATH}software.html) | [Learn]({CONTENT_PATH}learn.html) | [Help](help.html) | [Contribute]({CONTENT_PATH}contribute.html)
""" + +manual_redirect = """ + + + + + + +""" +help_redirect = """ + + + + + + +""" + +url_maps = [ + # { "path": "", "target": "/.md"}, +] + +def scan_pages(base_path): + files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "."] + directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."] + + page_sources = [] + + for file in files: + if file.endswith(".md"): + page_sources.append(base_path+"/"+file) + + for directory in directories: + page_sources.extend(scan_pages(base_path+"/"+directory)) + + return page_sources + +def get_prop(md, prop): + try: + pt = "["+prop+"]: <> (" + pp = md.find(pt) + if pp != -1: + ps = pp+len(pt) + pe = md.find(")", ps) + return md[ps:pe] + else: + return None + + except Exception as e: + print("Error while extracting topic property: "+str(e)) + return None + +def list_topic(topic): + base_path = SOURCES_PATH+"/"+topic + files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "." and file != "index.md"] + + topic_entries = [] + for file in files: + if file.endswith(".md"): + fp = base_path+"/"+file + f = open(fp, "rb") + link_path = fp.replace(SOURCES_PATH, ".").replace(".md", ".html") + + md = f.read().decode(INPUT_ENCODING) + topic_entries.append({ + "title": get_prop(md, "title"), + "image": get_prop(md, "image"), + "date": get_prop(md, "date"), + "excerpt": get_prop(md, "excerpt"), + "md": md, + "file": link_path + }) + + topic_entries.sort(key=lambda e: e["date"], reverse=True) + return topic_entries + +def render_topic(topic_entries): + md = "" + for topic in topic_entries: + md += "" + md += "" + md += "" + md += ""+str(topic["title"])+"" + #md += ""+str(topic["date"])+"" + md += ""+str(topic["excerpt"])+"" + md += "" + md += "" + + + return md + +def generate_html(f, root_path): + md = f.read().decode(INPUT_ENCODING) + + page_title = get_prop(md, "title") + if page_title == None: + page_title = DEFAULT_TITLE + else: + page_title += " | "+DEFAULT_TITLE + + tt = "{TOPIC:" + tp = md.find(tt) + if tp != -1: + ts = tp+len(tt) + te = md.find("}", ts) + topic = md[ts:te] + + rt = tt+topic+"}" + tl = render_topic(list_topic(topic)) + print("Found topic: "+str(topic)+", rt "+str(rt)) + md = md.replace(rt, tl) + + menu_html = markdown.markdown(menu_md.replace("{CONTENT_PATH}", root_path), extensions=["md_in_html", "markdown.extensions.fenced_code", "sane_lists"]).replace("

", "") + page_html = markdown.markdown(md, extensions=["md_in_html", "markdown.extensions.fenced_code"]).replace("{ASSET_PATH}", root_path) + page_html = page_html.replace("{LXMF_ADDRESS}", LXMF_ADDRESS) + for pkg_name in packages: + page_html = page_html.replace("{PKG_"+pkg_name+"}", "pkg/"+pkg_name+".zip") + page_html = page_html.replace("{PKG_BASE_"+pkg_name+"}", pkg_name+".zip") + page_html = page_html.replace("{PKG_NAME_"+pkg_name+"}", packages[pkg_name]) + + page_date = get_prop(md, "date") + if page_date != None: + page_html = page_html.replace("{DATE}", page_date) + + return document_start.replace("{ASSET_PATH}", root_path).replace("{MENU}", menu_html).replace("{PAGE_TITLE}", page_title) + page_html + document_end + +source_files = scan_pages(SOURCES_PATH) + +mf = open(BUILD_PATH+"/m.html", "w") +mf.write(manual_redirect) +mf.close() +mf = open(BUILD_PATH+"/h.html", "w") +mf.write(help_redirect) +mf.close() + +def optimise_manual(path): + pm = 180 + scale_imgs = [ + ("_images/board_rnodev2.png", pm), + ("_images/board_rnode.png", pm), + ("_images/board_heltec32v20.png", pm), + ("_images/board_heltec32v30.png", pm), + ("_images/board_heltec32v4.png", pm), + ("_images/board_t3v21.png", pm), + ("_images/board_t3v20.png", pm), + ("_images/board_t3v10.png", pm), + ("_images/board_t3s3.png", pm), + ("_images/board_tbeam.png", pm), + ("_images/board_tdeck.png", pm), + ("_images/board_rak4631.png", pm), + ("_images/board_tbeam_supreme.png", pm), + ("_images/sideband_devices.webp", pm), + ("_images/nomadnet_3.png", pm), + ("_images/meshchat_1.webp", pm), + ("_images/radio_is5ac.png", pm), + ("_images/radio_rblhg5.png", pm), + ("_images/rbrowser.webp", pm), + ("_images/rnphone.webp", pm), + ("_images/retibbs.webp", pm), + ("_images/meshchatx.webp", pm), + ("_images/lxst_phone.webp", pm), + ("_images/columba.webp", pm), + ("_static/rns_logo_512.png", 256), + ("../images/bg_h_1.webp", pm), + + ("../../images/3_conv.webp", pm/2), + ("../../images/an1.webp", pm/2), + ("../../images/bg1ds1.webp", pm/2), + ("../../images/bg1ds2.webp", pm/2), + ("../../images/bg_h_1.webp", pm/2), + ("../../images/bg_h_2.webp", pm/2), + ("../../images/g1p.webp", pm/2), + ("../../images/g2p.webp", pm/2), + ("../../images/g3p.webp", pm/2), + ("../../images/g4p.webp", pm/2), + ("../../images/lora_rnodes.webp", pm/2), + ("../../images/nn_an.webp", pm/2), + ("../../images/nn_conv.webp", pm/2), + ("../../images/nn_init.webp", pm/2), + ] + + import subprocess + import shlex + for i,s in scale_imgs: + fp = path+"/"+i + input_file = fp + output_file = input_file + resize = "convert "+input_file+" -quality 25 -resize "+str(s)+" "+output_file + print(resize) + subprocess.call(shlex.split(resize), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # if output_file != input_file and os.path.isfile(input_file): os.unlink(input_file) + + remove_files = [ + "objects.inv", + "Reticulum Manual.pdf", + "Reticulum Manual.epub", + "_static/styles/furo.css.map", + "_static/scripts/furo.js.map", + "_static/jquery-3.6.0.js", + "_static/jquery.js", + "static/underscore-1.13.1.js", + "_static/_sphinx_javascript_frameworks_compat.js", + "_static/scripts/furo.js.LICENSE.txt", + "_static/styles/furo-extensions.css.map", + "_images/board_rak4631.png", + "_images/board_rnodev2.png", + "_images/board_t114.png", + "_images/board_t3s3.png", + "_images/board_t3v10.png", + "_images/board_t3v20.png", + "_images/board_t3v21.png", + "_images/board_tbeam.png", + "_images/board_tdeck.png", + "_images/board_techo.png", + "_images/board_tbeam_supreme.png", + "_images/board_opencomxl.png", + "_images/board_heltec32v20.png", + "_images/board_heltec32v30.png", + # "_static/pygments.css", + # "_static/language_data.js", + # "_static/searchtools.js", + # "searchindex.js", + ] + for file in remove_files: + fp = path+"/"+file + print("Removing file: "+str(fp)) + try: + os.unlink(fp) + except Exception as e: + print("An error occurred while attempting to unlink "+str(fp)+": "+str(e)) + + remove_dirs = [ + "_sources", + ] + for d in remove_dirs: + fp = path+"/"+d + print("Removing dir: "+str(fp)) + shutil.rmtree(fp) + + shutil.move(path, BUILD_PATH+"/m") + +def fetch_reticulum_site(): + r_site_path = BUILD_PATH+"/r" + if not os.path.isdir(r_site_path): + shutil.copytree(PACKAGES_PATH+"/reticulum.network", r_site_path) + if os.path.isdir(r_site_path+"/manual"): + optimise_manual(r_site_path+"/manual") + remove_files = [ + "gfx/reticulum_logo_512.png", + ] + for file in remove_files: + fp = r_site_path+"/"+file + print("Removing file: "+str(fp)) + os.unlink(fp) + replace_paths() + +def replace_paths(): + repls = [ + ("gfx/reticulum_logo_512.png", "/m/_static/rns_logo_512.png") + ] + for root, dirs, files in os.walk(BUILD_PATH): + for file in files: + fpath = root+"/"+file + if fpath.endswith(".html"): + print("Performing replacements in "+fpath+"") + f = open(fpath, "rb") + html = f.read().decode("utf-8") + f.close() + for s,r in repls: + html = html.replace(s,r) + f = open(fpath, "wb") + f.write(html.encode("utf-8")) + f.close() + + # if not os.path.isdir(BUILD_PATH+"/d"): + # os.makedirs(BUILD_PATH+"/d") + # shutil.move(fpath, BUILD_PATH+"/d/") + + +def remap_names(): + for root, dirs, files in os.walk(BUILD_PATH): + for file in files: + fpath = root+"/"+file + spath = fpath.replace(BUILD_PATH, "") + if len(spath) > 31: + print("Path "+spath+" is too long, remapping...") + if not os.path.isdir(BUILD_PATH+"/d"): + os.makedirs(BUILD_PATH+"/d") + shutil.move(fpath, BUILD_PATH+"/d/") + + + +def gz_all(): + import gzip + for root, dirs, files in os.walk(BUILD_PATH): + for file in files: + fpath = root+"/"+file + print("Gzipping "+fpath+"...") + f = open(fpath, "rb") + g = gzip.open(fpath+".gz", "wb") + g.writelines(f) + g.close() + f.close() + os.unlink(fpath) + +from zipfile import ZipFile +for pkg_name in packages: + pkg_file = packages[pkg_name] + pkg_full_path = PACKAGES_PATH+"/"+pkg_file + if os.path.isfile(pkg_full_path): + print("Including "+pkg_file) + z = ZipFile(BUILD_PATH+"/pkg/"+pkg_name+".zip", "w") + z.write(pkg_full_path, pkg_full_path[len(PACKAGES_PATH+"/"):]) + z.close() + # shutil.copy(pkg_full_path, BUILD_PATH+"/"+pkg_name) + + else: + print("Could not find "+pkg_full_path) + exit(1) + +for um in url_maps: + with open(SOURCES_PATH+"/"+um["target"], "rb") as f: + of = BUILD_PATH+um["target"].replace(SOURCES_PATH, "").replace(".md", ".html") + root_path = "../" + html = generate_html(f, root_path) + + print("Map path : "+str(um["path"])) + print("Map target : "+str(um["target"])) + print("Mapped root path: "+str(root_path)) + + if not os.path.isdir(BUILD_PATH+"/"+um["path"]): + os.makedirs(BUILD_PATH+"/"+um["path"], exist_ok=True) + + with open(BUILD_PATH+"/"+um["path"]+"/index.html", "wb") as wf: + wf.write(html.encode(OUTPUT_ENCODING)) + +for mdf in source_files: + with open(mdf, "rb") as f: + of = BUILD_PATH+mdf.replace(SOURCES_PATH, "").replace(".md", ".html") + root_path = "../"*(len(of.replace(BUILD_PATH+"/", "").split("/"))-1) + html = generate_html(f, root_path) + + if not os.path.isdir(os.path.dirname(of)): + os.makedirs(os.path.dirname(of), exist_ok=True) + + with open(of, "wb") as wf: + wf.write(html.encode(OUTPUT_ENCODING)) + +fetch_reticulum_site() +if not "--no-gz" in sys.argv: + gz_all() + +if not "--no-remap" in sys.argv: + remap_names() diff --git a/vendor/rnode_firmware/Console/source/builds/ap.md b/vendor/rnode_firmware/Console/source/builds/ap.md new file mode 100644 index 0000000..73f5741 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/builds/ap.md @@ -0,0 +1,8 @@ +[date]: <> (2023-01-12) +[title]: <> (Outdoor RNode) +[image]: <> (gfx/cs.webp) +[excerpt]: <> (An outdoor-mountable RNode suitable for Access Point or network extension operation. Also supports high-capacity batteries and solar charging.) +## Outdoor AP RNode +This RNode comes with a weather-proof case and convenient cable management options, suitable for outdoor mounting and operation. It is possible to mount this RNode directly to masts and antennas, and it supports high-capacity batteries and solar charging. + +This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! diff --git a/vendor/rnode_firmware/Console/source/builds/handheld.md b/vendor/rnode_firmware/Console/source/builds/handheld.md new file mode 100644 index 0000000..d2c0663 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/builds/handheld.md @@ -0,0 +1,168 @@ +[date]: <> (2023-01-14) +[title]: <> (Handheld RNode) +[image]: <> (gfx/rnode_iso.webp) +[excerpt]: <> (This RNode is suitable for mobile and handheld operation, and offers both wireless and wired connectivity to host devices. A good all-round unit. It is also suitable for permanent installation indoors.) +## Handheld RNode Recipe +*Version 2.1* + +This build recipe will help you create an RNode that is suitable for mobile and handheld operation, and offers both wireless and wired connectivity to host devices. It is also useful for permanent installation indoors, or even outdoors, as long as it is protected from water ingress and direct sunlight. + +Depending on the board you use, it will offer a workable frequency range between **420 and 520 MHz**, or **820 and 1020 MHz**, and a maximum TX power of **17 dBm** (50 mW). + +Completed Handheld RNode +
*A completed Handheld RNode*
+ +### Table of Contents + +1. [Preparation](#prep) +2. [Supported Boards](#devboard) +3. [Materials](#materials) +4. [Print Parts](#parts) +5. [Install Tools](#tools) +6. [Firmware Setup](#firmware) +7. [Assembly](#assembly) + + +### Step 1: Preparation + +When you have completed this recipe, you will end up with a fully-featured RNode device, similar to the one pictured above. To make it as easy as possible to complete this guide, make sure to read it all in its entirity *before* starting. I also recommend you familiarise yourself with the required materials, and the software tools needed for the setup. + +To complete this build recipe, you will need access to the following items: + +- A computer with a functional operating system, such as Linux, BSD or macOS +- One of the [supported development boards](#devboard) for this recipe +- A suitable USB cable for connecting the development board to your computer +- A 3D printer and the necessary amount of material for printing the [device parts](#parts) +- 6 pieces of M2x6mm screws to assemble the case +- A suitable antenna +- An optional NeoPixel RGB LED +- An optional [battery](#battery) + - This build can use any single-cell (3.7v) lithium battery with a 1.25mm JST connector, provided it will fit in the case. Please see [this section](#battery) for details on battery sizes. + +### Step 2: Supported Development Boards + +This RNode design is using a **LilyGO LoRa32 v2.1** board, in either the **433 MHz**, **868 MHz**, **915 MHz** or **923 MHz** variants. It seems that the 868, 915 and 923 MHz variants are in fact completely identical, and all offer a frequency range between 820 and 1020 MHz. The 433MHz variants offer a frequency range between 420 and 520 MHz. + +These boards are also sold under many different "brand" names other than LilyGO, but using the images below, you should be able to identify the correct ones. + +It is easiest to obtain the version of the board with an **u.FL** (sometimes also labeled *IPX* or *IPEX*) antenna connector, instead of the **SMA** connector. This version comes with an SMA to u.FL pigtail, which is installed into the 3D-printed case. If it is not possible to obtain this version, you can use the one with an **SMA** connector, either as is, or by removing the **SMA** connector, and using the on-board **u.FL** connector instead. + +If you do not wish to use the 3D-printable case included in this guide, it does not matter which version you get. There is **no functional difference** between the boards with **SMA** and **u.FL** connectors. + +Compatible board +
*The correct board version for this RNode build recipe*
+ +If you want to use the case provided for this build guide, and you have the version with an *SMA* connector, you will have to desolder the **SMA** connector, and activate the *u.FL* connector instead (it's already installed on all the boards, just not activated on the **SMA** connector versions). + +To activate the **u.FL** connector, you will just have to "rotate" the small resistor next to the antenna connectors by 90 degrees, so it "points" at the connector you wish to use. + +Please note that the "resistor" is actually just a zero-ohm jumper. If you don't feel like fiddling around with small components, you can simply remove it, and bridge the relevant gap with a blob of solder. + +Refer to the following two pictures to locate the resistor that needs moving: + +Before desoldering +After desoldering +
*Before and after removing the SMA connector and moving the resistor*
+ +You will also need to dismount the OLED display from the small acrylic riser on the board, and unscrew and discard the riser. Be careful not to damage the display or ribbon cable while doing this. The OLED display will be mounted directly into a matching slot in the 3D-printed case. + +As before, if you do not want to use the 3D printed case supplied here, it's probably much easier to keep the display on the board, and you can simply skip this step. + +### Step 3: Obtain Materials + +In addition to the board, you will need a few other components to build this RNode. + +- A suitable **antenna**. Most boards purchased online include a passable antenna, but you may want to upgrade it to a better one. +- 6 pieces of **M2x6mm screws** for assembling the case. Can be bought in most hardware stores or from online vendors. +- An optional **NeoPixel RGB LED** for displaying status, and TX/RX activity. If you do not want to add this, it can simply be omitted. + - The easiest way is to use the PCB-mounted NeoPixel "mini-buttons" manufactured by [adafruit.com](https://www.adafruit.com/product/1612). These fit exactly into the slot in the mounting position in the 3D-printed case, and are easy to connect cables to. +- An optional **lithium-polymer battery**. + - This RNode supports **3.7v**, **single-cell** LiPo batteries with a **1.25mm JST connector** + - The standard case can fit up to a 700mAh LP602248 battery + - Maximum battery dimensions for this case is 50mm x 25mm x 6mm + - There is a larger bottom casing available that fits 1100mAh batteries + - Maximum battery dimensions for this case is 50mm x 25mm x 12mm + +### Step 4: 3D Print Parts + +To complete the build of this RNode, you will need to 3D-print the parts for the casing. Download, extract and slice the STL files from the [parts package]({ASSET_PATH}3d/Handheld_RNode_Parts.7z) in your preferred software. + +- Two of the parts are LED light-guides, and should be printed in a semi-translucent material: + - The `LED_Window.stl` file is a light-guide for the NeoPixel LED, mounted in the circular cutout at the top of the device. + - The `LED_Guide.stl` file is a light-guide for the power and charging LEDs, mounted in the rectangular grove at the bottom of the device. +- The rest of the parts can be printed in any material, but for durability and heat-resistance, PETG is recommended. + - The `Power_Switch.stl` file is a small power-switch slider, mounted in the matching grove on the bottom-left of the device. + - The `Case_Top.stl` file is the top shell of the case. It holds the OLED display and NeoPixel RGB LED, and mounts to the bottom shell of the case with 6 M2 screws. The screw holes in both the top and bottom shells of the case are dimensioned to be self-threading when screws are inserted for the first time. Do not over-tighten. + - The `Case_Bottom_Small_Battery.stl` file is the default bottom shell of the case. It holds batteries up to approximately 700mAh. + - The `Case_Bottom_Large_Battery.stl` file is an alternative bottom shell for the case. It holds batteries up to approximately 1100mAh. + - The `Case_Bottom_No_Battery.stl` file is an alternative bottom shell for the case. It does not have space for a battery, but results in a very compact device. + - The `Case_Battery_Door.stl` file is the door for the battery compartment of the device. It snap-fits tightly into place in the bottom shell, and features a small slot for opening with a flathead screwdriver or similar. + +All files are dimensioned to fit together perfectly without any scaling on a well-tuned 3D-printer. + +The recommended layer height for all files is 0.15mm for FDM printers. + +### Step 5: Install Tools + +To install and configure the RNode Firmware on the device, you will need to install the `rnodeconf` program on your computer. This is included in the `rns` package, that can be [installed directly from this RNode]({ASSET_PATH}s_rns.html). Please carry out the installation instructions on [this page]({ASSET_PATH}s_rns.html), and continue to the next step when the `rnodeconf` program is installed. + + +### Step 6: Firmware Setup + +Once the `rnodeconf` program is installed, we will use it to install the RNode Firmware on your device, and do the initial provisioning of configuration parameters. This process can be completed automatically, by using the auto-installer. Run the `rnodeconf` auto-installer with the following command: + +``` +rnodeconf --autoinstall +``` + +1. The program will ask you to connect your device to an USB-port on your computer. Do so, and hit enter. +2. Select the serial port the device is connected as. +3. You will now be asked what device this is, select the option **A Specific Kind of RNode**. +4. The installer will ask you what model your device is. Select the **Handheld RNode v2.x** option that matches the frequency band of your device. +5. The installer will display a summary of your choices. If you are satisfied, confirm your selection. +6. The installer will now automatically install and configure the firmware and prepare the device for use. + +> **Please Note!** If you are connected to the Internet while installing, the autoinstaller will automatically download any needed firmware files to a local cache before installing. + +> If you do not have an active Internet connection while installing, you can extract and use the firmware from this device instead. This will **only** work if you are building the same type of RNode as the device you are extracting from, as the firmware has to match the targeted board and hardware configuration. + +If you need to extract the firmware from an existing RNode, run the following command: + +``` +rnodeconf --extract +``` + +If `rnodeconf` finds a working RNode, it will extract and save the firmware from the device for later use. You can then run the auto-installer with the `--use-extracted` option to use the locally extracted file: + +``` +rnodeconf --autoinstall --use-extracted +``` + +This also works for updating the firmware on existing RNodes, so you can extract a newer firmware from one RNode, and deploy it onto other RNodes using the same method. Just use the `--update` option instead of `--autoinstall`. + +### Step 7: Assembly + +With the firmware installed and configured, and the case parts printed, it's time to put it all together. + +1. Insert the **SMA to u.FL** pigtail adatper into the matching **slot** in the top part of the bottom shell. Make sure it lines up with the internal hex-nut cut-out in the bottom shell, as the hex nut of the adapter will get pulled into this cut-out, and thereby self-lock, when an antenna is connected. You can optionally mount a locking nut on the exterior thread of the SMA connector when the case has been completely assembled. +2. Thread the cable of the **SMA to u.FL** pigtail adapter into the matching grove, and run it out of the bottom opening. +3. Mount the **power-switch slider** into the matching slot, in the bottom-left part of the bottom shell. +4. With the SMA connector and power switch mounted, slide the **board** into the bottom shell, such that the **power switch** of the **board** mates with the slot in the already installed power-switch slider. Click the **board** into place in the bottom shell. +5. Optionally mount the **NeoPixel LED**: + - Measure out cables that matches lenghts between the NeoPixel mounting slot, and the corresponding pins on the board. + - Solder the **V+**, **GND** and **DATA** cables to the NeoPixel. + - Solder the **V+** cable to the **3.3v** pin on the board. + - Solder the **GND** cable to the **GND** pin on the board. + - Solder the **DATA** cable to **IO Pin 12** on the board. + - Mount the **NeoPixel** in the circular slot in the top part of the top shell. +6. Carefully mount the OLED display in the rectangular slot in the middle part of the top shell. +7. While ensuring that all internal cables stay within their routing groves, place the **top shell** on top of the **bottom shell**, making sure that the screw-mounting holes line up. +8. Mount the 6 **M2x6mm screws** into the mounting holes, until the two shells of the case are tightly and securely connected. +9. Flip over the device. +10. Connect the male **u.FL** connector to the female **u.FL** socket on the **board**. +11. Optionally, connect the male JST connector of the **battery** to the female JST connector on the **board**. +12. Fit the **battery door** into place. + +Congratulations, Your Handheld RNode is now complete! + +Flip the power switch, and start using it! \ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/builds/micropylon.md b/vendor/rnode_firmware/Console/source/builds/micropylon.md new file mode 100644 index 0000000..bf70c51 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/builds/micropylon.md @@ -0,0 +1,8 @@ +[date]: <> (2023-01-09) +[title]: <> (Reticulum MicroPylon) +[image]: <> (gfx/cs.webp) +[excerpt]: <> (A powerful, solar-powered multi-transceiver RNode-based radio system for autonomous and self-configuring Reticulum network deployments.) +## Reticulum MicroPylon +This radio system is a powerful and flexible multi-transceiver radio system, designed for rapidly deploying autonomous and self-configuring Reticulum networks over wide areas. + +This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! diff --git a/vendor/rnode_firmware/Console/source/builds/wallmount.md b/vendor/rnode_firmware/Console/source/builds/wallmount.md new file mode 100644 index 0000000..e775099 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/builds/wallmount.md @@ -0,0 +1,6 @@ +[date]: <> (2023-01-10) +[title]: <> (Wall-Mount RNode) +[image]: <> (gfx/cs.webp) +[excerpt]: <> (A sleek, wall-mountable RNode, suitable for permanent installation and operation indoors, or in a semi-protected environment outdoors.) +## Wall-Mount RNode +This build recipe will be released soon. Please [support the project]({ASSET_PATH}contribute.html) to help realise it! diff --git a/vendor/rnode_firmware/Console/source/contact.md b/vendor/rnode_firmware/Console/source/contact.md new file mode 100644 index 0000000..ea366f2 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/contact.md @@ -0,0 +1,20 @@ +[title]: <> (Contact) +# Contact Me + +**Hello!** I am the creator of the RNode ecosystem. + +If you have any general questions or comments about any of the projects I maintain, I encourage you to post it in one of the following places: + +- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub +- The [Reticulum Matrix Channel](#reticulum:matrix.org) at `#reticulum:matrix.org` +- The [Reticulum subreddit](https://reddit.com/r/reticulum) + +To get in touch with me personally, you can use one of the following methods, in order of preference: + +- LXMF at `8dd57a738226809646089335a6b03695` +- Matrix using `@unsignedmark:matrix.org` +- Email by using the address mark at unsigned dot io + +Please use the public forums and channels for support and help requests. I receive a lot of messages, and while I try to answer everyone (eventually), this is not always possible. + +
`3502`
\ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/contribute.md b/vendor/rnode_firmware/Console/source/contribute.md new file mode 100644 index 0000000..2f8b662 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/contribute.md @@ -0,0 +1,39 @@ +[title]: <> (Donate) +## Keep Communications Free and Open +Please take part in keeping the continued development, maintenance and distribution of the RNode ecosystem possible by donating via one of the following channels: + +- Monero
+ ``` + 84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w + ``` +

+- Ethereum
+ ``` + 0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73 + ``` +

+- Bitcoin
+ ``` + 35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH + ``` +

+- Ko-Fi
+ `https://ko-fi.com/markqvist` + +## Spread Knowledge and Awareness +Another great way to contribute, is to spread awareness about the RNode project. Here's some ideas: + +- Introduce the concepts of Free & Open Communications Systems to your community +- Teach others to build and use RNodes, and how to set up resilient and private communications systems +- Learn about using Reticulum to set up resilient communications networks, and teach these skills to people in your area that need them + +## Contribute Code & Material +If you like to write, build and design, there are plenty of oppertunities to take part in the community around RNode, and the wider Reticulum community as well. + +There's always plenty of work to do, from writing code, tutorials and guides, to designing parts, devices and integrations, and translating material to other languages. + +You can find us the following places: + +- The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org` +- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub +- The [Reticulum subreddit](https://reddit.com/r/reticulum) \ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/guides/install_firmware.md b/vendor/rnode_firmware/Console/source/guides/install_firmware.md new file mode 100644 index 0000000..0d77695 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/guides/install_firmware.md @@ -0,0 +1,123 @@ +[date]: <> (2023-01-12) +[title]: <> (Installing RNode Firmware on Supported Devices) +[image]: <> (images/g2p.webp) +[excerpt]: <> (If you have a T-Beam or LoRa32 device handy, it is very easy to get it set up for all the things that the RNode firmware allows you to do.) + +# Installing RNode Firmware on Supported Devices + +Do you have one of the devices available that the RNode Firmware supports? In that case, it is very easy to turn it into a working RNode by using the `rnodeconf` autoinstaller. + +With the firmware installed, you can use your newly created RNode as: + +- A [LoRa interface for Reticulum]({ASSET_PATH}m/interfaces.html#rnode-lora-interface) +- A LoRa packet sniffer with [LoRaMon](https://unsigned.io/loramon/) +- A Linux network interface using the [tncattach program]({ASSET_PATH}pkg/tncattach.zip) +- A LoRa-based TNC for almost any amateur radio packet application + +So let's get started! You will need either a **LilyGO T-Beam v1.1**, a **LilyGO LoRa32 v2.0**, a **LilyGO LoRa32 v2.1** or a **Heltec LoRa32 v2** device. More supported devices are added regularly, so it might be useful to check the latest [list of supported devices]({ASSET_PATH}supported.html) as well. + +It is currently recommended to use one of the following devices: A **LilyGO LoRa32 v2.1** (also known as **TTGO T3 v1.6.1**) or a **LilyGO T-Beam v1.1**. + +![Compatible LoRa devices]({ASSET_PATH}images/g2p.webp) +
*Some of the device types compatible with this installation guide*
+ +## Device Variations + +Some devices come with transceiver chips that are currently unsupported by the RNode Firmware. Currently devices with an **SX1276** or **SX1278** chip are supported. Support for **SX1262**, **SX1268** and **SX1280** is being added. Please support the development with [donations]({ASSET_PATH}donate.html), if you would like to see these chips supported. + +> **Beware!** Some devices, like the T-Beam, use SiLabs USB chips. These may need [additional drivers](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) to work well on macOS and Windows. Linux usually has up-to-date drivers pre-installed. The SiLabs driver may also experience conflicts with earlier, pre-installed versions of the driver, causing a *resource busy* error, which can be fixed by [removing the old driver](https://community.platformio.org/t/mac-usb-port-detected-but-won-t-upload/20663/2). + +## Preparations + +To get started, you will need to install at least version 2.1.0 of the [RNode Configuration Utility]({ASSET_PATH}m/using.html#the-rnodeconf-utility). + +The `rnodeconf` program is included in the `rns` package. Please read [these instructions]({ASSET_PATH}s_rns.html) for more information on how to install it from this repository, or from the Internet. If installation goes well, you can now move on to the next step. + +## Install The Firmware + +We are now ready to start installing the firmware. To install the RNode Firmware on your devices, run the RNode autoinstaller using this command: + +```txt +rnodeconf --autoinstall +``` + +The installer will now ask you to insert the device you want to set up, scan for connected serial ports, and ask you a number of questions regarding the device. When it has the information it needs, it will install the correct firmware and configure the necessary parameters in the device EEPROM for it to function properly. + +If the install goes well, you will be greated with a success message telling you that your device is now ready. + +> **Please Note!** If you are connected to the Internet while installing, the autoinstaller will automatically download any needed firmware files to a local cache before installing. + +> If you do not have an active Internet connection while installing, you can extract and use the firmware from this device instead. This will **only** work if you are building the same type of RNode as the device you are extracting from, as the firmware has to match the targeted board and hardware configuration. + +If you need to extract the firmware from an existing RNode, run the following command: + +``` +rnodeconf --extract +``` + +If `rnodeconf` finds a working RNode, it will extract and save the firmware from the device for later use. You can then run the auto-installer with the `--use-extracted` option to use the locally extracted file: + +``` +rnodeconf --autoinstall --use-extracted +``` + +This also works for updating the firmware on existing RNodes, so you can extract a newer firmware from one RNode, and deploy it onto other RNodes using the same method. Just use the `--update` option instead of `--autoinstall`. + +## Verify Installation +To confirm everything is OK, you can query the device info with: + +```txt +rnodeconf --info /dev/ttyUSB0 +``` + +Remember to replace `/dev/ttyUSB0` with the actual port the installer used in the previous step. You should now see `rnodeconf` connect to your device and show something like this: + +```txt +[20:11:22] Opening serial port /dev/ttyUSB0... +[20:11:25] Device connected +[20:11:25] Current firmware version: 1.26 +[20:11:25] Reading EEPROM... +[20:11:25] EEPROM checksum correct +[20:11:25] Device signature validated +[20:11:25] +[20:11:25] Device info: +[20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36) +[20:11:25] Device signature : Validated - Local signature +[20:11:25] Firmware version : 1.26 +[20:11:25] Hardware revision : 1 +[20:11:25] Serial number : 00:00:00:02 +[20:11:25] Frequency range : 850.0 MHz - 950.0 MHz +[20:11:25] Max TX power : 17 dBm +[20:11:25] Manufactured : 2022-01-27 20:10:32 +[20:11:25] Device mode : Normal (host-controlled) +``` + +On the hardware side, you should see the status LED flashing briefly approximately every 2 seconds. If all of the above checks out, congratulations! Your RNode is now ready to use. If your device has a display, it should also come alive and show you various information related to the device state. + +If you want to use it with [Reticulum]({ASSET_PATH}s_rns.html), [Nomad Network]({ASSET_PATH}s_nn.html), [LoRaMon](https://unsigned.io/loramon), or other such applications, leave it in the default `Normal (host-controlled)` mode. + +If you want to use it with legacy amateur radio applications that work with KISS TNCs, you should [set it up in TNC mode]({ASSET_PATH}guides/tnc_mode.html). + +## External RGB LED +If you are using a **LilyGO LoRa32 v2.1** device, you can connect an external **NeoPixel RGB LED** for device status using the following setup: + +- Connect the NeoPixel **V+** pin to the **3.3v** pin on the board. +- Connect the NeoPixel **GND** pin to the **GND** pin on the board. +- Connect the NeoPixel **DATA** pin to **IO Pin 12** on the board. + +For the firmware to activate the NeoPixel LED, you must also make specific choices in the autoinstaller guide: + +- When asked what type of device you have, select **A specific kind of RNode**. +- When asked what model the device is, select the **Handheld v2.x RNode** that matches the frequency of your board. + +## External Display & LEDs +If you are using a **LilyGO T-Beam** device, you can connect an external **SSD1306 OLED** display using the following setup: + +- The **SSD1306**-based display must be set to use **I2C** and address `0x3D` +- Connect display **GND** to T-Beam **GND** +- Connect display **Vin** to suitable power-supplying pin on the T-Beam +- Connect display **RST** to T-Beam **Pin 13** +- Connect display **I2C CLK** to T-Beam **SCL** / **Pin 22** +- Connect display **I2C DATA** to T-Beam **SDA** / **Pin 21** + +On **T-Beam** devices, you can also connect external RX/TX LEDs to **Pin 2** and **Pin 4**. diff --git a/vendor/rnode_firmware/Console/source/guides/loracomms.md b/vendor/rnode_firmware/Console/source/guides/loracomms.md new file mode 100644 index 0000000..1b5b5b3 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/guides/loracomms.md @@ -0,0 +1,237 @@ +[date]: <> (2023-01-14) +[title]: <> (Private, Secure and Uncensorable Messaging Over a LoRa Mesh) +[image]: <> (images/g1p.webp) +[excerpt]: <> (Or: How to set up a completely private, independent and encrypted communication system in half an hour, using stuff you can buy for under $100.) + +# Private, Secure and Uncensorable Messaging Over a LoRa Mesh +*Or: How to set up a completely private, independent and encrypted communication system in half an hour, using stuff you can buy for under $100.* + +![]({ASSET_PATH}images/g1p.webp) + +In this post, we will explore how two people, Alice and Bob, can set up a LoRa mesh communication system for their use that has the following characteristics: + +- Allows both real-time and asynchronous text message communication between Alice and Bob. +- Works *completely* indpendently of any infrastructure outside the control of Alice and Bob. Even if the Internet, cellular networks and the power grid fails, Alice and Bob must still be able to communicate. +- Is completely private and outside the reach of automated surveillance, and does not reveal any identifying information about Alice or Bob, nor any contents of or information about their conversations. + +In later parts of this series, we will expand the system to provide these oppertunities to an entire community, and add other mediums like Packet Radio, but for now we will focus on learning the basics by just establishing a Free Communications System between Alice and Bob. + +To accomplish this, we will be building a small and simple system based on freely available and Open Source software. To realise our system we will need the following components: + +- A networking system that can function reliably and efficiently even without any functional Internet infrastructure available. This will be provided by [Reticulum]({ASSET_PATH}r/index.html). +- Software that Alice and Bob can interact with on their computers and mobile devices to actually communicate with each other. This will be provided by the programs [Nomad Network]({ASSET_PATH}s_nn.html) and [Sideband]({ASSET_PATH}s_sideband.html). +- Radio hardware that Reticulum can use to cover the 7 kilometer distance between Bobs apartment and Alices house. This will be provided by installing the [RNode Firmware]({ASSET_PATH}guides/install_firmware.html) on a couple of small LoRa radio modules that can be purchased cheaply off Amazon or similar online vendors. + +As you might have already guessed, the "magic glue" that acutally makes this entire system possible is [Reticulum]({ASSET_PATH}r/index.html). + +Reticulum is a complete networking stack that was designed to handle challenging situations and requirements like this. Reticulum is an incredibly flexible networking platform, that can use almost anything as a carrier for digital information transfer, and it can automatically form secure mesh networks with very minimal resources, infrastructure and setup. + +Please do keep in mind though, that at the time of writing this, Reticulum is still in beta. There might be bugs and security issues that have not yet been discovered. You can keep up with such things, and get updates on the general development and releases, over on the [Reticulum GitHub page](https://github.com/markqvist/reticulum). + +The user-facing software that Alice and Bob will be installing already includes Reticulum, so there is no complicated installation and configuration setups, and getting everything up and running will be quite simple. The requirements are also very minimal, and everything can run on hardware they already have available, be that an old computer, a Raspberry Pi, or an Android phone. + +Let's get started. + +# LoRa Radio Setup +The first step is to get the LoRa radios prepared and installed. I have written in more length and details about these subjects in other posts on this site ([Installing RNode Firmware on Supported Devices]({ASSET_PATH}guides/install_firmware.html) and [How To Make Your Own RNodes]({ASSET_PATH}guides/make_rnodes.html), so this article will just quickly guide you through the basics required to get up and running. For much more information, read the above articles. + +First of all, Alice and Bob need to get a compatible piece of radio hardware to use. Had they been living closer to each other, they might have just been able to use WiFi, but they need to cover a distance of more than 7 kilometers, so they decide to go with a couple of LoRa radios. + +They take a look at the RNode Firmware [Supported Devices List]({ASSET_PATH}supported.html), and decide to go with a couple of LilyGO T-Beam devices. They could have also used others, and they don't need to choose the same device, as long as they are within the same frequency range, all compatible devices work with Reticulum and can communicate with each other, as soon as the RNode Firmware has been installed on them. + +![]({ASSET_PATH}images/lora_rnodes.webp) + +Once the devices arrive, it is time to get the firmware installed. For this they will need a computer running some sort of Linux. Alice has a computer with Ubuntu installed, so they decide to use that. Since Python3 came installed as standard with the OS, Alice can go ahead and install the RNode configuration program by simply opening a terminal and typing: + +``` +pip install rnodeconf +``` + +The above command installs the program they need to flash the LoRa radios with the right firmware. If for some reason Python3 had not already been installed on Alices computer, she would have had to install it first with the command `sudo apt install python python-pip`. + +Now that the firmware installer is ready, it is time to actually get the firmware on to the devices. Alice launches the installer with the following command: + +``` +rnodeconf --autoinstall +``` + +After this she is greated with an interactive guide that asks a few questions about the device type, grabs the latest firmware files, and installs them onto the device. After repeating with the second device, that is all there is to it, and the LoRa radios are now ready for use with Reticulum. + +# Installation at Alices House +To get a better signal, Alice mounts her LoRa radio in the attic of her house. She then runs a USB cable from the mounting location to the computer she wants to use for messaging, and plugs the cable into the computer. The LoRa radio is now directly connected to her computer via USB, and receives power from it when the computer is on. + +At her computer (running Ubuntu Linux), she installs the Nomad Network program by entering the following command in a terminal: + +``` +pip install nomadnet +``` + +After a few seconds, Nomad Network and Reticulum is installed and ready to use. She can now run the Nomad Network client by entering the following command: + +``` +nomadnet +``` + +All required directories and configuration files will now be created, and the client will start up. After a few seconds, Alice will be greeted with a screen like this: + +![]({ASSET_PATH}images/nn_init.webp) + +Confirming that everything is installed and working, it is time to add the LoRa radio as an interface that Reticulum can use. To do this, she opens up the Reticulum configuration file (located at `˜/.reticulum/config`) in a text editor. + +By referring to the [RNode LoRa Interface]({ASSET_PATH}m/interfaces.html#rnode-lora-interface) section of the [Reticulum Manual]({ASSET_PATH}m), she can just copy-and-paste in a new configuration section for the interface, and edit the radio parameters to her requirements. She ends up with a configuration file that looks like this in it's entirity: + +``` +[reticulum] + enable_transport = False + share_instance = Yes + + shared_instance_port = 37428 + instance_control_port = 37429 + + panic_on_interface_error = No + +[logging] + loglevel = 4 + +[interfaces] + [[Default Interface]] + type = AutoInterface + interface_enabled = True + + [[RNode LoRa Interface]] + type = RNodeInterface + interface_enabled = True + port = /dev/ttyUSB0 + frequency = 867200000 + bandwidth = 125000 + txpower = 7 + spreadingfactor = 8 + codingrate = 5 +``` + +*Please note that the assignment and use of radio frequency spectrum is completely outside the scope of this exploratory post. Laws and regulations about spectrum use vary greatly around the world, and you will have to do your own research for what frequencies and modes you can use in your location, and what licenses, if any, are required for any given use case.* + +Alice can now start the Nomad Network client again, and this time around it will initialise and use the LoRa radio installed in her attic. Having completed Alices part of the setup, lets move on to Bobs apartment. + +# Installation at Bobs Apartment +Bob likes his messaging to happen on a handy device like a phone, so he decides to go with the [Sideband]({ASSET_PATH}s_sideband.html) app instead of Nomad Network. He goes to the [download page](https://github.com/markqvist/Sideband/releases/latest) and installs the APK on his Android phone. He now needs a way to connect to the LoRa radio already running at Alices house to establish communication. + +Since he doesn't want to walk around with the LoRa radio constantly dangling by a USB cable from his phone, he decides to set up a Reticulum gateway in his apartment using a Raspberry Pi he had lying around. The RNode LoRa radio will connect via USB to the Raspberry Pi, and the Raspberry Pi will be connected to the WiFi network in his apartment. + +This way, any device on his WiFi network (including his Android phone) will be able to route information through the LoRa radio as well. Reticulum takes care of everything automatically, and there is no need to configure addresses, subnet, routing rules or anything. + +Both his WiFi router and the Rasperry Pi is powered by a small battery system, so even if the power goes out, the system will be able to stay on for several days on the battery, and indefinitely if he props up a solar panel on his balcony. + +Bob installs a fresh copy of Raspberry Pi OS on the small computer, and in the terminal issues the following command to install Reticulum: + +``` +pip install rns +``` + +In this case, Bob will not be running any user-facing software on the Raspberry Pi itself, so instead he starts Reticulum as a service, by running the `rnsd` program, to check that everything installed correctly: + +``` +rnsd +``` + +After a moment, the following output is shown from the `rnsd` program, signalling that everything is working properly, but that a new, default configuration file has just been created: + +``` +[2022-03-26 17:14:05] [Notice] Could not load config file, creating default configuration file... +[2022-03-26 17:14:05] [Notice] Default config file created. Make any necessary changes in /home/bob/.reticulum/config and restart Reticulum if needed. +[2022-03-26 17:14:09] [Notice] Started rnsd version 0.3.3 +``` + +Bob terminates the `rnsd` program, and then connects the LoRa radio to the Raspberry Pi with a USB cable. Since he doesn't have any particular access to the roof or attic of the building, he just sticky-tapes the LoRa radio to a window facing in the general direction of Alices house. + +He then proceeds to add the same interface configuration to his Reticulum configuration file as Alice did, so that the radio parameters of their respective LoRa radios match each other. + +To allow other devices on his network to route through his new Reticulum gateway, he also adds the line `enable_transport = yes` to his Reticulum config file, so the file in it's entirity looks like this: + +``` +[reticulum] + enable_transport = Yes + share_instance = Yes + + shared_instance_port = 37428 + instance_control_port = 37429 + + panic_on_interface_error = No + +[logging] + loglevel = 4 + +[interfaces] + [[Default Interface]] + type = AutoInterface + interface_enabled = True + + [[RNode LoRa Interface]] + type = RNodeInterface + interface_enabled = True + port = /dev/ttyUSB0 + frequency = 867200000 + bandwidth = 125000 + txpower = 7 + spreadingfactor = 8 + codingrate = 5 +``` + + After starting the program again, this time using `rnsd -vvv` to get more verbose output, he can now see that the LoRa radio is correctly configured and used by Reticulum: + +``` +[2022-03-26 18:17:43] [Debug] Bringing up system interfaces... +[2022-03-26 18:17:43] [Verbose] AutoInterface[Default Interface] discovering peers for 1.8 seconds... +[2022-03-26 18:17:45] [Notice] Opening serial port /dev/ttyUSB0... +[2022-03-26 18:17:47] [Notice] Serial port /dev/ttyUSB0 is now open +[2022-03-26 18:17:47] [Verbose] Configuring RNode interface... +[2022-03-26 18:17:47] [Verbose] Wating for radio configuration validation for RNodeInterface[RNode LoRa Interface]... +[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting frequency is 867.2 MHz +[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting bandwidth is 125 KHz +[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting TX power is 7 dBm +[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting spreading factor is 8 +[2022-03-26 18:17:47] [Debug] RNodeInterface[RNode LoRa Interface] Radio reporting coding rate is 5 +[2022-03-26 18:17:47] [Verbose] RNodeInterface[RNode LoRa Interface] On-air bitrate is now 3.1 kbps +[2022-03-26 18:17:47] [Notice] RNodeInterface[RNode LoRa Interface] is configured and powered up +[2022-03-26 18:17:48] [Debug] System interfaces are ready +[2022-03-26 18:17:48] [Verbose] Configuration loaded from /home/bob/.reticulum/config +[2022-03-26 18:17:50] [Verbose] Loaded 0 path table entries from storage +[2022-03-26 18:17:50] [Verbose] Loaded 0 tunnel table entries from storage +[2022-03-26 18:17:50] [Verbose] Transport instance started +[2022-03-26 18:17:50] [Notice] Started rnsd version 0.3.3 +``` + +Everything is ready, and when Bob launches the Sideband appplication on his phone, Alice and him will now be able to communicate securely and independently of any other infrastructure. + +# Communication +Both the [Nomad Network]({ASSET_PATH}s_nn.html) program and the [Sideband]({ASSET_PATH}s_sidband.html) application use a cryptographic message delivery system named [LXMF]({ASSET_PATH}s_lxmf.html), that in turn uses Reticulum for encryption and privacy guarantees. Both Nomad Network and Sideband are *LXMF clients*. + +Much like many different e-mail clients exist, so can many different LXMF clients, and they can all communicate with each other, which is why Alice and Bob can message each other even though they prefer to use very different kinds of user-facing software. + +An LXMF addresses consist of 32 hexadecimal characters, and are usually encapsulated in single angle quotation marks like this: `<9824f6367015b30f2d7b8a24bc6205d7>`. + +Nobody controls the allocation of addresses, and since the address space is so huge, and governed by cryptographic principles, you can create as many or as few adresses as you need. + +Since you can just create them with freely avaible software, and without any sort of permission from anyone, they are never linked to any personally identifiable information either. They are completely and truly anonymous from the beginning, and you control how much or how little of your identity you associate with them. + +For an LXMF address to be reachable for direct-delivery instant messaging on a Reticulum network, it must announce it's public keys on the network. Both Sideband and Nomad Network allows you to send an announce on the network, and both programs can be configured to do so automatically when they start. If you only want to use the system for "email-style" communication (via LXMF propagation nodes), you don't *need* to send any announces on the network, but to learn how it all works, it is a good idea to just set the programs to automatically announce at start up. + +To make sure his public cryptographic key is known by the network, Bob taps the **Announce** button in the Sideband app: + +

+ +After a few seconds, Bobs announce shows up in the **Announce Stream** section of the Nomad Network program on Alices computer: + +

+ +Using the received announce, Alice starts a conversation with Bob. Either one of them could also have started the conversation by manually typing in the others LXMF address in their program, but in many cases it can be convenient to use the announces. Now that everything is ready, they exchange a few messages to test the system. On Bobs Android phone, this looks like this: + +

+ +And on Alices computer running Nomad Network, it looks like this: + +

+ +Although pretty useful, what we have explored here does not even begin to scratch the surface of what is possible with Reticulum and associated software. I hope you will find yourself inspired to explore and read deeper into the documentation and available software. + +To learn more, take a look at the [Learn]({ASSET_PATH}learn.html) section. diff --git a/vendor/rnode_firmware/Console/source/guides/make_rnodes.md b/vendor/rnode_firmware/Console/source/guides/make_rnodes.md new file mode 100644 index 0000000..6190b3f --- /dev/null +++ b/vendor/rnode_firmware/Console/source/guides/make_rnodes.md @@ -0,0 +1,111 @@ +[date]: <> (2023-01-10) +[title]: <> (How To Make Your Own RNodes) +[image]: <> (images/g3p.webp) +[excerpt]: <> (This article will outline the general process, and provide the information you need, for building your own RNode from a few basic modules. The RNode will be functionally identical to a commercially purchased board.) +# How To Make Your Own RNodes + +This article will outline the general process, and provide the information you need, for building your own RNode from a few basic modules. The RNode will be functionally identical to a purchased device. + +Once you have learned the put together a custom RNode with your own choice of components, you can use these skills to create your own RNode designs from scratch, using either a custom-designed PCB, or simply by mounting your choice of modules in a enclosure or case. + +If you haven't already, you migh also want to check out how to [install the RNode firmware directly on pre-made LoRa development boards]({ASSET_PATH}guides/install_firmware.html). + +![A Homemade RNode]({ASSET_PATH}images/g3p.webp) +
*A homemade RNode, based on an ESP32 board and a transceiver module, ready for use*
+ +Since there is not *one right way* to cut this pie, this article will probably not give the *exact* steps for the combination of components you choose, but will instead attempt to provide you with the information you need to build RNodes from a wide variety of microcontroller boards and LoRa modules. Generally speaking, you will need three things to construct a working RNode: + +- A supported microcontroller board +- A supported transceiver module +- A way to mount and connect the two + +## Preparing the Hardware + +Currently, the RNode firmware supports a variety of different microcontrollers, and more are being added regurlarly. That means that there is a *lot* of boards to choose from. You can probably use most boards that are based on either the **ATmega1284P**, **ATmega2560** or **ESP32** microcontrollers. Regarding microcontroller boards there is a few key points to take note of: + +- You will need to connect the transceiver module over the SPI bus. This means that the board should have SPI pins for exposed for you to connect to. UART-only modules will **not** work. +- Logic voltage levels must match the transceiver module you are using, or you will have to add a voltage level converter in between the two devices, that is fast enough for the clock of the SPI bus (usually 8 or 10MHz). I recommend using a microcontroller and transceiver module with matching logic levels. Most will be 3.3 volts. +- Apart from the SPI pins for *clock*, *chip select*, *MOSI* and *MISO*, you will also need an output pin for a *reset* line to the transceiver module, and one **interrupt-capable** input pin for the interrupt signal from the transceiver module. Almost all boards should have plenty of IO available for this, but you might as well make sure before ordering anything. +- You need to choose a board that can provide enough power on it's internal regulators to power the transceiver module while it is transmitting. This can draw quite a bit of power, and some boards only have very small 3.3v regulators, which will not cut it while driving the transmitter at full tilt. + +Regarding the LoRa transceiver module, there is going to be an almost overwhelming amount of options to choose from. To narrow it down, here are the essential characteristics to look for: + +- The RNode firmware needs a module based on the **Semtech SX1276**, **Semtech SX1278**, **SX1262**, **SX1268** and **SX1280** LoRa transceiver ICs. These come in several different variants, for all frequency bands from about 150 MHz to 2500 MHz. +- The module *must* expose the direct SPI bus to the transceiver chip. UART based modules that add their own communications layer will not work. +- The module must also expose the *reset* line of the chip, and provide the **DIO0** (or other relevant) interrupt signal *from* the chip. +- As mentioned above, the module must be logic-level compatible with the microcontroller you are using, unless you want to add a level-shifter. Resistor divider arrays will most likely not work here, due to the bus speeds required. + +Keeping those things in mind, you should be able to select a suitable combination of microcontroller board and transceiver module. + +## Assembling the RNode + +Ok, having gone through the endless combinations and selected a board and a module, you are actually almost done. Connecting the devices together is pretty simple, and should only take a few minutes. I recommend that you place both devices in a solderless breadboard initially, to make sure everything is working as expected. Once you have a working setup, you can make it more durable and permanent by soldering it to a prototyping board, and connecting permanent lines between the devices. + +In the photo above I used an Adafruit Feather ESP32 board and a ModTronix inAir4 module. That will result in an RNode suitable for the 420 MHz to 520 MHz range. To complete the device I did the following: + +1. Connect the GND pin of the microcontroller board to the GND rail of the breadboard. +2. Connect the GND pin of the transceiver module to the GND rail of the breadboard. +3. Connect the 3.3 volt output line of the microcontroller board to the V_IN pin of the transceiver module. +4. Connect the *chip select* pin of the microcontroller board to the *chip select* pin of the transceiver module. +5. Connect the *SPI clock* pin of the microcontroller board to the *SPI clock* pin of the transceiver module. +6. Connect the *MOSI* pin of microcontroller board to the *MOSI* pin of the transceiver module. +7. Connect the *MISO* pin of the microcontroller board to the *MISO* pin of the transceiver module. +8. Connect the *transceiver reset* pin of the microcontroller board to the *reset* pin of the transceiver module. +9. Connect the *DIO0* pin of the transceiver module to the *DIO0 interrupt pin* of the microcontroller board. +10. You can optionally connect transmit and receiver LEDs to the corresponding pins of the microcontroller board. + +The pin layouts of your transceiver module and microcontroller board will vary, but you can look up the correct pin assignments for your processor type and board layout in the [Config.h](https://github.com/markqvist/RNode_Firmware/blob/master/Config.h) file of the [RNode Firmware](https://unsigned.io/rnode_firmware). + +### Loading the Firmware +Once the hardware is assembled, you are ready to load the firmware onto the board and configure the configuration parameters in the boards EEPROM. Luckily, this process is completely automated by the [RNode Configuration Utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnodeconf-utility). To prepare for loading the firmware, make sure that `python` and `pip` is installed on your system, then install the `rns` package (which includes the `rnodeconf` program) by issuing the command: + + +```txt +pip install rns +``` + +If installation goes well, you can now move on to the next step. + +> *Take Care*: A LoRa transceiver module **must** be connected to the board for the firmware to start and accept commands. If the firmware does not verify that the correct transceiver is available on the SPI bus, execution is stopped, and the board will not accept commands. If you find the board unresponsive after installing the firmware, or EEPROM configuration fails, double-check your transceiver module wiring! + +Having double-checked that everything is connected correctly, it is time to power up the board and install the firmware. Run the `rnodeconf` autoinstaller by executing the command: + +```txt +rnodeconf --autoinstall +``` + +The installer will now ask you to insert the device you want to set up, scan for connected serial ports, and ask you a number of questions regarding the device. When it has the information it needs, it will install the correct firmware and configure the necessary parameters in the device EEPROM for it to function properly. + +If the install goes well, you will be greated with a success message telling you that your device is now ready. To confirm everything is OK, you can query the device info with: + +```txt +rnodeconf --info /dev/ttyUSB0 +``` + +Remember to replace `/dev/ttyUSB0` with the actual port the installer used in the previous step. You should now see `rnodeconf` connect to your device and show something like this: + +```txt +[2022-01-27 20:11:22] Opening serial port /dev/ttyUSB0... +[2022-01-27 20:11:25] Device connected +[2022-01-27 20:11:25] Current firmware version: 1.26 +[2022-01-27 20:11:25] Reading EEPROM... +[2022-01-27 20:11:25] EEPROM checksum correct +[2022-01-27 20:11:25] Device signature validated +[2022-01-27 20:11:25] +[2022-01-27 20:11:25] Device info: +[2022-01-27 20:11:25] Product : LilyGO LoRa32 v2.0 850 - 950 MHz (b0:b8:36) +[2022-01-27 20:11:25] Device signature : Validated - Local signature +[2022-01-27 20:11:25] Firmware version : 1.26 +[2022-01-27 20:11:25] Hardware revision : 1 +[2022-01-27 20:11:25] Serial number : 00:00:00:02 +[2022-01-27 20:11:25] Frequency range : 850.0 MHz - 950.0 MHz +[2022-01-27 20:11:25] Max TX power : 17 dBm +[2022-01-27 20:11:25] Manufactured : 2022-01-27 20:10:32 +[2022-01-27 20:11:25] Device mode : Normal (host-controlled) +``` + +On the hardware side, you should see the status LED flashing briefly approximately every 2 seconds. If all of the above checks out, congratulations! Your RNode is now ready to use. + +If you want to use it with [Reticulum]({ASSET_PATH}s_rns.html), [Nomad Network]({ASSET_PATH}s_nn.html), [LoRaMon](https://unsigned.io/loramon), or other such applications, leave it in the default `Normal (host-controlled)` mode. + +If you want to use it with legacy amateur radio applications that work with KISS TNCs, you should [set it up in TNC mode]({ASSET_PATH}guides/tnc_mode.html). diff --git a/vendor/rnode_firmware/Console/source/guides/tnc_mode.md b/vendor/rnode_firmware/Console/source/guides/tnc_mode.md new file mode 100644 index 0000000..732c890 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/guides/tnc_mode.md @@ -0,0 +1,22 @@ +[date]: <> (2023-01-07) +[title]: <> (Using an RNode With Amateur Radio Software) +[image]: <> (images/g4p.webp) +[excerpt]: <> (If you want to use an RNode with amateur radio applications, like APRS or a packet radio BBS, you will need to put the device into TNC Mode. In this mode, an RNode will behave exactly like a KISS-compatible TNC, which will make it usable with any amateur radio software.) + +# Using an RNode With Amateur Radio Software + +If you want to use an RNode with amateur radio applications, like APRS or a packet radio BBS, you will need to put the device into *TNC Mode*. In this mode, an RNode will behave exactly like a KISS-compatible TNC, which will make it usable with any amateur radio software that can talk to a KISS TNC over a serial port. + +You can use the [RNode Configuration Utility]({ASSET_PATH}m/using.html#the-rnodeconf-utility) to change settings on your device, including putting it into TNC mode. + +The `rnodeconf` program is included in the `rns` package. Please read [these instructions]({ASSET_PATH}s_rns.html) for more information on how to install it from this repository, or from the Internet. + +With the `rnodeconf` program installed, you can put your RNode into TNC mode simply by entering the command: + +``` +rnodeconf -T /dev/ttyUSB0 +``` + +Remember to replace `/dev/ttyUSB0` with the actual port your RNode is connected to. The program will now ask you for the channel configuration parameters, like frequency, bandwidth, transmission power and so on. It is also possible to specify all the parameters at once on the command line, see the `rnodeconf --help` for information on how to do this. + +That's all there is to it! Your RNode is now configured in TNC mode, and ready for use with amateur radio applications. diff --git a/vendor/rnode_firmware/Console/source/help.md b/vendor/rnode_firmware/Console/source/help.md new file mode 100644 index 0000000..d6dd72d --- /dev/null +++ b/vendor/rnode_firmware/Console/source/help.md @@ -0,0 +1,14 @@ +[title]: <> (Get Help) +## Get Help +If you are having trouble, or if something is not working, this RNode contains a number of useful resources. + +- Read [Questions & Answers](qa.html) section +- Read the [Reticulum Manual](m/index.html) stored on this RNode +- Browse a copy of the [Reticulum Website](r/index.html) stored on this RNode + +## Community & Support +If things still aren't working as expected here are some great places to ask for help: + +- The [discussion forum](https://github.com/markqvist/Reticulum/discussions) on GitHub +- The [Reticulum Matrix Channel](element://room/!TRaVWNnQhAbvuiSnEK%3Amatrix.org?via=matrix.org) at `#reticulum:matrix.org` +- The [Reticulum subreddit](https://reddit.com/r/reticulum) diff --git a/vendor/rnode_firmware/Console/source/index.md b/vendor/rnode_firmware/Console/source/index.md new file mode 100644 index 0000000..e51055d --- /dev/null +++ b/vendor/rnode_firmware/Console/source/index.md @@ -0,0 +1,28 @@ +## Hello! + + + + + + + +
+You have connected to the RNode Bootstrap Console.
+
+The tools and information contained in this RNode will allow you to replicate the RNode design, build more RNodes and grow your communications ecosystems.
+
+This repository also contains tools, software and information necessary to bootstrap networks and communications systems based on RNodes and Reticulum. +
+
+
+
+

What would you like to do?

+
You can browse this repository freely, or jump straight into a task-oriented workflow by selecting one of the starting points below.
+ + + + + + + +
diff --git a/vendor/rnode_firmware/Console/source/learn.md b/vendor/rnode_firmware/Console/source/learn.md new file mode 100644 index 0000000..53eda53 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/learn.md @@ -0,0 +1,14 @@ +[title]: <> (Learn More) +## Learn More +This RNode contains a selection of tutorials and guides on setting up communications, creating RNodes, building networks and using Reticulum. You can learn more by: + +- Reading the [What is an RNode?](rnode.html) page +- Checking the [Questions & Answers](qa.html) section +- Reading the [Reticulum Manual](m/index.html) stored on this RNode +- Browsing a copy of the [Reticulum Website]({ASSET_PATH}r/index.html) stored on this RNode +- Visiting the [unsigned.io](https://unsigned.io/) website +- You can also find **unsigned.io** on Nomad Network, at `ec58b0e430cd9628907383954feea068` + +## Guides + +{TOPIC:guides} diff --git a/vendor/rnode_firmware/Console/source/qa.md b/vendor/rnode_firmware/Console/source/qa.md new file mode 100644 index 0000000..ec83bcd --- /dev/null +++ b/vendor/rnode_firmware/Console/source/qa.md @@ -0,0 +1,20 @@ +[title]: <> (Questions & Answers) +## Questions & Answers +This section contains a list of common questions, and associated answers. + +- **What are the system requirements for running Reticulum?** +Practically any system that can run Python3 can also run Reticulum. Any computer made since the early 2000's should work, provided it has a reasonably up-to-date operating system installed. Even low-power embedded devices with 256 megabytes of RAM will run Reticulum. +- **Does Reticulum work without the Internet?** +Yes. Reticulum *is* itself both a networking, and an inter-net protocol. A key difference between Reticulum and IPv4/v6, however, is that Reticulum does not require any central coordination or authority to work. As soon as two devices running Reticulum can talk to each other, they form a network. That network can dynamically grow to planetary-scale nets, split up, re-connect and heal in any number of ways, while still continuing to function. As long as there is *some sort of physical way* for two or more devices to communicate, Reticulum will allow them to form a secure and reliable network. +- **Who owns and controls the addresses I use on a Reticulum network?** +You do. Every address is in complete ownership and control of the person that created it. +- **If nobody centrally controls the addresses, will my address still be globally reachable?** +Yes. Reticulum ensures end-to-end connectivity. All addresses are globally and directly reachable. Reticulum has no concept of "private address spaces" and NAT, as you might be suffering from with IPv4. +- **Is communication over Reticulum encrypted?** +Yes. All traffic is end-to-end encrypted. Reticulum *is fundamentally unable to route unencrypted traffic*. Links established over Reticulum networks offer forward secrecy, by using ephemeral encryption keys. +- **Could you build a global Internet with Reticulum instead of IP?** +Yes. In theory this is completely possible, but it will take a lot of refinement, development, hardware support and adoption to transition the global base-layer for communication to Reticulum. Please [help us]({ASSET_PATH}contribute.html) towards this goal! +- **Is Reticulum as fast and optimised as my favorite TCP/IP stack?** +Currently not, but we are working towards being much faster than IP. The primary focus of Reticulum has been to build an understandable and well-documented *reference implementation*, that works exceptionally well over medium-bandwidth to extremely low-bandwidth forms of communication. This focus is very valuable, since it allows people to build secure communications networks that span vast areas, with very simple hardware, and very little cost. +- **Who created all of this?** +The Reticulum protocol, and the RNode system was created by [Mark Qvist]({ASSET_PATH}contact.html), of [unsigned.io](https://unsigned.io). \ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/recipes.md b/vendor/rnode_firmware/Console/source/recipes.md new file mode 100644 index 0000000..690602a --- /dev/null +++ b/vendor/rnode_firmware/Console/source/recipes.md @@ -0,0 +1,5 @@ +[title]: <> (RNode Recipes) +## RNode Build Recipes +This section contains a library of build recipes for various types of RNodes. All the recipes contain necessary plans, instructions and 3D-printable files for completing the build. + +{TOPIC:builds} diff --git a/vendor/rnode_firmware/Console/source/replicate.md b/vendor/rnode_firmware/Console/source/replicate.md new file mode 100644 index 0000000..5d43ebb --- /dev/null +++ b/vendor/rnode_firmware/Console/source/replicate.md @@ -0,0 +1,27 @@ +[title]: <> (Replicate) +## Create RNodes +This section contains the tools and guides necessary to create more RNodes. Creating any number of RNodes is **completely free and unrestricted** for all personal, non-commercial and humanitarian purposes. If doing so provides value to you or your community, you are encouraged to [contribute](./contribute.html) whatever you find to be reasonable. + +If you want to create RNodes for sale or commercial purposes, please read the [selling RNodes]({ASSET_PATH}sell_rnodes.html) section for more details. + +### Firmware Source Code +If you would like to inspect or compile the RNode Firmware source code yourself, you can download a copy of the [RNode Firmware source-code]({ASSET_PATH}pkg/rnode_firmware.zip) stored in this RNode. + +### Getting Started +To create your own RNodes, there are generally three distinct paths you can take: + +- The first, and easiest option, is to [create a basic RNode]({ASSET_PATH}guides/install_firmware.html) from one of the supported development boards. This option allows you to simply acquire a board from any online or local vendor that sells them, and then use the `rnodeconf` program to automatically turn it into an RNode. Such an RNode will be functionally equivalent to the other options, but might lack some niceties. +- The second option is to use one of the [RNode Build Recipes]({ASSET_PATH}recipes.html) included here. These recipes contain all the resources needed to build a specific type of RNode, such as a handheld device, or an outdoor-mountable, solar-powered access point. +- The third option is to [create your own RNode design]({ASSET_PATH}guides/make_rnodes.html) from scratch. This offers unlimited flexibility, but is a bit more involved. + +If you already have some experience with 3D-printing and electronics projects, the recommended path is to pick a [build recipe]({ASSET_PATH}recipes.html) for the RNode type you want. That way, you will get a neat and portable unit that's ready for real-world use. + +If you are just getting started, it might be nice to get a working "proof-of-concept" with minimal effort first, though. In such a case, [creating a basic RNode]({ASSET_PATH}guides/install_firmware.html) is a good starting point. +

+
+

Choose a path to get started

+
+ + + +
diff --git a/vendor/rnode_firmware/Console/source/rnode.md b/vendor/rnode_firmware/Console/source/rnode.md new file mode 100644 index 0000000..a34782e --- /dev/null +++ b/vendor/rnode_firmware/Console/source/rnode.md @@ -0,0 +1,11 @@ +[title]: <> (What is an RNode?) +## What is an RNode? +An RNode is an open, free and unrestricted digital radio transceiver. It enables anyone to send and receive any kind of data over both short and very long distances. RNodes can be used with many different kinds of programs and systems, but they are especially well suited for use with Reticulum. + +RNode is not a product, and not any one specific device in particular. It is a system that is easy to replicate across space and time, that produces highly functional communications tools, which respects user autonomy and empowers individuals and communities to protect their sovereignty and privacy. + +The RNode system is primarily software, which *transforms* available hardware devices into functional, physical RNodes, which can then be used to solve a wide range of communications tasks. Such RNodes can be modified and build to suit the specific time, locale and environment they need to exist in. + +If you notice the presence of a circularity in the naming of the system as a whole, and the physical devices, it is no coincidence. Every RNode contains the seeds necessary to reproduce the system, and create more RNodes, and even to bootstrap entire communications networks, completely independently of existing infrastructure, or the lack thereof. + +The production of one particular RNode device is not an end, but the potential starting point of a new branch of devices on the tree of the RNode system as a whole. This tree fits into the larger biome of Free & Open Communications Systems, which I hope that you - by using communications tools like RNode - will help grow and prosper. diff --git a/vendor/rnode_firmware/Console/source/s_lxmf.md b/vendor/rnode_firmware/Console/source/s_lxmf.md new file mode 100644 index 0000000..7171208 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/s_lxmf.md @@ -0,0 +1,23 @@ +[title]: <> (LXMF) +## LXMF +LXMF is a simple and flexible messaging format and delivery protocol that allows a wide variety of implementations, while using as little bandwidth as possible. It is built on top of [Reticulum](https://reticulum.network) and offers zero-conf message routing, end-to-end encryption and Forward Secrecy, and can be transported over any kind of medium that Reticulum supports. + +LXMF is efficient enough that it can deliver messages over extremely low-bandwidth systems such as packet radio or LoRa. Encrypted LXMF messages can also be encoded as QR-codes or text-based URIs, allowing completely analog *paper message* transport. + +Installing this LXMF library allows other programs on your system, like Nomad Network, to use the LXMF messaging system. It also includes the `lxmd` program that you can use to run LXMF propagation nodes on your network. + +**Local Installation** + +If you do not have access to the Internet, or would prefer to install LXMF directly from this RNode, you can use the following instructions. + +- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first +- Download the [{PKG_BASE_lxmf}]({ASSET_PATH}{PKG_lxmf}) package from this RNode and unzip it +- Install it with the command `pip install ./{PKG_NAME_lxmf}` +- Verify the installed Reticulum version by running `lxmd --version` + +**Online Installation** + +If you are connected to the Internet, you can try to install the latest version of LXMF via the `pip` package manager. + +- Install Nomad Network by running the command `pip install lxmf` +- Verify the installed Reticulum version by running `lxmd --version` diff --git a/vendor/rnode_firmware/Console/source/s_nn.md b/vendor/rnode_firmware/Console/source/s_nn.md new file mode 100644 index 0000000..f54e68d --- /dev/null +++ b/vendor/rnode_firmware/Console/source/s_nn.md @@ -0,0 +1,27 @@ +[title]: <> (Nomad Network) +## Nomad Network +Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy. + +Nomad Network Allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them. No signups, no agreements, no handover of any data, no permissions and gatekeepers. + +![Screenshot]({ASSET_PATH}gfx/nn.webp) + +Nomad Network is build on [LXMF](lxmf.html) and [Reticulum]({ASSET_PATH}r/), which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to fiber optics. + +Nomad Network does not need any connections to the public internet to work. In fact, it doesn't even need an IP or Ethernet network. You can use it entirely over packet radio, LoRa or even serial lines. But if you wish, you can bridge islanded networks over the Internet or private ethernet networks, or you can build networks running completely over the Internet. The choice is yours. + +### Local Installation + +If you do not have access to the Internet, or would prefer to install Nomad Network directly from this RNode, you can use the following instructions. + +- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) and [LXMF](./s_lxmf.html) packages first +- Download the [{PKG_BASE_nomadnet}]({ASSET_PATH}{PKG_nomadnet}) package from this RNode and unzip it +- Install it with the command `pip install ./{PKG_NAME_nomadnet}` +- Verify the installed Nomad Network version by running `nomadnet --version` + +### Online Installation + +If you are connected to the Internet, you can try to install the latest version of Nomad Network via the `pip` package manager. + +- Install Nomad Network by running the command `pip install nomadnet` +- Verify the installed Nomad Network version by running `nomadnet --version` diff --git a/vendor/rnode_firmware/Console/source/s_rns.md b/vendor/rnode_firmware/Console/source/s_rns.md new file mode 100644 index 0000000..7c33a69 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/s_rns.md @@ -0,0 +1,33 @@ +[title]: <> (Reticulum) +## Reticulum +The cryptographic networking stack for building resilient networks anywhere. The vision of Reticulum is to allow anyone to operate their own sovereign communication networks, and to make it cheap and easy to cover vast areas with a myriad of independent, interconnectable and autonomous networks. Reticulum is Unstoppable Networks for The People. + +

+ +This packages requires you have `python` and `pip` installed on your computer. This should come as standard on most operating systems released since 2020. + +### Local Installation +If you do not have access to the Internet, or would prefer to install Reticulum directly from this RNode, you can use the following instructions. + +- Download the [{PKG_BASE_rns}]({ASSET_PATH}{PKG_rns}) package from this RNode and unzip it +- Install it with the command `pip install ./{PKG_NAME_rns}` +- Verify the installed Reticulum version by running `rnstatus --version` + +### Online Installation +If you are connected to the Internet, you can try to install the latest version of Reticulum via the `pip` package manager. + +- Install Reticulum by running the command `pip install rns` +- Verify the installed Reticulum version by running `rnstatus --version` + +### Dependencies +If the installation has problems resolving dependencies, first try installing the `python-cryptography`, `python-netifaces` and `python-pyserial` packages from your systems package manager. + +If this fails, or is simply not possible in your situation, you can make the installation of Reticulum ignore the resolution of dependencies using the command: + +`pip install --no-dependencies ./{PKG_NAME_rns}` + +This will allow you to install Reticulum on systems, or in circumstances, where one or more dependencies cannot be resolved. This will most likely mean that some functionality will not be available, which may be a worthwhile tradeoff in some situations. + +If you use this method of installation, it is essential to read the [Pure-Python Reticulum]({ASSET_PATH}m/gettingstartedfast.html#pure-python-reticulum) section of the Reticulum Manual, and to understand the potential security implications of this installation method. + +For more detailed information, please read the entire [Getting Started section of the Reticulum Manual]({ASSET_PATH}m/gettingstartedfast.html). diff --git a/vendor/rnode_firmware/Console/source/s_rnsh.md b/vendor/rnode_firmware/Console/source/s_rnsh.md new file mode 100644 index 0000000..deb5adb --- /dev/null +++ b/vendor/rnode_firmware/Console/source/s_rnsh.md @@ -0,0 +1,20 @@ +[title]: <> (Shell Over Reticulum) +## Shell Over Reticulum + +The `rnsh` program lets you establish fully interactive remote shell sessions over Reticulum. It also allows you to pipe any program to or from a remote system, and is similar to how the ``ssh`` program works. + +### Local Installation + +If you do not have access to the Internet, or would prefer to install `rnsh` directly from this RNode, you can use the following instructions. + +- If you do not have an Internet connection while installing make sure to install the [Reticulum](./s_rns.html) package first +- Download the [{PKG_BASE_rnsh}]({ASSET_PATH}{PKG_rnsh}) package from this RNode and unzip it +- Install it with the command `pip install ./{PKG_NAME_rnsh}` +- Verify the installed `rnsh` version by running `rnsh --version` + +### Online Installation + +If you are connected to the Internet, you can try to install the latest version of `rnsh` via the `pip` package manager. + +- Install `rnsh` by running the command `pip install rnsh` +- Verify the installed `rnsh` version by running `rnsh --version` diff --git a/vendor/rnode_firmware/Console/source/s_sideband.md b/vendor/rnode_firmware/Console/source/s_sideband.md new file mode 100644 index 0000000..5b06d2f --- /dev/null +++ b/vendor/rnode_firmware/Console/source/s_sideband.md @@ -0,0 +1,12 @@ +[title]: <> (Sideband) +## Sideband +Sideband is an LXMF client for Android, Linux, Windows and macOS. It has built-in support for communicating over RNodes, and many other mediums, such as Packet Radio, WiFi, I2P, or anything else Reticulum supports. + +Sideband also supports voice calls, file transfers, and exchanging messages through encrypted QR-codes on paper, or through messages embedded directly in lxm:// links. + +![Screenshot]({ASSET_PATH}gfx/sideband.webp) + +The installation files for the Sideband program is too large to be included on this RNode, but downloads for Linux, Android and macOS can be obtained from following sources: + +- The [Sideband page](https://unsigned.io/sideband/) on [unsigned.io](https://unsigned.io/) +- The [GitHub release page for Sideband](https://github.com/markqvist/Sideband/releases/latest) diff --git a/vendor/rnode_firmware/Console/source/sell_rnodes.md b/vendor/rnode_firmware/Console/source/sell_rnodes.md new file mode 100644 index 0000000..31e2cdb --- /dev/null +++ b/vendor/rnode_firmware/Console/source/sell_rnodes.md @@ -0,0 +1,9 @@ +[title]: <> (Sell RNodes) +## Build & Sell RNodes +Creating any number of RNodes is completely free and unrestricted for all personal, non-commercial and humanitarian purposes. Feel free to use all the resources provided here, and on the [unsigned.io](https://unsigned.io/) website. If doing so provides value to you or your community, you are encouraged to [contribute]({ASSET_PATH}contribute.html) whatever you find to be reasonable. + +The RNode Ecosystem is free and non-proprietary, and actively seeks to distribute it's ownership and control. If you want to build RNodes for commercial purposes, including selling them, you must do so adhering to the Open Source licenses that the various parts of the RNode project is released under, and under your own responsibility. + +The RNode Firmware is released under GPLv3, and basing commercial works on it means (among other things), that you must also make your derivatives open source and available under the same terms. + +In practice, this means that you can use the firmware commercially, but you should understand your obligation to provide all future users of the system the same rights that you have been provided by the GPLv3. \ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/software.md b/vendor/rnode_firmware/Console/source/software.md new file mode 100644 index 0000000..a3bca99 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/software.md @@ -0,0 +1,23 @@ +[title]: <> (Software) +## Software +This RNode contains a repository of downloadable software and utilities, that are useful for bootstrapping communications networks, and for replicating RNodes. + +**Please Note!** Whenever you install software onto your computer, there is a risk that someone modified this software to include malicious code. Be extra careful installing anything from this RNode, if you did not get it from a source you trust, or if there is a risk it was modified in transit. + +If possible, you can check that the `SHA-256` hashes of any downloaded files correspond to the list of release hashes published on the [Reticulum Release page](https://github.com/markqvist/Reticulum/releases). + +**You Have The Source!** Due to the size limitations of shipping all this software within an RNode, we don't include separate source-code archives for the below programs, but *all the source code is included within the Python .whl files*! + +You can simply unzip any of them with any program that understands `zip` files, and you will find the source code inside the unzipped directory (for some zip programs, you may need to change the file ending to `.zip`). + +You can also download the copy of the [RNode Firmware source-code]({ASSET_PATH}pkg/rnode_firmware.zip) that is stored in this RNode. +

+
+

Choose a software package to get started

+
+ + + + + +
\ No newline at end of file diff --git a/vendor/rnode_firmware/Console/source/supported.md b/vendor/rnode_firmware/Console/source/supported.md new file mode 100644 index 0000000..7be87b9 --- /dev/null +++ b/vendor/rnode_firmware/Console/source/supported.md @@ -0,0 +1,17 @@ +[title]: <> (Supported Hardware) +## Supported Boards & Devices +The RNode Firmware supports the following boards: + +- Handheld v2.x RNodes from [unsigned.io](https://unsigned.io/shop/product/handheld-rnode) +- Original v1.x RNodes from [unsigned.io](https://unsigned.io/shop/product/rnode) +- LilyGO T-Beam v1.1 devices +- LilyGO LoRa32 v2.0 devices +- LilyGO LoRa32 v2.1 devices +- Heltec LoRa32 v2 devices +- Homebrew RNodes based on ATmega1284p boards +- Homebrew RNodes based on ATmega2560 boards +- Homebrew RNodes based on Adafruit Feather ESP32 boards +- Homebrew RNodes based on generic ESP32 boards + +## Supported Transceiver Modules +The RNode Firmware supports all transceiver modules based on **Semtech SX1276** or **Semtech SX1278** chips, that have an **SPI interface** and expose the **DIO_0** interrupt pin from the chip. \ No newline at end of file diff --git a/vendor/rnode_firmware/Device.h b/vendor/rnode_firmware/Device.h new file mode 100644 index 0000000..2534a1d --- /dev/null +++ b/vendor/rnode_firmware/Device.h @@ -0,0 +1,280 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#if MCU_VARIANT == MCU_ESP32 +#include "mbedtls/md.h" +#include "esp_ota_ops.h" +#include "esp_flash_partitions.h" +#include "esp_partition.h" + +#elif MCU_VARIANT == MCU_NRF52 +#include "Adafruit_nRFCrypto.h" + +// size of chunk to retrieve from flash sector +#define CHUNK_SIZE 128 + +#define END_SECTION_SIZE 256 + +#if defined(NRF52840_XXAA) +// https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map +// each section follows along from one another, in this order +// this is always at the start of the memory map +#define APPLICATION_START 0x26000 + +#define USER_DATA_START 0xED000 + +#define IMG_SIZE_START 0xFF008 +#endif + +#endif + +// Forward declaration from Utilities.h +void eeprom_update(int mapped_addr, uint8_t byte); +uint8_t eeprom_read(uint32_t addr); +void hard_reset(void); + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + void eeprom_flush(); +#endif + +const uint8_t dev_keys [] PROGMEM = { + 0x0f, 0x15, 0x86, 0x74, 0xa0, 0x7d, 0xf2, 0xde, 0x32, 0x11, 0x29, 0xc1, 0x0d, 0xda, 0xcc, 0xc3, + 0xe1, 0x9b, 0xac, 0xf2, 0x27, 0x06, 0xee, 0x89, 0x1f, 0x7a, 0xfc, 0xc3, 0x6a, 0xf5, 0x38, 0x08 +}; + +#define DEV_SIG_LEN 64 +uint8_t dev_sig[DEV_SIG_LEN]; + +#define DEV_KEY_LEN 32 +uint8_t dev_k_prv[DEV_KEY_LEN]; +uint8_t dev_k_pub[DEV_KEY_LEN]; + +#define DEV_HASH_LEN 32 +uint8_t dev_hash[DEV_HASH_LEN]; +uint8_t dev_partition_table_hash[DEV_HASH_LEN]; +uint8_t dev_bootloader_hash[DEV_HASH_LEN]; +uint8_t dev_firmware_hash[DEV_HASH_LEN]; +uint8_t dev_firmware_hash_target[DEV_HASH_LEN]; + +#define EEPROM_SIG_LEN 128 +uint8_t dev_eeprom_signature[EEPROM_SIG_LEN]; + +bool dev_signature_validated = false; +bool fw_signature_validated = true; + +#define DEV_SIG_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN +#define dev_sig_addr(a) (a+DEV_SIG_OFFSET) + +#define DEV_FWHASH_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN-DEV_HASH_LEN +#define dev_fwhash_addr(a) (a+DEV_FWHASH_OFFSET) + +bool device_signatures_ok() { + return dev_signature_validated && fw_signature_validated; +} + +void device_validate_signature() { + int n_keys = sizeof(dev_keys)/DEV_KEY_LEN; + bool valid_signature_found = false; + for (int i = 0; i < n_keys; i++) { + memcpy(dev_k_pub, dev_keys+DEV_KEY_LEN*i, DEV_KEY_LEN); + if (Ed25519::verify(dev_sig, dev_k_pub, dev_hash, DEV_HASH_LEN)) { + valid_signature_found = true; + } + } + + if (valid_signature_found) { + dev_signature_validated = true; + } else { + dev_signature_validated = false; + } +} + +void device_save_signature() { + device_validate_signature(); + if (dev_signature_validated) { + for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { + eeprom_update(dev_sig_addr(i), dev_sig[i]); + } + } +} + +void device_load_signature() { + for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { + #if HAS_EEPROM + dev_sig[i] = EEPROM.read(dev_sig_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + dev_sig[i] = eeprom_read(dev_sig_addr(i)); + #endif + } +} + +void device_load_firmware_hash() { + for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { + #if HAS_EEPROM + dev_firmware_hash_target[i] = EEPROM.read(dev_fwhash_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + dev_firmware_hash_target[i] = eeprom_read(dev_fwhash_addr(i)); + #endif + } +} + +void device_save_firmware_hash() { + for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { + eeprom_update(dev_fwhash_addr(i), dev_firmware_hash_target[i]); + } + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + eeprom_flush(); + #endif + if (!fw_signature_validated) hard_reset(); +} + +#if MCU_VARIANT == MCU_NRF52 +uint32_t retrieve_application_size() { + uint8_t bytes[4]; + memcpy(bytes, (const void*)IMG_SIZE_START, 4); + uint32_t fw_len = bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24; + return fw_len; +} + +void calculate_region_hash(unsigned long long start, unsigned long long end, uint8_t* return_hash) { + // this function calculates the hash digest of a region of memory, + // currently it is only designed to work for the application region + uint8_t chunk[CHUNK_SIZE] = {0}; + + // to store potential last chunk of program + uint8_t chunk_next[CHUNK_SIZE] = {0}; + nRFCrypto_Hash hash; + + hash.begin(CRYS_HASH_SHA256_mode); + + uint8_t size; + + while (start < end ) { + const void* src = (const void*)start; + if (start + CHUNK_SIZE >= end) { + size = end - start; + } + else { + size = CHUNK_SIZE; + } + + memcpy(chunk, src, CHUNK_SIZE); + + hash.update(chunk, size); + + start += CHUNK_SIZE; + } + hash.end(return_hash); +} +#endif + +void device_validate_partitions() { + device_load_firmware_hash(); + #if MCU_VARIANT == MCU_ESP32 + esp_partition_t partition; + partition.address = ESP_PARTITION_TABLE_OFFSET; + partition.size = ESP_PARTITION_TABLE_MAX_LEN; + partition.type = ESP_PARTITION_TYPE_DATA; + esp_partition_get_sha256(&partition, dev_partition_table_hash); + partition.address = ESP_BOOTLOADER_OFFSET; + partition.size = ESP_PARTITION_TABLE_OFFSET; + partition.type = ESP_PARTITION_TYPE_APP; + esp_partition_get_sha256(&partition, dev_bootloader_hash); + esp_partition_get_sha256(esp_ota_get_running_partition(), dev_firmware_hash); + #elif MCU_VARIANT == MCU_NRF52 + // todo, add bootloader, partition table, or softdevice? + calculate_region_hash(APPLICATION_START, APPLICATION_START+retrieve_application_size(), dev_firmware_hash); + #endif + #if VALIDATE_FIRMWARE + bool firmware_hash_valid = true; + for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { + if (dev_firmware_hash_target[i] != dev_firmware_hash[i]) { + firmware_hash_valid = false; + break; + } + } + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK + if (!firmware_hash_valid) { + memcpy(dev_firmware_hash_target, dev_firmware_hash, DEV_HASH_LEN); + device_save_firmware_hash(); + firmware_hash_valid = true; + } + #endif + + if (!firmware_hash_valid) { + fw_signature_validated = false; + } + #endif +} + +bool device_firmware_ok() { + return fw_signature_validated; +} + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 +bool device_init() { + if (bt_ready) { + #if MCU_VARIANT == MCU_ESP32 + for (uint8_t i=0; i. + +#include "Graphics.h" +#include + +#if BOARD_MODEL != BOARD_TECHO + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + #include + #include + #include + #define SSD1306_WHITE 0xFFFF + #define SSD1306_BLACK 0x0000 + + #define CLR_BG_STATUSBAR 0x18E3 + #define CLR_BG_PANEL 0x0861 + #define CLR_BORDER 0x4208 + #define CLR_TEXT_PRIMARY 0xFFFF + #define CLR_TEXT_SECONDARY 0xB596 + #define CLR_TEXT_ACCENT 0x07FF + #define CLR_TEXT_DIM 0x6B4D + #define CLR_TEXT_WARN 0xFBE0 + #define CLR_TEXT_ERROR 0xF800 + #define CLR_CABLE_OFF 0x4208 + #define CLR_CABLE_ON 0x07E0 + #define CLR_BT_OFF 0x4208 + #define CLR_BT_ON 0x001F + #define CLR_BT_PAIR 0xFBE0 + #define CLR_BT_CONN 0x07FF + #define CLR_LORA_OFF 0x4208 + #define CLR_LORA_ON 0x07E0 + #define CLR_BAT_FULL 0x07E0 + #define CLR_BAT_MED 0xFBE0 + #define CLR_BAT_LOW 0xF800 + #define CLR_BAT_CHARGE 0x07FF + #define CLR_BAT_OUTLINE 0x8410 + + #define CP_WF_X 0 + #define CP_WF_Y 21 + #define CP_WF_W 22 + #define CP_WF_H 114 + #define CP_WF_BAR_W 20 + #define CP_WF_SIZE 114 + + #define CP_CONTENT_X 23 + #define CP_CONTENT_Y 21 + #define CP_CONTENT_W 217 + #define CP_CONTENT_H 114 + + static const uint16_t wf_palette[9] = { + 0x0000, 0x0011, 0x001F, 0x07FF, + 0x07E0, 0xFFE0, 0xFBE0, 0xF800, 0xFFFF + }; + #elif BOARD_MODEL == BOARD_TDECK + #include + #elif BOARD_MODEL == BOARD_HELTEC_T114 + #include "ST7789.h" + #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #include + #else + #include + #include + #endif + +#else + void (*display_callback)(); + void display_add_callback(void (*callback)()) { display_callback = callback; } + void busyCallback(const void* p) { display_callback(); } + #define SSD1306_BLACK GxEPD_BLACK + #define SSD1306_WHITE GxEPD_WHITE + #include + #include +#endif + +#include "Fonts/Org_01.h" +#define DISP_W 128 +#define DISP_H 64 + +#if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 + #define DISP_RST -1 + #define DISP_ADDR 0x3C +#elif BOARD_MODEL == BOARD_TBEAM + #define DISP_RST 13 + #define DISP_ADDR 0x3C + #define DISP_CUSTOM_ADDR true +#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 SDA_OLED 4 +#elif BOARD_MODEL == BOARD_HELTEC32_V3 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 +#elif BOARD_MODEL == BOARD_HELTEC32_V4 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 +#elif BOARD_MODEL == BOARD_RAK4631 + // RAK1921/SSD1306 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 14 + #define SDA_OLED 13 +#elif BOARD_MODEL == BOARD_RNODE_NG_21 + #define DISP_RST -1 + #define DISP_ADDR 0x3C +#elif BOARD_MODEL == BOARD_T3S3 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 17 + #define SDA_OLED 18 +#elif BOARD_MODEL == BOARD_TECHO + SPIClass displaySPI = SPIClass(NRF_SPIM0, pin_disp_miso, pin_disp_sck, pin_disp_mosi); + #define DISP_W 128 + #define DISP_H 64 + #define DISP_ADDR -1 +#elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 + #define DISP_CUSTOM_ADDR false +#elif BOARD_MODEL == BOARD_XIAO_S3 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 6 + #define SDA_OLED 5 + #define DISP_CUSTOM_ADDR true +#elif BOARD_MODEL == BOARD_CARDPUTER_ADV + #undef DISP_W + #undef DISP_H + #define DISP_W 240 + #define DISP_H 135 + #define DISP_ADDR -1 + #define DISP_CUSTOM_ADDR false +#else + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define DISP_CUSTOM_ADDR true +#endif + +#define SMALL_FONT &Org_01 + +#if BOARD_MODEL == BOARD_TDECK + Adafruit_ST7789 display = Adafruit_ST7789(DISPLAY_CS, DISPLAY_DC, -1); + #define SSD1306_WHITE ST77XX_WHITE + #define SSD1306_BLACK ST77XX_BLACK +#elif BOARD_MODEL == BOARD_HELTEC_T114 + ST7789Spi display(&SPI1, DISPLAY_RST, DISPLAY_DC, DISPLAY_CS); + #define SSD1306_WHITE ST77XX_WHITE + #define SSD1306_BLACK ST77XX_BLACK +#elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1); + #define SSD1306_WHITE SH110X_WHITE + #define SSD1306_BLACK SH110X_BLACK +#elif BOARD_MODEL == BOARD_CARDPUTER_ADV + M5Canvas m5canvas(&M5Cardputer.Display); +#elif BOARD_MODEL == BOARD_TECHO + GxEPD2_BW display(GxEPD2_154_D67(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy)); + uint32_t last_epd_refresh = 0; + uint32_t last_epd_full_refresh = 0; + #define REFRESH_PERIOD 300000 +#else + Adafruit_SSD1306 display(DISP_W, DISP_H, &Wire, DISP_RST); +#endif + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + float disp_target_fps = 3; +#else + float disp_target_fps = 7; +#endif +float epd_update_fps = 0.5; + +#define DISP_MODE_UNKNOWN 0x00 +#define DISP_MODE_LANDSCAPE 0x01 +#define DISP_MODE_PORTRAIT 0x02 +#define DISP_PIN_SIZE 6 +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + #define DISPLAY_BLANKING_TIMEOUT 30*1000 + #define CARDPUTER_ADV_DISPLAY_INTENSITY_DEFAULT 96 +#else + #define DISPLAY_BLANKING_TIMEOUT 15*1000 +#endif +uint8_t disp_mode = DISP_MODE_UNKNOWN; +uint8_t disp_ext_fb = false; +unsigned char fb[512]; +uint32_t last_disp_update = 0; +uint32_t last_unblank_event = 0; +uint32_t display_blanking_timeout = DISPLAY_BLANKING_TIMEOUT; +uint8_t display_unblank_intensity = display_intensity; +bool display_blanked = false; +bool display_blank_frame_drawn = false; +bool display_tx = false; +bool recondition_display = false; +int disp_update_interval = 1000/disp_target_fps; +int epd_update_interval = 1000/disp_target_fps; +uint32_t last_page_flip = 0; +int page_interval = 4000; +bool device_signatures_ok(); +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; +int p_ad_y = 0; +int p_as_x = 0; +int p_as_y = 0; + +#define START_PAGE 0 +const uint8_t pages = 3; +uint8_t disp_page = START_PAGE; + +#if BOARD_MODEL != BOARD_CARDPUTER_ADV + GFXcanvas1 stat_area(64, 64); + GFXcanvas1 disp_area(64, 64); +#else + int cp_waterfall[CP_WF_SIZE] = {0}; + int cp_waterfall_head = 0; + uint8_t cp_charge_tick = 0; + uint32_t cp_pairing_tip_until = 0; + uint32_t cp_ble_notice_until = 0; + bool cp_ble_notice_enabled = false; +#endif + +static const uint8_t one_counts[256] = { + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 +}; + +void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour); + +void update_area_positions() { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 16; + p_ad_y = 64; + p_as_x = 16; + p_as_y = p_ad_y+126; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 96; + p_as_x = 126; + p_as_y = p_ad_y; + } + #elif BOARD_MODEL == BOARD_TECHO + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 61; + p_ad_y = 36; + p_as_x = 64; + p_as_y = 64+36; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 64; + p_as_y = 0; + } + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 64; + p_as_y = 0; + #else + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 0 * DISPLAY_SCALE; + p_ad_y = 0 * DISPLAY_SCALE; + p_as_x = 0 * DISPLAY_SCALE; + p_as_y = 64 * DISPLAY_SCALE; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0 * DISPLAY_SCALE; + p_ad_y = 0 * DISPLAY_SCALE; + p_as_x = 64 * DISPLAY_SCALE; + p_as_y = 0 * DISPLAY_SCALE; + } + #endif +} + +uint8_t display_contrast = 0x00; +#if BOARD_MODEL == BOARD_TBEAM_S_V1 + void set_contrast(Adafruit_SH1106G *display, uint8_t value) { + } +#elif BOARD_MODEL == BOARD_HELTEC_T114 + void set_contrast(ST7789Spi *display, uint8_t value) { } +#elif BOARD_MODEL == BOARD_TECHO + void set_contrast(void *display, uint8_t value) { + if (value == 0) { analogWrite(pin_backlight, 0); } + else { analogWrite(pin_backlight, value); } + } +#elif BOARD_MODEL == BOARD_TDECK + void set_contrast(Adafruit_ST7789 *display, uint8_t value) { + static uint8_t level = 0; + static uint8_t steps = 16; + if (value > 15) value = 15; + if (value == 0) { + digitalWrite(DISPLAY_BL_PIN, 0); + delay(3); + level = 0; + return; + } + if (level == 0) { + digitalWrite(DISPLAY_BL_PIN, 1); + level = steps; + delayMicroseconds(30); + } + int from = steps - level; + int to = steps - value; + int num = (steps + to - from) % steps; + for (int i = 0; i < num; i++) { + digitalWrite(DISPLAY_BL_PIN, 0); + digitalWrite(DISPLAY_BL_PIN, 1); + } + level = value; + } +#elif BOARD_MODEL == BOARD_CARDPUTER_ADV + void set_contrast(M5Canvas *display, uint8_t value) { + M5Cardputer.Display.setBrightness(value); + } +#else + void set_contrast(Adafruit_SSD1306 *display, uint8_t contrast) { + display->ssd1306_command(SSD1306_SETCONTRAST); + display->ssd1306_command(contrast); + } +#endif + +bool display_init() { + #if HAS_DISPLAY + #if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 + int pin_display_en = 16; + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + #elif BOARD_MODEL == BOARD_T3S3 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + // enable vext / pin 36 + pinMode(Vext, OUTPUT); + digitalWrite(Vext, LOW); + delay(50); + int pin_display_en = 21; + pinMode(pin_display_en, OUTPUT); + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + delay(50); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + // enable vext / pin 36 + pinMode(Vext, OUTPUT); + digitalWrite(Vext, LOW); + delay(50); + int pin_display_en = 21; + pinMode(pin_display_en, OUTPUT); + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + delay(50); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + int pin_display_en = 16; + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + pinMode(PIN_T114_TFT_EN, OUTPUT); + digitalWrite(PIN_T114_TFT_EN, LOW); + #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); + display.epd2.setBusyCallback(busyCallback); + #if HAS_BACKLIGHT + pinMode(pin_backlight, OUTPUT); + analogWrite(pin_backlight, 0); + #endif + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_XIAO_S3 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + { + auto cfg = M5.config(); + cfg.internal_spk = false; + cfg.internal_mic = false; + M5Cardputer.begin(cfg, true); + } + #endif + + #if HAS_EEPROM + uint8_t display_rotation = EEPROM.read(eeprom_addr(ADDR_CONF_DROT)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t display_rotation = eeprom_read(eeprom_addr(ADDR_CONF_DROT)); + #endif + if (display_rotation < 0 or display_rotation > 3) display_rotation = 0xFF; + + #if DISP_CUSTOM_ADDR == true + #if HAS_EEPROM + uint8_t display_address = EEPROM.read(eeprom_addr(ADDR_CONF_DADR)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t display_address = eeprom_read(eeprom_addr(ADDR_CONF_DADR)); + #endif + if (display_address == 0xFF) display_address = DISP_ADDR; + #else + uint8_t display_address = DISP_ADDR; + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + display_blanking_enabled = true; + display_blanking_timeout = DISPLAY_BLANKING_TIMEOUT; + #endif + + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { + uint8_t db_timeout = EEPROM.read(eeprom_addr(ADDR_CONF_DBLK)); + if (db_timeout == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = db_timeout*1000; + } + } + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { + uint8_t db_timeout = eeprom_read(eeprom_addr(ADDR_CONF_DBLK)); + if (db_timeout == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = db_timeout*1000; + } + } + #endif + + #if BOARD_MODEL == BOARD_TECHO + // Don't check if display is actually connected + if(false) { + #elif BOARD_MODEL == BOARD_TDECK + display.init(240, 320); + display.setSPISpeed(80e6); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + display.init(); + // set white as default pixel colour for Heltec T114 + display.setRGB(COLOR565(0xFF, 0xFF, 0xFF)); + if (false) { + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + if (false) { + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + if (!display.begin(display_address, true)) { + #else + if (!display.begin(SSD1306_SWITCHCAPVCC, display_address)) { + #endif + return false; + } else { + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + set_contrast(&m5canvas, display_contrast); + #else + set_contrast(&display, display_contrast); + #endif + if (display_rotation != 0xFF) { + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + M5Cardputer.Display.setRotation(display_rotation); + if (M5Cardputer.Display.width() >= M5Cardputer.Display.height()) { + disp_mode = DISP_MODE_LANDSCAPE; + } else { + disp_mode = DISP_MODE_PORTRAIT; + } + #else + if (display_rotation == 0 || display_rotation == 2) { + disp_mode = DISP_MODE_LANDSCAPE; + } else { + disp_mode = DISP_MODE_PORTRAIT; + } + display.setRotation(display_rotation); + #endif + } else { + #if BOARD_MODEL == BOARD_RNODE_NG_20 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TBEAM + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_RAK4631 + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TDECK + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_TECHO + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + disp_mode = DISP_MODE_LANDSCAPE; + M5Cardputer.Display.setRotation(1); + #else + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #endif + } + + update_area_positions(); + + for (int i = 0; i < WATERFALL_SIZE; i++) { waterfall[i] = 0; } + + last_page_flip = millis(); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + M5Cardputer.Display.fillScreen(SSD1306_BLACK); + m5canvas.setColorDepth(8); + if (!m5canvas.createSprite(DISP_W, DISP_H)) { + return false; + } + m5canvas.setPaletteColor(0, SSD1306_BLACK); + m5canvas.setPaletteColor(1, CLR_BG_STATUSBAR); + m5canvas.setPaletteColor(2, CLR_BG_PANEL); + m5canvas.setPaletteColor(3, CLR_BORDER); + m5canvas.setPaletteColor(4, CLR_TEXT_PRIMARY); + m5canvas.setPaletteColor(5, CLR_TEXT_SECONDARY); + m5canvas.setPaletteColor(6, CLR_TEXT_ACCENT); + m5canvas.setPaletteColor(7, CLR_TEXT_WARN); + m5canvas.setPaletteColor(8, CLR_TEXT_ERROR); + m5canvas.setPaletteColor(9, CLR_LORA_ON); + m5canvas.setPaletteColor(10, CLR_CABLE_ON); + m5canvas.setTextSize(1); + m5canvas.fillSprite(SSD1306_BLACK); + m5canvas.pushSprite(0, 0); + #else + stat_area.cp437(true); + disp_area.cp437(true); + #endif + + #if BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_CARDPUTER_ADV + display.cp437(true); + #endif + + #if HAS_EEPROM + display_intensity = EEPROM.read(eeprom_addr(ADDR_CONF_DINT)); + #elif MCU_VARIANT == MCU_NRF52 + display_intensity = eeprom_read(eeprom_addr(ADDR_CONF_DINT)); + #endif + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + if (display_intensity == 0xFF) { + display_intensity = CARDPUTER_ADV_DISPLAY_INTENSITY_DEFAULT; + } + #endif + display_unblank_intensity = display_intensity; + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + set_contrast(&m5canvas, display_intensity); + #endif + + #if BOARD_MODEL == BOARD_TECHO + #if HAS_BACKLIGHT + if (display_intensity == 0) { analogWrite(pin_backlight, 0); } + else { analogWrite(pin_backlight, display_intensity); } + #endif + #endif + + #if BOARD_MODEL == BOARD_TDECK + display.fillScreen(SSD1306_BLACK); + #endif + + #if BOARD_MODEL == BOARD_HELTEC_T114 + // Enable backlight led (display is always black without this) + fillRect(p_ad_x, p_ad_y, 128, 128, SSD1306_BLACK); + fillRect(p_as_x, p_as_y, 128, 128, SSD1306_BLACK); + pinMode(PIN_T114_TFT_BLGT, OUTPUT); + digitalWrite(PIN_T114_TFT_BLGT, LOW); + #endif + + return true; + } + #else + return false; + #endif +} + +#if BOARD_MODEL != BOARD_CARDPUTER_ADV +// Draws a line on the screen +void drawLine(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if(colour == SSD1306_WHITE){ + display.setColor(WHITE); + } else if(colour == SSD1306_BLACK) { + display.setColor(BLACK); + } + display.drawLine(x, y, width, height); + #else + display.drawLine(x, y, width, height, colour); + #endif +} + +// Draws a filled rectangle on the screen +void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if(colour == SSD1306_WHITE){ + display.setColor(WHITE); + } else if(colour == SSD1306_BLACK) { + display.setColor(BLACK); + } + display.fillRect(x, y, width, height); + #else + display.fillRect(x, y, width, height, colour); + #endif +} + +// Draws a bitmap to the display and auto scales it based on the boards configured DISPLAY_SCALE +void drawBitmap(int16_t startX, int16_t startY, const uint8_t* bitmap, int16_t bitmapWidth, int16_t bitmapHeight, uint16_t foregroundColour, uint16_t backgroundColour) { + #if DISPLAY_SCALE == 1 + display.drawBitmap(startX, startY, bitmap, bitmapWidth, bitmapHeight, foregroundColour, backgroundColour); + #else + for(int16_t row = 0; row < bitmapHeight; row++){ + for(int16_t col = 0; col < bitmapWidth; col++){ + + // determine index and bitmask + int16_t index = row * ((bitmapWidth + 7) / 8) + (col / 8); + uint8_t bitmask = 1 << (7 - (col % 8)); + + // check if the current pixel is set in the bitmap + if(bitmap[index] & bitmask){ + // draw a scaled rectangle for the foreground pixel + fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, foregroundColour); + } else { + // draw a scaled rectangle for the background pixel + fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, backgroundColour); + } + + } + } + #endif +} + +extern uint8_t wifi_mode; +extern bool wifi_is_connected(); +extern bool wifi_host_is_connected(); +void draw_cable_icon(int px, int py) { + #if HAS_WIFI + if (wifi_mode == WR_WIFI_OFF) { + if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + } else { + if (wifi_mode == WR_WIFI_STA) { + if (wifi_is_connected()) { + stat_area.drawBitmap(px, py, bm_wifi+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + if (!wifi_host_is_connected()) { stat_area.fillRect(px+5, py+12, 6, 3, SSD1306_BLACK); } + } else { stat_area.drawBitmap(px, py, bm_wifi+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + + } else if (wifi_mode == WR_WIFI_AP) { + if (wifi_host_is_connected()) { stat_area.drawBitmap(px, py, bm_wifi+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + else { stat_area.drawBitmap(px, py, bm_wifi+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + + } else { + if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + } + } + + #else + if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } + #endif +} + +void draw_bt_icon(int px, int py) { + if (bt_state == BT_STATE_OFF) { + stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_ON) { + stat_area.drawBitmap(px, py, bm_bt+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_PAIRING) { + stat_area.drawBitmap(px, py, bm_bt+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_CONNECTED) { + stat_area.drawBitmap(px, py, bm_bt+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +void draw_lora_icon(int px, int py) { + if (radio_online) { + stat_area.drawBitmap(px, py, bm_rf+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_rf+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +void draw_mw_icon(int px, int py) { + if (mw_radio_online) { + stat_area.drawBitmap(px, py, bm_rf+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_rf+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +uint8_t charge_tick = 0; +void draw_battery_bars(int px, int py) { + if (pmu_ready) { + if (battery_ready) { + if (battery_installed) { + float battery_value = battery_percent; + + // Disable charging state display for now, since + // boards without dedicated PMU are completely + // unreliable for determining actual charging state. + bool disable_charge_status = false; + if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING) { + disable_charge_status = true; + } + + if (battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { + float battery_prog = battery_percent; + if (battery_prog > 85) { battery_prog = 84; } + if (charge_tick < battery_prog ) { charge_tick = battery_prog; } + battery_value = charge_tick; + charge_tick += 3; + if (charge_tick > 100) charge_tick = 0; + } + + if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (battery_state == BATTERY_STATE_CHARGED) { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } else { + // stat_area.fillRect(px, py, 14, 3, SSD1306_BLACK); + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawRect(px-2, py-2, 17, 7, SSD1306_WHITE); + stat_area.drawLine(px+15, py, px+15, py+3, SSD1306_WHITE); + if (battery_value > 7) stat_area.drawLine(px, py, px, py+2, SSD1306_WHITE); + if (battery_value > 20) stat_area.drawLine(px+1*2, py, px+1*2, py+2, SSD1306_WHITE); + if (battery_value > 33) stat_area.drawLine(px+2*2, py, px+2*2, py+2, SSD1306_WHITE); + if (battery_value > 46) stat_area.drawLine(px+3*2, py, px+3*2, py+2, SSD1306_WHITE); + if (battery_value > 59) stat_area.drawLine(px+4*2, py, px+4*2, py+2, SSD1306_WHITE); + if (battery_value > 72) stat_area.drawLine(px+5*2, py, px+5*2, py+2, SSD1306_WHITE); + if (battery_value > 85) stat_area.drawLine(px+6*2, py, px+6*2, py+2, SSD1306_WHITE); + } + } + } else { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } + } + } else { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } +} + +#define Q_SNR_STEP 2.0 +#define Q_SNR_MIN_BASE -9.0 +#define Q_SNR_MAX 6.0 +void draw_quality_bars(int px, int py) { + stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); + if (radio_online) { + signed char t_snr = (signed int)last_snr_raw; + int snr_int = (int)t_snr; + float snr_min = Q_SNR_MIN_BASE-(int)lora_sf*Q_SNR_STEP; + float snr_span = (Q_SNR_MAX-snr_min); + float snr = ((int)snr_int) * 0.25; + float quality = ((snr-snr_min)/(snr_span))*100; + if (quality > 100.0) quality = 100.0; + if (quality < 0.0) quality = 0.0; + + // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); + if (quality > 0) stat_area.drawLine(px+0*2, py+7, px+0*2, py+6, SSD1306_WHITE); + if (quality > 15) stat_area.drawLine(px+1*2, py+7, px+1*2, py+5, SSD1306_WHITE); + if (quality > 30) stat_area.drawLine(px+2*2, py+7, px+2*2, py+4, SSD1306_WHITE); + if (quality > 45) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); + if (quality > 60) stat_area.drawLine(px+4*2, py+7, px+4*2, py+2, SSD1306_WHITE); + if (quality > 75) stat_area.drawLine(px+5*2, py+7, px+5*2, py+1, SSD1306_WHITE); + if (quality > 90) stat_area.drawLine(px+6*2, py+7, px+6*2, py+0, SSD1306_WHITE); + } +} + +#if MODEM == SX1280 + #define S_RSSI_MIN -105.0 + #define S_RSSI_MAX -65.0 +#else + #define S_RSSI_MIN -135.0 + #define S_RSSI_MAX -75.0 +#endif +#define S_RSSI_SPAN (S_RSSI_MAX-S_RSSI_MIN) +void draw_signal_bars(int px, int py) { + stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); + + if (radio_online) { + int rssi_val = last_rssi; + if (rssi_val < S_RSSI_MIN) rssi_val = S_RSSI_MIN; + if (rssi_val > S_RSSI_MAX) rssi_val = S_RSSI_MAX; + int signal = ((rssi_val - S_RSSI_MIN)*(1.0/S_RSSI_SPAN))*100.0; + + if (signal > 100.0) signal = 100.0; + if (signal < 0.0) signal = 0.0; + + // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); + if (signal > 85) stat_area.drawLine(px+0*2, py+7, px+0*2, py+0, SSD1306_WHITE); + if (signal > 72) stat_area.drawLine(px+1*2, py+7, px+1*2, py+1, SSD1306_WHITE); + if (signal > 59) stat_area.drawLine(px+2*2, py+7, px+2*2, py+2, SSD1306_WHITE); + if (signal > 46) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); + if (signal > 33) stat_area.drawLine(px+4*2, py+7, px+4*2, py+4, SSD1306_WHITE); + if (signal > 20) stat_area.drawLine(px+5*2, py+7, px+5*2, py+5, SSD1306_WHITE); + if (signal > 7) stat_area.drawLine(px+6*2, py+7, px+6*2, py+6, SSD1306_WHITE); + } +} + +#if MODEM == SX1280 + #define WF_TX_SIZE 5 +#else + #define WF_TX_SIZE 5 +#endif +#define WF_RSSI_MAX -60 +#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; + if (rssi_val > WF_RSSI_MAX) rssi_val = WF_RSSI_MAX; + 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; + } + + stat_area.fillRect(px,py,WF_PIXEL_WIDTH, WATERFALL_SIZE, SSD1306_BLACK); + 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) { + 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++) { + stat_area.drawPixel(px+ti*2+o, py+i, SSD1306_WHITE); + } + } + } +} + +bool stat_area_intialised = false; +void draw_stat_area() { + if (device_init_done) { + if (!stat_area_intialised) { + stat_area.drawBitmap(0, 0, bm_frame, 64, 64, SSD1306_WHITE, SSD1306_BLACK); + stat_area_intialised = true; + } + + draw_cable_icon(3, 8); + draw_bt_icon(3, 30); + draw_lora_icon(45, 8); + draw_mw_icon(45, 30); + draw_battery_bars(4, 58); + draw_quality_bars(28, 56); + draw_signal_bars(44, 56); + if (radio_online) { + draw_waterfall(27, 4); + } + } +} + +void update_stat_area() { + if (eeprom_ok && !firmware_update_mode && !console_active) { + + draw_stat_area(); + if (disp_mode == DISP_MODE_PORTRAIT) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + drawBitmap(p_as_x+2, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (device_init_done && !disp_ext_fb) drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); + } + + } else { + if (firmware_update_mode) { + drawBitmap(p_as_x, p_as_y, bm_updating, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); + } else if (console_active && device_init_done) { + drawBitmap(p_as_x, p_as_y, bm_console, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); + if (disp_mode == DISP_MODE_LANDSCAPE) { + drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); + } + } + } +} + +extern char bt_devname[11]; +extern char bt_dh[16]; +#if HAS_WIFI + extern IPAddress wr_device_ip; +#endif +void draw_disp_area() { + if (!device_init_done || firmware_update_mode) { + uint8_t p_by = 37; + if (disp_mode == DISP_MODE_LANDSCAPE || firmware_update_mode) { + p_by = 18; + disp_area.fillRect(0, 0, disp_area.width(), disp_area.height(), SSD1306_BLACK); + } + if (!device_init_done) disp_area.drawBitmap(0, p_by, bm_boot, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + if (firmware_update_mode) disp_area.drawBitmap(0, p_by, bm_fw_update, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (!disp_ext_fb or bt_ssp_pin != 0) { + if (radio_online && display_diagnostics) { + disp_area.fillRect(0,8,disp_area.width(),37, SSD1306_BLACK); disp_area.fillRect(0,37,disp_area.width(),27, SSD1306_WHITE); + disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(1); + + disp_area.setCursor(2, 13); + disp_area.print("On"); + disp_area.setCursor(14, 13); + disp_area.print("@"); + disp_area.setCursor(21, 13); + disp_area.printf("%.1fKbps", (float)lora_bitrate/1000.0); + + //disp_area.setCursor(31, 23-1); + disp_area.setCursor(2, 23-1); + disp_area.print("Airtime:"); + + disp_area.setCursor(11, 33-1); + if (total_channel_util < 0.099) { + //disp_area.printf("%.1f%%", total_channel_util*100.0); + disp_area.printf("%.1f%%", airtime*100.0); + } else { + //disp_area.printf("%.0f%%", total_channel_util*100.0); + disp_area.printf("%.0f%%", airtime*100.0); + } + disp_area.drawBitmap(2, 26-1, bm_hg_low, 5, 9, SSD1306_WHITE, SSD1306_BLACK); + + disp_area.setCursor(32+11, 33-1); + if (longterm_channel_util < 0.099) { + //disp_area.printf("%.1f%%", longterm_channel_util*100.0); + disp_area.printf("%.1f%%", longterm_airtime*100.0); + } else { + //disp_area.printf("%.0f%%", longterm_channel_util*100.0); + disp_area.printf("%.0f%%", longterm_airtime*100.0); + } + disp_area.drawBitmap(32+2, 26-1, bm_hg_high, 5, 9, SSD1306_WHITE, SSD1306_BLACK); + + + disp_area.setTextColor(SSD1306_BLACK); + disp_area.setCursor(2, 46); + disp_area.print("Channel"); + disp_area.setCursor(38, 46); + disp_area.print("Load:"); + + disp_area.setCursor(11, 57); + if (total_channel_util < 0.099) { + //disp_area.printf("%.1f%%", airtime*100.0); + disp_area.printf("%.1f%%", total_channel_util*100.0); + } else { + //disp_area.printf("%.0f%%", airtime*100.0); + disp_area.printf("%.0f%%", total_channel_util*100.0); + } + disp_area.drawBitmap(2, 50, bm_hg_low, 5, 9, SSD1306_BLACK, SSD1306_WHITE); + + disp_area.setCursor(32+11, 57); + if (longterm_channel_util < 0.099) { + //disp_area.printf("%.1f%%", longterm_airtime*100.0); + disp_area.printf("%.1f%%", longterm_channel_util*100.0); + } else { + //disp_area.printf("%.0f%%", longterm_airtime*100.0); + disp_area.printf("%.0f%%", longterm_channel_util*100.0); + } + disp_area.drawBitmap(32+2, 50, bm_hg_high, 5, 9, SSD1306_BLACK, SSD1306_WHITE); + + } else { + if (device_signatures_ok()) { disp_area.drawBitmap(0, 0, bm_def_lc, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } + else { disp_area.drawBitmap(0, 0, bm_def, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } + + bool display_ip = false; + #if HAS_WIFI + if (wifi_is_connected() && disp_page%2 == 1) { display_ip = true; } + #endif + if (display_ip) { + #if HAS_WIFI + uint8_t ones = 3+one_counts[wr_device_ip[0]]+one_counts[wr_device_ip[1]]+one_counts[wr_device_ip[2]]+one_counts[wr_device_ip[3]]; + uint8_t chars = 7; + for (uint8_t i = 0; i<4; i++) { if (wr_device_ip[i] > 9) { chars++; } if (wr_device_ip[i] > 99) { chars++; } } + uint8_t width = chars*6-(ones*4); + int alignment_offset = disp_area.width()-width; + int ipxpos = alignment_offset; + disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(1); + disp_area.fillRect(0, 20, disp_area.width(), 17, SSD1306_BLACK); + disp_area.setCursor(3, 34-8); disp_area.print("WiFi IP:"); + disp_area.setCursor(ipxpos, 34); disp_area.print(wr_device_ip); + #endif + } else { + disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(2); + disp_area.fillRect(0, 20, disp_area.width(), 17, SSD1306_BLACK); uint8_t ofsc = 0; + if ((bt_dh[14] & 0b00001111) == 0x01) { ofsc += 8; } + if ((bt_dh[14] >> 4) == 0x01) { ofsc += 8; } + if ((bt_dh[15] & 0b00001111) == 0x01) { ofsc += 8; } + if ((bt_dh[15] >> 4) == 0x01) { ofsc += 8; } + disp_area.setCursor(17+ofsc, 32); disp_area.printf("%02X%02X", bt_dh[14], bt_dh[15]); + } + } + + if (!hw_ready || radio_error || !device_firmware_ok()) { + if (!device_firmware_ok()) { + disp_area.drawBitmap(0, 37, bm_fw_corrupt, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (!modem_installed) { + disp_area.drawBitmap(0, 37, bm_no_radio, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_conf_missing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } + } else if (bt_state == BT_STATE_PAIRING and bt_ssp_pin != 0) { + char *pin_str = (char*)malloc(DISP_PIN_SIZE+1); + sprintf(pin_str, "%06d", bt_ssp_pin); + + disp_area.drawBitmap(0, 37, bm_pairing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + for (int i = 0; i < DISP_PIN_SIZE; i++) { + uint8_t numeric = pin_str[i]-48; + uint8_t offset = numeric*5; + disp_area.drawBitmap(7+9*i, 37+16, bm_n_uh+offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); + } + free(pin_str); + } else { + if (millis()-last_page_flip >= page_interval) { + disp_page = (++disp_page%pages); + last_page_flip = millis(); + if (not community_fw and disp_page == 0) disp_page = 1; + } + + if (radio_online) { + if (!display_diagnostics) { + disp_area.drawBitmap(0, 37, bm_online, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else { + if (disp_page == 0) { + if (true || device_signatures_ok()) { + disp_area.drawBitmap(0, 37, bm_checks, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_nfr, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else if (disp_page == 1) { + if (!console_active) { + disp_area.drawBitmap(0, 37, bm_hwok, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_console_active, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else if (disp_page == 2) { + disp_area.drawBitmap(0, 37, bm_version, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + char *v_str = (char*)malloc(3+1); + sprintf(v_str, "%01d%02d", MAJ_VERS, MIN_VERS); + for (int i = 0; i < 3; i++) { + uint8_t numeric = v_str[i]-48; uint8_t bm_offset = numeric*5; + uint8_t dxp = 20; + if (i == 1) dxp += 9*1+4; + if (i == 2) dxp += 9*2+4; + disp_area.drawBitmap(dxp, 37+16, bm_n_uh+bm_offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); + } + free(v_str); + disp_area.drawLine(27, 37+19, 28, 37+19, SSD1306_BLACK); + disp_area.drawLine(27, 37+20, 28, 37+20, SSD1306_BLACK); + } + } + } + } else { + disp_area.drawBitmap(0, 0, fb, disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } + } +} + +void update_disp_area() { + draw_disp_area(); + + drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (disp_mode == DISP_MODE_LANDSCAPE) { + if (device_init_done && !firmware_update_mode && !disp_ext_fb) { + drawLine(0, 0, 0, 63, SSD1306_WHITE); + } + } +} + +void display_recondition() { + #if PLATFORM == PLATFORM_ESP32 + for (uint8_t iy = 0; iy < disp_area.height(); iy++) { + unsigned char rand_seg [] = {random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF)}; + stat_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); + disp_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); + } + + drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (disp_mode == DISP_MODE_PORTRAIT) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } + #endif +} +#else +void display_recondition() { } + +#define Q_SNR_STEP 2.0 +#define Q_SNR_MIN_BASE -9.0 +#define Q_SNR_MAX 6.0 +#if MODEM == SX1280 + #define S_RSSI_MIN -105.0 + #define S_RSSI_MAX -65.0 +#else + #define S_RSSI_MIN -135.0 + #define S_RSSI_MAX -75.0 +#endif +#define S_RSSI_SPAN (S_RSSI_MAX-S_RSSI_MIN) +#if MODEM == SX1280 + #define WF_TX_SIZE 5 +#else + #define WF_TX_SIZE 5 +#endif +#define WF_RSSI_MAX -60 +#define WF_RSSI_MIN -135 +#define WF_RSSI_SPAN (WF_RSSI_MAX-WF_RSSI_MIN) + +extern char bt_devname[11]; +extern char bt_dh[16]; + +uint16_t cp_signal_color(float percent) { + if (percent > 66.0f) return 0x07E0; + if (percent > 33.0f) return 0xFBE0; + return 0xF800; +} + +uint16_t cp_load_color(float percent) { + if (percent < 33.0f) return 0x07E0; + if (percent < 66.0f) return 0xFBE0; + return 0xF800; +} + +void cp_draw_gradient_bar(int x, int y, int w, int h, float percent, bool load_colours) { + if (percent > 100.0f) percent = 100.0f; + if (percent < 0.0f) percent = 0.0f; + int fill_w = (int)((percent / 100.0f) * (w - 2)); + uint16_t fill_clr = load_colours ? cp_load_color(percent) : cp_signal_color(percent); + m5canvas.drawRect(x, y, w, h, CLR_BORDER); + if (fill_w > 0) { + m5canvas.fillRect(x + 1, y + 1, fill_w, h - 2, fill_clr); + } +} + +void cp_draw_usb_icon(int x, int y, uint16_t colour) { + m5canvas.fillRect(x + 3, y, 7, 10, colour); + m5canvas.fillRect(x + 4, y + 10, 5, 4, colour); + m5canvas.drawPixel(x + 1, y + 3, colour); + m5canvas.drawPixel(x + 11, y + 3, colour); +} + +void cp_draw_bt_icon(int x, int y, uint16_t colour) { + m5canvas.drawLine(x + 5, y, x + 5, y + 13, colour); + m5canvas.drawLine(x + 5, y, x + 9, y + 4, colour); + m5canvas.drawLine(x + 9, y + 4, x + 0, y + 10, colour); + m5canvas.drawLine(x + 0, y + 3, x + 9, y + 9, colour); + m5canvas.drawLine(x + 9, y + 9, x + 5, y + 13, colour); +} + +void cp_draw_lora_icon(int x, int y, uint16_t colour) { + m5canvas.fillCircle(x + 5, y + 7, 2, colour); + m5canvas.drawCircle(x + 5, y + 7, 5, colour); + m5canvas.drawCircle(x + 5, y + 7, 8, colour); +} + +void cp_draw_battery(int x, int y) { + if (!pmu_ready || !battery_ready || !battery_installed) { + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_DIM); + m5canvas.setCursor(x, y + 1); + m5canvas.print("PWR"); + return; + } + + float bval = battery_percent; + bool charge_status_known = !battery_indeterminate; + bool charging = charge_status_known && (battery_state == BATTERY_STATE_CHARGING); + bool charged = charge_status_known && (battery_state == BATTERY_STATE_CHARGED); + uint16_t fill_clr = CLR_BAT_FULL; + + if (charged) { + fill_clr = CLR_BAT_CHARGE; + bval = 100.0f; + } else if (charging) { + fill_clr = CLR_BAT_CHARGE; + cp_charge_tick += 3; + if (cp_charge_tick > 100) cp_charge_tick = 0; + bval = cp_charge_tick; + } else if (bval > 60.0f) { + fill_clr = CLR_BAT_FULL; + } else if (bval > 20.0f) { + fill_clr = CLR_BAT_MED; + } else { + fill_clr = CLR_BAT_LOW; + } + + int bw = 20; + int bh = 8; + m5canvas.drawRect(x, y, bw, bh, CLR_BAT_OUTLINE); + m5canvas.fillRect(x + bw, y + 2, 2, 4, CLR_BAT_OUTLINE); + int fill_w = (int)((bval / 100.0f) * (bw - 2)); + if (fill_w > 0) { + m5canvas.fillRect(x + 1, y + 1, fill_w, bh - 2, fill_clr); + } + + if (charging) { + m5canvas.drawLine(x + 10, y + 1, x + 7, y + 5, CLR_TEXT_PRIMARY); + m5canvas.drawLine(x + 7, y + 5, x + 11, y + 5, CLR_TEXT_PRIMARY); + m5canvas.drawLine(x + 11, y + 5, x + 8, y + 8, CLR_TEXT_PRIMARY); + } +} + +void cp_draw_statusbar() { + m5canvas.fillRect(0, 0, DISP_W, 20, CLR_BG_STATUSBAR); + + uint16_t usb_clr = (cable_state == CABLE_STATE_CONNECTED) ? CLR_CABLE_ON : CLR_CABLE_OFF; + cp_draw_usb_icon(3, 3, usb_clr); + + uint16_t bt_clr = CLR_BT_OFF; + if (bt_state == BT_STATE_ON) bt_clr = CLR_BT_ON; + else if (bt_state == BT_STATE_PAIRING) bt_clr = CLR_BT_PAIR; + else if (bt_state == BT_STATE_CONNECTED) bt_clr = CLR_BT_CONN; + cp_draw_bt_icon(18, 3, bt_clr); + + cp_draw_lora_icon(35, 3, radio_online ? CLR_LORA_ON : CLR_LORA_OFF); + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(82, 2); + m5canvas.print("RNode"); + + cp_draw_battery(177, 6); + + m5canvas.setFont(nullptr); + m5canvas.setTextSize(1); + if (pmu_ready && battery_ready && battery_installed) { + float pct = battery_percent; + if (pct > 100.0f) pct = 100.0f; + if (pct < 0.0f) pct = 0.0f; + uint16_t pct_clr = CLR_BAT_FULL; + if (pct <= 20.0f) pct_clr = CLR_BAT_LOW; + else if (pct <= 60.0f) pct_clr = CLR_BAT_MED; + m5canvas.setTextColor(pct_clr); + m5canvas.setCursor(203, 6); + char bbuf[6]; + snprintf(bbuf, sizeof(bbuf), "%d%%", (int)(pct + 0.5f)); + m5canvas.print(bbuf); + } else { + m5canvas.setTextColor(CLR_TEXT_DIM); + m5canvas.setCursor(203, 6); + m5canvas.print("--%"); + } + + m5canvas.drawFastHLine(0, 20, DISP_W, CLR_BORDER); +} + +void cp_draw_waterfall() { + if (!radio_online) { + m5canvas.fillRect(CP_WF_X, CP_WF_Y, CP_WF_W, CP_WF_H, SSD1306_BLACK); + m5canvas.drawFastVLine(CP_WF_W, CP_WF_Y, CP_WF_H, CLR_BORDER); + return; + } + + int rssi_val = current_rssi; + if (rssi_val < WF_RSSI_MIN) rssi_val = WF_RSSI_MIN; + if (rssi_val > WF_RSSI_MAX) rssi_val = WF_RSSI_MAX; + int rssi_norm = ((rssi_val - WF_RSSI_MIN) * 8) / WF_RSSI_SPAN; + if (rssi_norm > 7) rssi_norm = 7; + if (rssi_norm < 0) rssi_norm = 0; + + if (display_tx) { + for (uint8_t i = 0; i < WF_TX_SIZE; i++) { + cp_waterfall[cp_waterfall_head] = -1; + cp_waterfall_head = (cp_waterfall_head + 1) % CP_WF_SIZE; + } + display_tx = false; + } else { + cp_waterfall[cp_waterfall_head] = interference_detected ? 8 : rssi_norm; + cp_waterfall_head = (cp_waterfall_head + 1) % CP_WF_SIZE; + } + + m5canvas.fillRect(CP_WF_X, CP_WF_Y, CP_WF_W, CP_WF_H, SSD1306_BLACK); + for (int i = 0; i < CP_WF_SIZE; i++) { + int wi = (cp_waterfall_head + i) % CP_WF_SIZE; + int ws = cp_waterfall[wi]; + int ypos = CP_WF_Y + i; + if (ws >= 0) { + uint16_t colour = wf_palette[ws]; + int bar_w = (ws * CP_WF_BAR_W) / 8; + if (bar_w > 0) { + m5canvas.drawFastHLine(CP_WF_X + 1, ypos, bar_w, colour); + } + } else if (ws == -1) { + for (int tx = 0; tx < CP_WF_BAR_W; tx += 2) { + m5canvas.drawPixel(CP_WF_X + 1 + tx, ypos, SSD1306_WHITE); + } + } + } + m5canvas.drawFastVLine(CP_WF_W, CP_WF_Y, CP_WF_H, CLR_BORDER); +} + +void cp_draw_external_framebuffer() { + int scale = 1; + int ox = CP_CONTENT_X + (CP_CONTENT_W - 64 * scale) / 2; + int oy = CP_CONTENT_Y + (CP_CONTENT_H - 64 * scale) / 2; + m5canvas.fillRect(CP_CONTENT_X, CP_CONTENT_Y, CP_CONTENT_W, CP_CONTENT_H, SSD1306_BLACK); + m5canvas.drawRect(ox - 2, oy - 2, 64 * scale + 4, 64 * scale + 4, CLR_BORDER); + for (int row = 0; row < 64; row++) { + for (int col = 0; col < 64; col++) { + int index = row * 8 + (col / 8); + uint8_t bitmask = 1 << (7 - (col % 8)); + if (fb[index] & bitmask) { + m5canvas.fillRect(ox + col * scale, oy + row * scale, scale, scale, SSD1306_WHITE); + } + } + } +} + +void cp_draw_diagnostics() { + int x = CP_CONTENT_X + 4; + int y = CP_CONTENT_Y + 2; + + m5canvas.setFont(nullptr); + m5canvas.setTextSize(2); + m5canvas.setTextColor(CLR_TEXT_ACCENT); + m5canvas.setCursor(x, y); + uint32_t freq_hz = (radio_online && LoRa != nullptr) ? LoRa->getFrequency() : lora_freq; + char fbuf[16]; + snprintf(fbuf, sizeof(fbuf), "%.3f MHz", (float)freq_hz / 1000000.0f); + m5canvas.print(fbuf); + y += 22; + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(x, y); + uint32_t bw_hz = lora_bw; + if (bw_hz == 0 && radio_online && LoRa != nullptr) bw_hz = LoRa->getSignalBandwidth(); + char bwstr[10]; + if (bw_hz >= 1000) snprintf(bwstr, sizeof(bwstr), "%luK", (unsigned long)(bw_hz / 1000)); + else snprintf(bwstr, sizeof(bwstr), "%lu", (unsigned long)bw_hz); + char modbuf[32]; + snprintf(modbuf, sizeof(modbuf), "SF%d %s CR4/%d", lora_sf, bwstr, lora_cr); + m5canvas.print(modbuf); + y += 18; + + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(x, y); + char brbuf[34]; + int txp = (radio_online && LoRa != nullptr) ? (int)LoRa->getTxPower() : (int)lora_txp; + snprintf(brbuf, sizeof(brbuf), "%.1fKbps %+ddBm", (float)lora_bitrate / 1000.0f, txp); + m5canvas.print(brbuf); + m5canvas.setFont(nullptr); + y += 16; + + m5canvas.drawFastHLine(x, y, CP_CONTENT_W - 8, CLR_BORDER); + y += 4; + + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(x, y); + m5canvas.print("Airtime"); + m5canvas.setCursor(x + 110, y); + m5canvas.print("Channel Load"); + y += 11; + + float at_st = airtime * 100.0f; + float at_lt = longterm_airtime * 100.0f; + float cl_st = total_channel_util * 100.0f; + float cl_lt = longterm_channel_util * 100.0f; + char abuf[16]; + m5canvas.setCursor(x + 2, y); + m5canvas.setTextColor(cp_load_color(at_st)); + snprintf(abuf, sizeof(abuf), "ST: %.1f%%", at_st); + m5canvas.print(abuf); + m5canvas.setCursor(x + 112, y); + m5canvas.setTextColor(cp_load_color(cl_st)); + snprintf(abuf, sizeof(abuf), "ST: %.1f%%", cl_st); + m5canvas.print(abuf); + y += 10; + + m5canvas.setCursor(x + 2, y); + m5canvas.setTextColor(cp_load_color(at_lt)); + snprintf(abuf, sizeof(abuf), "LT: %.1f%%", at_lt); + m5canvas.print(abuf); + m5canvas.setCursor(x + 112, y); + m5canvas.setTextColor(cp_load_color(cl_lt)); + snprintf(abuf, sizeof(abuf), "LT: %.1f%%", cl_lt); + m5canvas.print(abuf); + y += 14; + + m5canvas.drawFastHLine(x, y, CP_CONTENT_W - 8, CLR_BORDER); + y += 4; + + signed char t_snr = (signed char)last_snr_raw; + float snr = ((int)t_snr) * 0.25f; + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(x, y); + char sigbuf[40]; + snprintf(sigbuf, sizeof(sigbuf), "RSSI:%d SNR:%.1f", last_rssi, snr); + m5canvas.print(sigbuf); + y += 12; + + float rssi_pct = ((float)(last_rssi - S_RSSI_MIN) / S_RSSI_SPAN) * 100.0f; + cp_draw_gradient_bar(x, y, CP_CONTENT_W - 8, 12, rssi_pct, false); + y += 15; + + int sf = lora_sf == 0 ? 7 : lora_sf; + float snr_min = Q_SNR_MIN_BASE - (int)sf * Q_SNR_STEP; + float snr_span = Q_SNR_MAX - snr_min; + float quality = snr_span > 0.0f ? ((snr - snr_min) / snr_span) * 100.0f : 0.0f; + cp_draw_gradient_bar(x, y, CP_CONTENT_W - 8, 12, quality, false); +} + +void cp_draw_idle_content() { + int x = CP_CONTENT_X + 4; + int y = CP_CONTENT_Y + 8; + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(x, y); + if (bt_devname[0] != 0) m5canvas.print(bt_devname); + else m5canvas.print("RNode"); + y += 24; + + m5canvas.setFont(nullptr); + m5canvas.setTextSize(3); + m5canvas.setTextColor(CLR_TEXT_ACCENT); + m5canvas.setCursor(x, y); + char idbuf[8]; + snprintf(idbuf, sizeof(idbuf), "%02X%02X", (uint8_t)bt_dh[14], (uint8_t)bt_dh[15]); + m5canvas.print(idbuf); + y += 34; + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setCursor(x, y); + + if (radio_online) { + m5canvas.setTextColor(CLR_LORA_ON); + m5canvas.print("Radio Online"); + } else if (disp_page == 0) { + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + m5canvas.setTextColor(CLR_LORA_ON); + m5canvas.print("Provisioned"); + #else + if (device_signatures_ok()) { + m5canvas.setTextColor(CLR_LORA_ON); + m5canvas.print("Signatures OK"); + } else { + m5canvas.setTextColor(CLR_TEXT_WARN); + m5canvas.print("Not Provisioned"); + } + #endif + } else if (disp_page == 1) { + if (!console_active) { + m5canvas.setTextColor(CLR_LORA_ON); + m5canvas.print("Hardware OK"); + } else { + m5canvas.setTextColor(CLR_TEXT_ACCENT); + m5canvas.print("Console Active"); + } + } else { + m5canvas.setTextColor(CLR_TEXT_DIM); + char vbuf[18]; + snprintf(vbuf, sizeof(vbuf), "Firmware v%d.%02d", MAJ_VERS, MIN_VERS); + m5canvas.print(vbuf); + } + m5canvas.setFont(nullptr); +} + +void cp_draw_boot_screen() { + m5canvas.fillSprite(SSD1306_BLACK); + + m5canvas.setFont(nullptr); + m5canvas.setTextSize(3); + m5canvas.setTextColor(CLR_TEXT_ACCENT); + m5canvas.setCursor(72, 30); + m5canvas.print("RNode"); + + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(78, 65); + m5canvas.print("Cardputer Adv"); + + int bar_w = 160; + int bar_x = (DISP_W - bar_w) / 2; + int bar_y = 95; + m5canvas.drawRect(bar_x, bar_y, bar_w, 8, CLR_BORDER); + int pulse = (millis() / 15) % bar_w; + int pw = 40; + int p_start = pulse - pw; + if (p_start < 0) p_start = 0; + int p_end = pulse; + if (p_end > bar_w - 2) p_end = bar_w - 2; + if (p_end > p_start) { + m5canvas.fillRect(bar_x + 1 + p_start, bar_y + 1, p_end - p_start, 6, CLR_TEXT_ACCENT); + } +} + +void cp_draw_pairing_screen() { + int x = CP_CONTENT_X + 4; + int y = CP_CONTENT_Y + 8; + + m5canvas.setTextSize(2); + m5canvas.setTextColor(CLR_TEXT_WARN); + m5canvas.setCursor(x, y); + m5canvas.print("BT Pairing"); + y += 28; + + if (bt_ssp_pin != 0) { + char pin_str[7]; + snprintf(pin_str, sizeof(pin_str), "%06d", (int)bt_ssp_pin); + + int dx = x + 4; + for (int i = 0; i < 6; i++) { + m5canvas.fillRoundRect(dx, y, 24, 36, 4, CLR_BG_PANEL); + m5canvas.drawRoundRect(dx, y, 24, 36, 4, CLR_TEXT_ACCENT); + m5canvas.setTextSize(3); + m5canvas.setTextColor(CLR_TEXT_ACCENT); + m5canvas.setCursor(dx + 5, y + 8); + m5canvas.print(pin_str[i]); + dx += 30; + } + } +} + +bool cp_pairing_tip_active() { + return (int32_t)(cp_pairing_tip_until - millis()) > 0; +} + +void cp_draw_pairing_tip_overlay() { + if (!cp_pairing_tip_active() || bt_state == BT_STATE_PAIRING) return; + + int w = 196; + int h = 54; + int x = (DISP_W - w) / 2; + int y = DISP_H - h - 10; + + m5canvas.fillRoundRect(x + 2, y + 2, w, h, 5, SSD1306_BLACK); + m5canvas.fillRoundRect(x, y, w, h, 5, CLR_BG_PANEL); + m5canvas.drawRoundRect(x, y, w, h, 5, CLR_TEXT_ACCENT); + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(x + 12, y + 8); + m5canvas.print("Press and hold `p`"); + m5canvas.setCursor(x + 12, y + 24); + m5canvas.print("or OK to enter"); + m5canvas.setCursor(x + 12, y + 40); + m5canvas.print("pairing mode."); + m5canvas.setFont(nullptr); +} + +bool cp_ble_notice_active() { + return (int32_t)(cp_ble_notice_until - millis()) > 0; +} + +void cp_draw_ble_notice_overlay() { + if (!cp_ble_notice_active()) return; + + int w = 132; + int h = 34; + int x = (DISP_W - w) / 2; + int y = DISP_H - h - 14; + + m5canvas.fillRoundRect(x + 2, y + 2, w, h, 5, SSD1306_BLACK); + m5canvas.fillRoundRect(x, y, w, h, 5, CLR_BG_PANEL); + m5canvas.drawRoundRect(x, y, w, h, 5, cp_ble_notice_enabled ? CLR_BT_ON : CLR_TEXT_DIM); + + m5canvas.setFont(&fonts::Font2); + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_PRIMARY); + m5canvas.setCursor(x + 18, y + 10); + m5canvas.print(cp_ble_notice_enabled ? "BLE Enabled" : "BLE Disabled"); + m5canvas.setFont(nullptr); +} + +void cp_draw_fw_update_screen() { + m5canvas.fillSprite(SSD1306_BLACK); + + m5canvas.setTextSize(2); + m5canvas.setTextColor(CLR_TEXT_WARN); + m5canvas.setCursor(20, 30); + m5canvas.print("Firmware Update"); + + int bar_w = 200; + int bar_x = 20; + int bar_y = 65; + m5canvas.drawRect(bar_x, bar_y, bar_w, 10, CLR_BORDER); + int anim = (millis() / 10) % bar_w; + int aw = 60; + int a_end = anim + aw; + if (a_end > bar_w - 2) a_end = bar_w - 2; + if (anim < bar_w - 2 && a_end > anim) { + m5canvas.fillRect(bar_x + 1 + anim, bar_y + 1, a_end - anim, 8, CLR_TEXT_WARN); + } + + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_ERROR); + m5canvas.setCursor(50, 95); + m5canvas.print("Do not power off"); +} + +void cp_draw_error_screen() { + int x = CP_CONTENT_X + 4; + int y = CP_CONTENT_Y + 10; + + m5canvas.setTextSize(2); + m5canvas.setTextColor(CLR_TEXT_ERROR); + m5canvas.setCursor(x, y); + + if (!device_firmware_ok()) { + m5canvas.print("FW Corrupt"); + y += 24; + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(x, y); + m5canvas.print("Reflash firmware to fix"); + } else if (!modem_installed) { + m5canvas.print("No Radio"); + y += 24; + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(x, y); + m5canvas.print("Check radio module"); + } else { + m5canvas.print("Config Error"); + y += 24; + m5canvas.setTextSize(1); + m5canvas.setTextColor(CLR_TEXT_SECONDARY); + m5canvas.setCursor(x, y); + m5canvas.print("Run rnodeconf"); + } +} + +void cp_render_frame() { + if (!device_init_done) { + cp_draw_boot_screen(); + return; + } + + if (firmware_update_mode) { + cp_draw_fw_update_screen(); + return; + } + + if (millis() - last_page_flip >= page_interval) { + disp_page = (++disp_page % pages); + last_page_flip = millis(); + if (!community_fw && disp_page == 0) disp_page = 1; + } + + m5canvas.fillRect(CP_CONTENT_X, CP_CONTENT_Y, CP_CONTENT_W, CP_CONTENT_H, SSD1306_BLACK); + cp_draw_statusbar(); + cp_draw_waterfall(); + + if (disp_ext_fb && bt_ssp_pin == 0) { + cp_draw_external_framebuffer(); + } else if (!hw_ready || radio_error || !device_firmware_ok()) { + cp_draw_error_screen(); + } else if (bt_state == BT_STATE_PAIRING && bt_ssp_pin != 0) { + cp_draw_pairing_screen(); + } else if (radio_online && display_diagnostics) { + cp_draw_diagnostics(); + } else { + cp_draw_idle_content(); + } + + cp_draw_pairing_tip_overlay(); + cp_draw_ble_notice_overlay(); +} +#endif + +bool epd_blanked = false; +#if BOARD_MODEL == BOARD_TECHO + void epd_blank(bool full_update = true) { + display.setFullWindow(); + display.fillScreen(SSD1306_WHITE); + display.display(full_update); + } + + void epd_black(bool full_update = true) { + display.setFullWindow(); + display.fillScreen(SSD1306_BLACK); + display.display(full_update); + } +#endif + +void update_display(bool blank = false) { + display_updating = true; + if (blank == true) { + last_disp_update = millis()-disp_update_interval-1; + } else { + if (display_blanking_enabled && millis()-last_unblank_event >= display_blanking_timeout) { + blank = true; + display_blanked = true; + if (display_intensity != 0) { + display_unblank_intensity = display_intensity; + } + display_intensity = 0; + } else { + display_blanked = false; + if (display_unblank_intensity != 0x00) { + display_intensity = display_unblank_intensity; + display_unblank_intensity = 0x00; + } + } + } + + if (blank) { + if (display_blank_frame_drawn && display_contrast == display_intensity) { + display_updating = false; + return; + } + if (millis()-last_disp_update >= disp_update_interval) { + if (display_contrast != display_intensity) { + display_contrast = display_intensity; + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + set_contrast(&m5canvas, display_contrast); + #else + set_contrast(&display, display_contrast); + #endif + } + + #if BOARD_MODEL == BOARD_TECHO + if (!epd_blanked) { + epd_blank(); + epd_blanked = true; + } + #endif + + #if BOARD_MODEL == BOARD_HELTEC_T114 + display.clear(); + display.display(); + digitalWrite(PIN_T114_TFT_BLGT, HIGH); + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + m5canvas.fillSprite(SSD1306_BLACK); + m5canvas.pushSprite(0, 0); + #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO + display.clearDisplay(); + display.display(); + #else + // TODO: Clear screen + #endif + + display_blank_frame_drawn = true; + last_disp_update = millis(); + } + + } else { + display_blank_frame_drawn = false; + if (millis()-last_disp_update >= disp_update_interval) { + uint32_t current = millis(); + if (display_contrast != display_intensity) { + display_contrast = display_intensity; + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + set_contrast(&m5canvas, display_contrast); + #else + set_contrast(&display, display_contrast); + #endif + } + + #if BOARD_MODEL == BOARD_HELTEC_T114 + display.clear(); + digitalWrite(PIN_T114_TFT_BLGT, LOW); + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + m5canvas.fillSprite(SSD1306_BLACK); + #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO + display.clearDisplay(); + #endif + + if (recondition_display) { + disp_target_fps = 30; + disp_update_interval = 1000/disp_target_fps; + display_recondition(); + } else { + #if BOARD_MODEL == BOARD_TECHO + display.setFullWindow(); + display.fillScreen(SSD1306_WHITE); + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + cp_render_frame(); + #else + update_stat_area(); + update_disp_area(); + #endif + } + + #if BOARD_MODEL == BOARD_TECHO + if (current-last_epd_refresh >= epd_update_interval) { + if (current-last_epd_full_refresh >= REFRESH_PERIOD) { display.display(false); last_epd_full_refresh = millis(); } + else { display.display(true); } + last_epd_refresh = millis(); + epd_blanked = false; + } + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + m5canvas.pushSprite(0, 0); + #elif BOARD_MODEL != BOARD_TDECK + display.display(); + #endif + + last_disp_update = millis(); + } + } + display_updating = false; +} + +void display_unblank() { + last_unblank_event = millis(); + display_blank_frame_drawn = false; +} + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV +void cardputer_show_pairing_tip() { + cp_pairing_tip_until = millis() + 3500; + display_unblank(); + last_disp_update = 0; +} + +void cardputer_show_ble_notice(bool enabled) { + cp_ble_notice_enabled = enabled; + cp_ble_notice_until = millis() + 2200; + display_unblank(); + last_disp_update = 0; +} +#endif + +void ext_fb_enable() { + disp_ext_fb = true; +} + +void ext_fb_disable() { + disp_ext_fb = false; +} diff --git a/vendor/rnode_firmware/Documentation/README.md b/vendor/rnode_firmware/Documentation/README.md new file mode 100644 index 0000000..debba1b --- /dev/null +++ b/vendor/rnode_firmware/Documentation/README.md @@ -0,0 +1 @@ +# RNode Documentation diff --git a/vendor/rnode_firmware/Documentation/RNode_v1_Manual.pdf b/vendor/rnode_firmware/Documentation/RNode_v1_Manual.pdf new file mode 100644 index 0000000..26101df Binary files /dev/null and b/vendor/rnode_firmware/Documentation/RNode_v1_Manual.pdf differ diff --git a/vendor/rnode_firmware/Documentation/images/126dcfe92fb7.webp b/vendor/rnode_firmware/Documentation/images/126dcfe92fb7.webp new file mode 100644 index 0000000..31cf5eb Binary files /dev/null and b/vendor/rnode_firmware/Documentation/images/126dcfe92fb7.webp differ diff --git a/vendor/rnode_firmware/Documentation/images/devboards_1.webp b/vendor/rnode_firmware/Documentation/images/devboards_1.webp new file mode 100644 index 0000000..0ba2311 Binary files /dev/null and b/vendor/rnode_firmware/Documentation/images/devboards_1.webp differ diff --git a/vendor/rnode_firmware/Documentation/images/rnv21_bgp.webp b/vendor/rnode_firmware/Documentation/images/rnv21_bgp.webp new file mode 100644 index 0000000..fe4196e Binary files /dev/null and b/vendor/rnode_firmware/Documentation/images/rnv21_bgp.webp differ diff --git a/vendor/rnode_firmware/Documentation/rnfw_1.jpg b/vendor/rnode_firmware/Documentation/rnfw_1.jpg new file mode 100644 index 0000000..27c9c92 Binary files /dev/null and b/vendor/rnode_firmware/Documentation/rnfw_1.jpg differ diff --git a/vendor/rnode_firmware/Fonts/Org_01.h b/vendor/rnode_firmware/Fonts/Org_01.h new file mode 100644 index 0000000..9b80258 --- /dev/null +++ b/vendor/rnode_firmware/Fonts/Org_01.h @@ -0,0 +1,131 @@ +#pragma once +#include + +// Org_v01 by Orgdot (www.orgdot.com/aliasfonts). A tiny, +// stylized font with all characters within a 6 pixel height. + +const uint8_t Org_01Bitmaps[] PROGMEM = { + 0xE8, 0xA0, 0x57, 0xD5, 0xF5, 0x00, 0xFD, 0x3E, 0x5F, 0x80, 0x88, 0x88, + 0x88, 0x80, 0xF4, 0xBF, 0x2E, 0x80, 0x80, 0x6A, 0x40, 0x95, 0x80, 0xAA, + 0x80, 0x5D, 0x00, 0xC0, 0xF0, 0x80, 0x08, 0x88, 0x88, 0x00, 0xFC, 0x63, + 0x1F, 0x80, 0xF8, 0xF8, 0x7F, 0x0F, 0x80, 0xF8, 0x7E, 0x1F, 0x80, 0x8C, + 0x7E, 0x10, 0x80, 0xFC, 0x3E, 0x1F, 0x80, 0xFC, 0x3F, 0x1F, 0x80, 0xF8, + 0x42, 0x10, 0x80, 0xFC, 0x7F, 0x1F, 0x80, 0xFC, 0x7E, 0x1F, 0x80, 0x90, + 0xB0, 0x2A, 0x22, 0xF0, 0xF0, 0x88, 0xA8, 0xF8, 0x4E, 0x02, 0x00, 0xFD, + 0x6F, 0x0F, 0x80, 0xFC, 0x7F, 0x18, 0x80, 0xF4, 0x7D, 0x1F, 0x00, 0xFC, + 0x21, 0x0F, 0x80, 0xF4, 0x63, 0x1F, 0x00, 0xFC, 0x3F, 0x0F, 0x80, 0xFC, + 0x3F, 0x08, 0x00, 0xFC, 0x2F, 0x1F, 0x80, 0x8C, 0x7F, 0x18, 0x80, 0xF9, + 0x08, 0x4F, 0x80, 0x78, 0x85, 0x2F, 0x80, 0x8D, 0xB1, 0x68, 0x80, 0x84, + 0x21, 0x0F, 0x80, 0xFD, 0x6B, 0x5A, 0x80, 0xFC, 0x63, 0x18, 0x80, 0xFC, + 0x63, 0x1F, 0x80, 0xFC, 0x7F, 0x08, 0x00, 0xFC, 0x63, 0x3F, 0x80, 0xFC, + 0x7F, 0x29, 0x00, 0xFC, 0x3E, 0x1F, 0x80, 0xF9, 0x08, 0x42, 0x00, 0x8C, + 0x63, 0x1F, 0x80, 0x8C, 0x62, 0xA2, 0x00, 0xAD, 0x6B, 0x5F, 0x80, 0x8A, + 0x88, 0xA8, 0x80, 0x8C, 0x54, 0x42, 0x00, 0xF8, 0x7F, 0x0F, 0x80, 0xEA, + 0xC0, 0x82, 0x08, 0x20, 0x80, 0xD5, 0xC0, 0x54, 0xF8, 0x80, 0xF1, 0xFF, + 0x8F, 0x99, 0xF0, 0xF8, 0x8F, 0x1F, 0x99, 0xF0, 0xFF, 0x8F, 0x6B, 0xA4, + 0xF9, 0x9F, 0x10, 0x8F, 0x99, 0x90, 0xF0, 0x55, 0xC0, 0x8A, 0xF9, 0x90, + 0xF8, 0xFD, 0x63, 0x10, 0xF9, 0x99, 0xF9, 0x9F, 0xF9, 0x9F, 0x80, 0xF9, + 0x9F, 0x20, 0xF8, 0x88, 0x47, 0x1F, 0x27, 0xC8, 0x42, 0x00, 0x99, 0x9F, + 0x99, 0x97, 0x8C, 0x6B, 0xF0, 0x96, 0x69, 0x99, 0x9F, 0x10, 0x2E, 0x8F, + 0x2B, 0x22, 0xF8, 0x89, 0xA8, 0x0F, 0xE0}; + +const GFXglyph Org_01Glyphs[] PROGMEM = {{0, 0, 0, 6, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 1, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 5, 5, 6, 0, -4}, // 0x24 '$' + {10, 5, 5, 6, 0, -4}, // 0x25 '%' + {14, 5, 5, 6, 0, -4}, // 0x26 '&' + {18, 1, 1, 2, 0, -4}, // 0x27 ''' + {19, 2, 5, 3, 0, -4}, // 0x28 '(' + {21, 2, 5, 3, 0, -4}, // 0x29 ')' + {23, 3, 3, 4, 0, -3}, // 0x2A '*' + {25, 3, 3, 4, 0, -3}, // 0x2B '+' + {27, 1, 2, 2, 0, 0}, // 0x2C ',' + {28, 4, 1, 5, 0, -2}, // 0x2D '-' + {29, 1, 1, 2, 0, 0}, // 0x2E '.' + {30, 5, 5, 6, 0, -4}, // 0x2F '/' + {34, 5, 5, 6, 0, -4}, // 0x30 '0' + {38, 1, 5, 2, 0, -4}, // 0x31 '1' + {39, 5, 5, 6, 0, -4}, // 0x32 '2' + {43, 5, 5, 6, 0, -4}, // 0x33 '3' + {47, 5, 5, 6, 0, -4}, // 0x34 '4' + {51, 5, 5, 6, 0, -4}, // 0x35 '5' + {55, 5, 5, 6, 0, -4}, // 0x36 '6' + {59, 5, 5, 6, 0, -4}, // 0x37 '7' + {63, 5, 5, 6, 0, -4}, // 0x38 '8' + {67, 5, 5, 6, 0, -4}, // 0x39 '9' + {71, 1, 4, 2, 0, -3}, // 0x3A ':' + {72, 1, 4, 2, 0, -3}, // 0x3B ';' + {73, 3, 5, 4, 0, -4}, // 0x3C '<' + {75, 4, 3, 5, 0, -3}, // 0x3D '=' + {77, 3, 5, 4, 0, -4}, // 0x3E '>' + {79, 5, 5, 6, 0, -4}, // 0x3F '?' + {83, 5, 5, 6, 0, -4}, // 0x40 '@' + {87, 5, 5, 6, 0, -4}, // 0x41 'A' + {91, 5, 5, 6, 0, -4}, // 0x42 'B' + {95, 5, 5, 6, 0, -4}, // 0x43 'C' + {99, 5, 5, 6, 0, -4}, // 0x44 'D' + {103, 5, 5, 6, 0, -4}, // 0x45 'E' + {107, 5, 5, 6, 0, -4}, // 0x46 'F' + {111, 5, 5, 6, 0, -4}, // 0x47 'G' + {115, 5, 5, 6, 0, -4}, // 0x48 'H' + {119, 5, 5, 6, 0, -4}, // 0x49 'I' + {123, 5, 5, 6, 0, -4}, // 0x4A 'J' + {127, 5, 5, 6, 0, -4}, // 0x4B 'K' + {131, 5, 5, 6, 0, -4}, // 0x4C 'L' + {135, 5, 5, 6, 0, -4}, // 0x4D 'M' + {139, 5, 5, 6, 0, -4}, // 0x4E 'N' + {143, 5, 5, 6, 0, -4}, // 0x4F 'O' + {147, 5, 5, 6, 0, -4}, // 0x50 'P' + {151, 5, 5, 6, 0, -4}, // 0x51 'Q' + {155, 5, 5, 6, 0, -4}, // 0x52 'R' + {159, 5, 5, 6, 0, -4}, // 0x53 'S' + {163, 5, 5, 6, 0, -4}, // 0x54 'T' + {167, 5, 5, 6, 0, -4}, // 0x55 'U' + {171, 5, 5, 6, 0, -4}, // 0x56 'V' + {175, 5, 5, 6, 0, -4}, // 0x57 'W' + {179, 5, 5, 6, 0, -4}, // 0x58 'X' + {183, 5, 5, 6, 0, -4}, // 0x59 'Y' + {187, 5, 5, 6, 0, -4}, // 0x5A 'Z' + {191, 2, 5, 3, 0, -4}, // 0x5B '[' + {193, 5, 5, 6, 0, -4}, // 0x5C '\' + {197, 2, 5, 3, 0, -4}, // 0x5D ']' + {199, 3, 2, 4, 0, -4}, // 0x5E '^' + {200, 5, 1, 6, 0, 1}, // 0x5F '_' + {201, 1, 1, 2, 0, -4}, // 0x60 '`' + {202, 4, 4, 5, 0, -3}, // 0x61 'a' + {204, 4, 5, 5, 0, -4}, // 0x62 'b' + {207, 4, 4, 5, 0, -3}, // 0x63 'c' + {209, 4, 5, 5, 0, -4}, // 0x64 'd' + {212, 4, 4, 5, 0, -3}, // 0x65 'e' + {214, 3, 5, 4, 0, -4}, // 0x66 'f' + {216, 4, 5, 5, 0, -3}, // 0x67 'g' + {219, 4, 5, 5, 0, -4}, // 0x68 'h' + {222, 1, 4, 2, 0, -3}, // 0x69 'i' + {223, 2, 5, 3, 0, -3}, // 0x6A 'j' + {225, 4, 5, 5, 0, -4}, // 0x6B 'k' + {228, 1, 5, 2, 0, -4}, // 0x6C 'l' + {229, 5, 4, 6, 0, -3}, // 0x6D 'm' + {232, 4, 4, 5, 0, -3}, // 0x6E 'n' + {234, 4, 4, 5, 0, -3}, // 0x6F 'o' + {236, 4, 5, 5, 0, -3}, // 0x70 'p' + {239, 4, 5, 5, 0, -3}, // 0x71 'q' + {242, 4, 4, 5, 0, -3}, // 0x72 'r' + {244, 4, 4, 5, 0, -3}, // 0x73 's' + {246, 5, 5, 6, 0, -4}, // 0x74 't' + {250, 4, 4, 5, 0, -3}, // 0x75 'u' + {252, 4, 4, 5, 0, -3}, // 0x76 'v' + {254, 5, 4, 6, 0, -3}, // 0x77 'w' + {257, 4, 4, 5, 0, -3}, // 0x78 'x' + {259, 4, 5, 5, 0, -3}, // 0x79 'y' + {262, 4, 4, 5, 0, -3}, // 0x7A 'z' + {264, 3, 5, 4, 0, -4}, // 0x7B '{' + {266, 1, 5, 2, 0, -4}, // 0x7C '|' + {267, 3, 5, 4, 0, -4}, // 0x7D '}' + {269, 5, 3, 6, 0, -3}}; // 0x7E '~' + +const GFXfont Org_01 PROGMEM = {(uint8_t *)Org_01Bitmaps, + (GFXglyph *)Org_01Glyphs, 0x20, 0x7E, 7}; + +// Approx. 943 bytes diff --git a/vendor/rnode_firmware/Fonts/PicoPixel.h b/vendor/rnode_firmware/Fonts/PicoPixel.h new file mode 100644 index 0000000..01c79d5 --- /dev/null +++ b/vendor/rnode_firmware/Fonts/PicoPixel.h @@ -0,0 +1,123 @@ +#pragma once +#include + +// Picopixel by Sebastian Weber. A tiny font +// with all characters within a 6 pixel height. + +const uint8_t PicopixelBitmaps[] PROGMEM = { + 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, + 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, + 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, + 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, + 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, + 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, + 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, + 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, + 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, + 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, + 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, + 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, + 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, + 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, + 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; + +const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 2, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 3, 6, 4, 0, -4}, // 0x24 '$' + {9, 3, 5, 4, 0, -4}, // 0x25 '%' + {11, 4, 5, 5, 0, -4}, // 0x26 '&' + {14, 1, 2, 2, 0, -4}, // 0x27 ''' + {15, 2, 5, 3, 0, -4}, // 0x28 '(' + {17, 2, 5, 3, 0, -4}, // 0x29 ')' + {19, 3, 3, 4, 0, -3}, // 0x2A '*' + {21, 3, 3, 4, 0, -3}, // 0x2B '+' + {23, 2, 2, 3, 0, 0}, // 0x2C ',' + {24, 3, 1, 4, 0, -2}, // 0x2D '-' + {25, 1, 1, 2, 0, 0}, // 0x2E '.' + {26, 3, 5, 4, 0, -4}, // 0x2F '/' + {28, 3, 5, 4, 0, -4}, // 0x30 '0' + {30, 2, 5, 3, 0, -4}, // 0x31 '1' + {32, 3, 5, 4, 0, -4}, // 0x32 '2' + {34, 3, 5, 4, 0, -4}, // 0x33 '3' + {36, 3, 5, 4, 0, -4}, // 0x34 '4' + {38, 3, 5, 4, 0, -4}, // 0x35 '5' + {40, 3, 5, 4, 0, -4}, // 0x36 '6' + {42, 3, 5, 4, 0, -4}, // 0x37 '7' + {44, 3, 5, 4, 0, -4}, // 0x38 '8' + {46, 3, 5, 4, 0, -4}, // 0x39 '9' + {48, 1, 3, 2, 0, -3}, // 0x3A ':' + {49, 2, 4, 3, 0, -3}, // 0x3B ';' + {50, 2, 3, 3, 0, -3}, // 0x3C '<' + {51, 3, 3, 4, 0, -3}, // 0x3D '=' + {53, 2, 3, 3, 0, -3}, // 0x3E '>' + {54, 3, 5, 4, 0, -4}, // 0x3F '?' + {56, 3, 5, 4, 0, -4}, // 0x40 '@' + {58, 3, 5, 4, 0, -4}, // 0x41 'A' + {60, 3, 5, 4, 0, -4}, // 0x42 'B' + {62, 3, 5, 4, 0, -4}, // 0x43 'C' + {64, 3, 5, 4, 0, -4}, // 0x44 'D' + {66, 3, 5, 4, 0, -4}, // 0x45 'E' + {68, 3, 5, 4, 0, -4}, // 0x46 'F' + {70, 3, 5, 4, 0, -4}, // 0x47 'G' + {72, 3, 5, 4, 0, -4}, // 0x48 'H' + {74, 1, 5, 2, 0, -4}, // 0x49 'I' + {75, 3, 5, 4, 0, -4}, // 0x4A 'J' + {77, 3, 5, 4, 0, -4}, // 0x4B 'K' + {79, 3, 5, 4, 0, -4}, // 0x4C 'L' + {81, 5, 5, 6, 0, -4}, // 0x4D 'M' + {85, 4, 5, 5, 0, -4}, // 0x4E 'N' + {88, 3, 5, 4, 0, -4}, // 0x4F 'O' + {90, 3, 5, 4, 0, -4}, // 0x50 'P' + {92, 3, 6, 4, 0, -4}, // 0x51 'Q' + {95, 3, 5, 4, 0, -4}, // 0x52 'R' + {97, 3, 5, 4, 0, -4}, // 0x53 'S' + {99, 3, 5, 4, 0, -4}, // 0x54 'T' + {101, 3, 5, 4, 0, -4}, // 0x55 'U' + {103, 3, 5, 4, 0, -4}, // 0x56 'V' + {105, 5, 5, 6, 0, -4}, // 0x57 'W' + {109, 3, 5, 4, 0, -4}, // 0x58 'X' + {111, 3, 5, 4, 0, -4}, // 0x59 'Y' + {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' + {115, 2, 5, 3, 0, -4}, // 0x5B '[' + {117, 3, 5, 4, 0, -4}, // 0x5C '\' + {119, 2, 5, 3, 0, -4}, // 0x5D ']' + {121, 3, 2, 4, 0, -4}, // 0x5E '^' + {122, 4, 1, 4, 0, 1}, // 0x5F '_' + {123, 2, 2, 3, 0, -4}, // 0x60 '`' + {124, 3, 4, 4, 0, -3}, // 0x61 'a' + {126, 3, 5, 4, 0, -4}, // 0x62 'b' + {128, 3, 3, 4, 0, -2}, // 0x63 'c' + {130, 3, 5, 4, 0, -4}, // 0x64 'd' + {132, 3, 4, 4, 0, -3}, // 0x65 'e' + {134, 2, 5, 3, 0, -4}, // 0x66 'f' + {136, 3, 5, 4, 0, -3}, // 0x67 'g' + {138, 3, 5, 4, 0, -4}, // 0x68 'h' + {140, 1, 5, 2, 0, -4}, // 0x69 'i' + {141, 2, 6, 3, 0, -4}, // 0x6A 'j' + {143, 3, 5, 4, 0, -4}, // 0x6B 'k' + {145, 2, 5, 3, 0, -4}, // 0x6C 'l' + {147, 5, 3, 6, 0, -2}, // 0x6D 'm' + {149, 3, 3, 4, 0, -2}, // 0x6E 'n' + {151, 3, 3, 4, 0, -2}, // 0x6F 'o' + {153, 3, 4, 4, 0, -2}, // 0x70 'p' + {155, 3, 4, 4, 0, -2}, // 0x71 'q' + {157, 2, 3, 3, 0, -2}, // 0x72 'r' + {158, 3, 4, 4, 0, -3}, // 0x73 's' + {160, 2, 5, 3, 0, -4}, // 0x74 't' + {162, 3, 3, 4, 0, -2}, // 0x75 'u' + {164, 3, 3, 4, 0, -2}, // 0x76 'v' + {166, 5, 3, 6, 0, -2}, // 0x77 'w' + {168, 3, 3, 4, 0, -2}, // 0x78 'x' + {170, 3, 4, 4, 0, -2}, // 0x79 'y' + {172, 3, 4, 4, 0, -3}, // 0x7A 'z' + {174, 3, 5, 4, 0, -4}, // 0x7B '{' + {176, 1, 6, 2, 0, -4}, // 0x7C '|' + {177, 3, 5, 4, 0, -4}, // 0x7D '}' + {179, 4, 2, 5, 0, -3}}; // 0x7E '~' + +const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps, + (GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7}; + +// Approx. 852 bytes \ No newline at end of file diff --git a/vendor/rnode_firmware/Framing.h b/vendor/rnode_firmware/Framing.h new file mode 100644 index 0000000..634fd22 --- /dev/null +++ b/vendor/rnode_firmware/Framing.h @@ -0,0 +1,124 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef FRAMING_H + #define FRAMING_H + + #define FEND 0xC0 + #define FESC 0xDB + #define TFEND 0xDC + #define TFESC 0xDD + + #define CMD_UNKNOWN 0xFE + #define CMD_DATA 0x00 + #define CMD_FREQUENCY 0x01 + #define CMD_BANDWIDTH 0x02 + #define CMD_TXPOWER 0x03 + #define CMD_SF 0x04 + #define CMD_CR 0x05 + #define CMD_RADIO_STATE 0x06 + #define CMD_RADIO_LOCK 0x07 + #define CMD_DETECT 0x08 + #define CMD_IMPLICIT 0x09 + #define CMD_LEAVE 0x0A + #define CMD_ST_ALOCK 0x0B + #define CMD_LT_ALOCK 0x0C + #define CMD_PROMISC 0x0E + #define CMD_READY 0x0F + + #define CMD_STAT_RX 0x21 + #define CMD_STAT_TX 0x22 + #define CMD_STAT_RSSI 0x23 + #define CMD_STAT_SNR 0x24 + #define CMD_STAT_CHTM 0x25 + #define CMD_STAT_PHYPRM 0x26 + #define CMD_STAT_BAT 0x27 + #define CMD_STAT_CSMA 0x28 + #define CMD_STAT_TEMP 0x29 + #define CMD_BLINK 0x30 + #define CMD_RANDOM 0x40 + + #define CMD_FB_EXT 0x41 + #define CMD_FB_READ 0x42 + #define CMD_FB_WRITE 0x43 + #define CMD_FB_READL 0x44 + #define CMD_DISP_READ 0x66 + #define CMD_DISP_INT 0x45 + #define CMD_DISP_ADDR 0x63 + #define CMD_DISP_BLNK 0x64 + #define CMD_DISP_ROT 0x67 + #define CMD_DISP_RCND 0x68 + #define CMD_NP_INT 0x65 + #define CMD_BT_CTRL 0x46 + #define CMD_BT_UNPAIR 0x70 + #define CMD_BT_PIN 0x62 + #define CMD_DIS_IA 0x69 + #define CMD_WIFI_MODE 0x6A + #define CMD_WIFI_SSID 0x6B + #define CMD_WIFI_PSK 0x6C + #define CMD_WIFI_CHN 0x6E + #define CMD_WIFI_IP 0x84 + #define CMD_WIFI_NM 0x85 + + #define CMD_BOARD 0x47 + #define CMD_PLATFORM 0x48 + #define CMD_MCU 0x49 + #define CMD_FW_VERSION 0x50 + #define CMD_CFG_READ 0x6D + #define CMD_ROM_READ 0x51 + #define CMD_ROM_WRITE 0x52 + #define CMD_CONF_SAVE 0x53 + #define CMD_CONF_DELETE 0x54 + #define CMD_DEV_HASH 0x56 + #define CMD_DEV_SIG 0x57 + #define CMD_FW_HASH 0x58 + #define CMD_HASHES 0x60 + #define CMD_FW_UPD 0x61 + #define CMD_UNLOCK_ROM 0x59 + #define ROM_UNLOCK_BYTE 0xF8 + #define CMD_RESET 0x55 + #define CMD_RESET_BYTE 0xF8 + + #define CMD_LOG 0x80 + #define CMD_TIME 0x81 + #define CMD_MUX_CHAIN 0x82 + #define CMD_MUX_DSCVR 0x83 + + #define DETECT_REQ 0x73 + #define DETECT_RESP 0x46 + + #define RADIO_STATE_OFF 0x00 + #define RADIO_STATE_ON 0x01 + + #define NIBBLE_SEQ 0xF0 + #define NIBBLE_FLAGS 0x0F + #define FLAG_SPLIT 0x01 + #define SEQ_UNSET 0xFF + + #define CMD_ERROR 0x90 + #define ERROR_INITRADIO 0x01 + #define ERROR_TXFAILED 0x02 + #define ERROR_EEPROM_LOCKED 0x03 + #define ERROR_QUEUE_FULL 0x04 + #define ERROR_MEMORY_LOW 0x05 + #define ERROR_MODEM_TIMEOUT 0x06 + + // Serial framing variables + size_t frame_len; + bool IN_FRAME = false; + bool ESCAPE = false; + uint8_t command = CMD_UNKNOWN; + +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/Graphics.h b/vendor/rnode_firmware/Graphics.h new file mode 100644 index 0000000..6eb00e0 --- /dev/null +++ b/vendor/rnode_firmware/Graphics.h @@ -0,0 +1,445 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const unsigned char bm_cable [] PROGMEM = { + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x38, 0x07, 0xfc, 0x08, 0x38, 0x10, 0x1c, 0x10, 0x00, 0x08, 0x00, + 0x07, 0xc0, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x07, 0xc0, 0x08, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x80, 0x04, 0x43, 0x08, 0x46, + 0xf1, 0x8f, 0x02, 0x16, 0x02, 0x23, 0x01, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_rf [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xc4, + 0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x4e, 0xc4, + 0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x4e, + 0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x71, 0x4e, + 0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00 +}; + +const unsigned char bm_wifi [] PROGMEM = { + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x04, 0x60, + 0x0a, 0x50, 0x0e, 0x60, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x04, 0x60, + 0x0a, 0x50, 0x0e, 0x60, 0x0a, 0x40, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x74, + 0x54, 0x40, 0x55, 0x64, 0x29, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x14, 0x28, 0x01, 0x80, 0x00, 0x00, 0x45, 0x74, + 0x54, 0x40, 0x55, 0x64, 0x29, 0x44, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00 +}; + +const unsigned char bm_bt [] PROGMEM = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x01, 0x40, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40, + 0x01, 0x80, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40, + 0x29, 0x94, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x11, 0x48, + 0x29, 0x94, 0x11, 0x48, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00 +}; + +const unsigned char bm_boot [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff, 0xff, + 0xff, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff, 0xff, + 0xff, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0c, 0x38, 0xe1, 0xc3, 0x33, 0x38, 0x7f, 0xfe, 0x7e, 0x72, 0x64, 0xe7, 0x31, 0x33, 0xff, + 0xff, 0x1e, 0x70, 0x61, 0xe7, 0x30, 0x32, 0x7f, 0xff, 0xce, 0x72, 0x61, 0xe7, 0x32, 0x32, 0x7f, + 0xfe, 0x1e, 0x72, 0x64, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_fw_update [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x98, 0x70, 0xf1, 0xc3, 0x33, 0x38, 0x7f, 0xfc, 0x99, 0x32, 0x64, 0xe7, 0x31, 0x33, 0xff, + 0xfc, 0x98, 0x72, 0x60, 0xe7, 0x30, 0x32, 0x7f, 0xfc, 0x99, 0xf2, 0x64, 0xe7, 0x32, 0x32, 0x7f, + 0xfe, 0x39, 0xf0, 0xe4, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x66, 0x1c, 0xe6, 0x73, 0x8e, 0x1c, 0x3f, 0xf9, 0xe6, 0x4c, 0x46, 0x53, 0x26, 0x4c, 0xff, + 0xf8, 0x66, 0x1c, 0x06, 0x53, 0x06, 0x1c, 0x3f, 0xf9, 0xe6, 0x1c, 0xa6, 0x03, 0x26, 0x1c, 0xff, + 0xf9, 0xe6, 0x4c, 0xe7, 0x27, 0x26, 0x4c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_console_active [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x8e, 0x67, 0x0e, 0x39, 0xe1, 0xff, + 0xff, 0x93, 0x26, 0x26, 0x7c, 0x99, 0xe7, 0xff, 0xff, 0x9f, 0x26, 0x07, 0x1c, 0x99, 0xe1, 0xff, + 0xff, 0x93, 0x26, 0x47, 0xcc, 0x99, 0xe7, 0xff, 0xff, 0xc7, 0x8e, 0x66, 0x1e, 0x38, 0x61, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3c, 0x70, 0xcc, 0xcc, 0x3f, 0xff, + 0xff, 0xfc, 0x99, 0x39, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xfc, 0x19, 0xf9, 0xce, 0x1c, 0x3f, 0xff, + 0xff, 0xfc, 0x99, 0x39, 0xce, 0x1c, 0xff, 0xff, 0xff, 0xfc, 0x9c, 0x79, 0xcf, 0x3c, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_updating [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff, + 0xff, 0xff, 0x7f, 0xf5, 0xff, 0x75, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x57, 0xd5, 0x7f, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x57, 0x55, 0x75, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, + 0xff, 0xff, 0x60, 0x00, 0x00, 0x03, 0x7f, 0xff, 0xff, 0xff, 0x30, 0x00, 0x00, 0x07, 0x7f, 0xff, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x1c, 0x7f, 0xff, + 0xff, 0xff, 0x56, 0x00, 0x00, 0x35, 0x7f, 0xff, 0xff, 0xff, 0x57, 0x00, 0x00, 0x74, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0xc0, 0x01, 0xd1, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x60, 0x03, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x70, 0x07, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x5c, 0x1d, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x56, 0x35, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x57, 0xe5, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_version [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x99, 0x86, 0x1e, 0x19, 0xc7, 0x33, 0xff, 0xff, 0x99, 0x9e, 0x4c, 0xf9, 0x93, 0x13, 0xff, + 0xff, 0xc3, 0x86, 0x1e, 0x39, 0x93, 0x03, 0xff, 0xff, 0xc3, 0x9e, 0x1f, 0x99, 0x93, 0x23, 0xff, + 0xff, 0xe7, 0x86, 0x4c, 0x39, 0xc7, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_fw_corrupt [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x30, 0xe7, 0x33, 0x9c, 0x70, 0xe1, 0xff, + 0xcf, 0x32, 0x62, 0x32, 0x99, 0x32, 0x67, 0xff, 0xc3, 0x30, 0xe0, 0x32, 0x98, 0x30, 0xe1, 0xff, + 0xcf, 0x30, 0xe5, 0x30, 0x19, 0x30, 0xe7, 0xff, 0xcf, 0x32, 0x67, 0x39, 0x39, 0x32, 0x61, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xc7, 0x0e, 0x1c, 0x98, 0x70, 0xfc, 0xff, + 0xc9, 0x93, 0x26, 0x4c, 0x99, 0x39, 0xfb, 0x7f, 0xcf, 0x93, 0x0e, 0x1c, 0x98, 0x79, 0xfb, 0x7f, + 0xc9, 0x93, 0x0e, 0x1c, 0x99, 0xf9, 0xf7, 0xbf, 0xe3, 0xc7, 0x26, 0x4e, 0x39, 0xf9, 0xf4, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static unsigned char bm_def[] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff, + 0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff, + 0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00, + 0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00, + 0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00, + 0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, + 0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, + 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x62, 0x24, 0x49, 0x22, 0x4e, 0x44, + 0x00, 0x24, 0x93, 0x66, 0xc9, 0x32, 0x44, 0x28, 0x00, 0x20, 0x92, 0xa5, 0x49, 0x2a, 0x44, 0x10, + 0x00, 0x24, 0x92, 0x24, 0x49, 0x26, 0x44, 0x10, 0x00, 0x18, 0x62, 0x24, 0x46, 0x22, 0x44, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x9c, 0x44, 0x88, 0xc7, 0x1c, 0x00, 0x00, 0x10, 0x92, 0x6c, 0xa9, 0x24, 0x90, + 0x00, 0x00, 0x1c, 0x9c, 0x54, 0xa9, 0xe7, 0x1c, 0x00, 0x00, 0x10, 0x94, 0x44, 0xa9, 0x25, 0x10, + 0x00, 0x00, 0x10, 0x92, 0x44, 0x51, 0x24, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_def_lc [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff, + 0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff, + 0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00, + 0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00, + 0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00, + 0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, + 0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, + 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x39, 0x10, 0x61, 0x88, 0x91, 0x1c, + 0x02, 0x49, 0x21, 0x90, 0x92, 0x4d, 0x9b, 0x20, 0x02, 0x4e, 0x39, 0x50, 0x82, 0x4a, 0x95, 0x18, + 0x02, 0x48, 0x21, 0x30, 0x92, 0x48, 0x91, 0x04, 0x01, 0x88, 0x39, 0x10, 0x61, 0x88, 0x91, 0x38, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xc8, 0x8e, 0x73, 0x91, 0x1c, 0x00, 0x00, 0x02, 0x05, 0x10, 0x22, 0x1b, 0x20, + 0x00, 0x00, 0x01, 0x82, 0x0c, 0x23, 0x95, 0x18, 0x00, 0x00, 0x00, 0x42, 0x02, 0x22, 0x11, 0x04, + 0x00, 0x00, 0x03, 0x82, 0x1c, 0x23, 0x91, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_frame [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc, + 0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x03, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xaa, 0x8a, 0xaa, 0x80 +}; + +const unsigned char bm_console [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff, + 0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xfc, 0x7f, 0x99, 0xe6, 0x61, 0x99, 0xfe, 0x3f, + 0xf8, 0xe7, 0x99, 0x26, 0x67, 0x99, 0xe7, 0x1f, 0xf9, 0xc7, 0x99, 0x26, 0x61, 0x99, 0xe3, 0x9f, + 0xf1, 0x8f, 0x98, 0x06, 0x67, 0x99, 0xf1, 0x8f, 0xf3, 0x9f, 0x9c, 0xce, 0x67, 0x99, 0xf9, 0xcf, + 0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf, 0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf, + 0xf3, 0x9f, 0x9f, 0xe3, 0x83, 0xf9, 0xf9, 0xcf, 0xf1, 0x8f, 0x9f, 0xc9, 0x93, 0xf9, 0xf1, 0x8f, + 0xf9, 0xc7, 0x9f, 0xc1, 0x83, 0xf9, 0xe3, 0x9f, 0xf8, 0xe7, 0x9f, 0xc9, 0x9f, 0xf9, 0xe7, 0x1f, + 0xfc, 0x7f, 0x9f, 0xc9, 0x9f, 0xf9, 0xfe, 0x3f, 0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f, + 0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, + 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x78, 0x1e, 0x07, 0x81, 0xe0, 0x78, 0x1f, + 0xef, 0xbb, 0xee, 0xfb, 0xbe, 0xef, 0xbb, 0xef, 0xe8, 0xda, 0xb6, 0x9d, 0xb3, 0x6d, 0xda, 0x37, + 0xef, 0xda, 0xf6, 0xb5, 0xad, 0x6c, 0xdb, 0xf7, 0xe8, 0x5a, 0x36, 0x95, 0xad, 0x6c, 0xda, 0x97, + 0xef, 0xdb, 0xf6, 0x85, 0xb3, 0x6c, 0xda, 0x97, 0xea, 0x5a, 0x36, 0xb5, 0xb3, 0x6c, 0xdb, 0xf7, + 0xef, 0xda, 0xf6, 0xa5, 0xad, 0x6f, 0xda, 0x57, 0xe8, 0x5a, 0xb6, 0x85, 0xad, 0x6c, 0xda, 0x57, + 0xef, 0xdb, 0xf6, 0xfd, 0xbf, 0x6f, 0xdb, 0xf7, 0xe0, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x42, 0x7c, 0x60, 0xf0, 0x78, 0x3c, 0x7f, 0xfe, 0x4a, 0x7c, 0x64, 0xf2, 0x79, 0x3c, 0x7f, + 0xfe, 0x43, 0xfe, 0x64, 0xf2, 0x79, 0x3e, 0x7f, 0xfe, 0x4e, 0x7e, 0x64, 0x92, 0x49, 0x26, 0x7f, + 0xfe, 0x4e, 0x7e, 0x60, 0x90, 0x48, 0x26, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +const unsigned char bm_checks [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0x0c, 0x99, 0xc7, 0x0f, + 0xe6, 0x00, 0x7f, 0x93, 0x3c, 0x99, 0x93, 0x3f, 0xe6, 0x00, 0x7f, 0x93, 0x0e, 0x39, 0x9f, 0x0f, + 0xff, 0xff, 0xff, 0x93, 0x3e, 0x39, 0x93, 0x3f, 0xff, 0xff, 0xff, 0x87, 0x0f, 0x79, 0xc7, 0x0f, + 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x87, + 0xe6, 0x00, 0x7c, 0x99, 0x33, 0xc9, 0x87, 0x3f, 0xe6, 0x00, 0x7c, 0xf8, 0x30, 0xcf, 0x8f, 0x8f, + 0xff, 0xff, 0xfc, 0x99, 0x33, 0xc9, 0x87, 0xe7, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x0f, + 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x9c, 0x3c, 0x78, 0x70, 0xc3, 0x0f, + 0xe6, 0x0d, 0x3c, 0x99, 0x33, 0xe7, 0xcf, 0x27, 0xe6, 0x04, 0x7c, 0x38, 0x38, 0xf1, 0xc3, 0x27, + 0xff, 0xfe, 0xfc, 0xf9, 0x3e, 0x7c, 0xcf, 0x27, 0xff, 0xff, 0xfc, 0xf9, 0x30, 0xe1, 0xc3, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_hwfail [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0xe3, 0x87, 0x0e, 0x73, 0x8e, 0x1c, 0x3f, + 0xe4, 0xc9, 0x93, 0x26, 0x53, 0x26, 0x4c, 0xff, 0xe0, 0xc1, 0x87, 0x26, 0x53, 0x06, 0x1c, 0x3f, + 0xe4, 0xc9, 0x87, 0x26, 0x03, 0x26, 0x1c, 0xff, 0xe4, 0xc9, 0x93, 0x0f, 0x27, 0x26, 0x4c, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe1, 0xc7, 0x33, 0xc9, 0x87, 0x0f, 0xf9, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x93, 0x3f, 0xf6, 0xff, + 0xe1, 0x83, 0x33, 0xc9, 0x87, 0x0f, 0xf6, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x87, 0x3f, 0xef, 0x7f, + 0xe7, 0x93, 0x30, 0xe3, 0x93, 0x0f, 0xe9, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_conf_missing [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x33, 0x87, 0x0c, 0xcc, 0xe1, 0xff, 0xff, + 0xe2, 0x33, 0x3e, 0x7c, 0xc4, 0xcf, 0xff, 0xff, 0xe0, 0x33, 0x8f, 0x1c, 0xc0, 0xc9, 0xff, 0xff, + 0xe5, 0x33, 0xe7, 0xcc, 0xc8, 0xc9, 0xff, 0xff, 0xe7, 0x33, 0x0e, 0x1c, 0xcc, 0xe3, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf1, 0xe3, 0x99, 0x86, 0x70, 0xff, 0xf6, 0xff, + 0xe4, 0xc9, 0x89, 0x9e, 0x67, 0xff, 0xf6, 0xff, 0xe7, 0xc9, 0x81, 0x86, 0x64, 0xff, 0xef, 0x7f, + 0xe4, 0xc9, 0x91, 0x9e, 0x64, 0xff, 0xe9, 0x7f, 0xf1, 0xe3, 0x99, 0x9e, 0x71, 0xff, 0xd9, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_no_radio [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc7, 0x0e, 0x71, 0xfc, 0xce, 0x38, 0x7f, + 0xc9, 0x93, 0x26, 0x64, 0xfc, 0x4c, 0x9c, 0xff, 0xc3, 0x83, 0x26, 0x64, 0xfc, 0x0c, 0x9c, 0xff, + 0xc3, 0x93, 0x26, 0x64, 0xfc, 0x8c, 0x9c, 0xff, 0xc9, 0x93, 0x0e, 0x71, 0xfc, 0xce, 0x3c, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x8e, 0x4c, 0xcc, 0x3f, 0xff, 0xfc, 0xff, + 0xcf, 0x26, 0x4c, 0x4c, 0x9f, 0xff, 0xfb, 0x7f, 0xc3, 0x26, 0x4c, 0x0c, 0x9f, 0xff, 0xfb, 0x7f, + 0xcf, 0x26, 0x4c, 0x8c, 0x9f, 0xff, 0xf7, 0xbf, 0xcf, 0x8f, 0x1c, 0xcc, 0x3f, 0xff, 0xf4, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_hwok [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf2, 0x71, 0xc3, 0x87, 0x39, 0xc7, 0x0e, 0x1f, 0xf2, 0x64, 0xc9, 0x93, 0x29, 0x93, 0x26, 0x7f, + 0xf0, 0x60, 0xc3, 0x93, 0x29, 0x83, 0x0e, 0x1f, 0xf2, 0x64, 0xc3, 0x93, 0x01, 0x93, 0x0e, 0x7f, + 0xf2, 0x64, 0xc9, 0x87, 0x93, 0x93, 0x26, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x30, 0xff, 0x8e, 0x4f, 0xff, + 0xff, 0xf3, 0x13, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x03, 0x39, 0xff, 0x26, 0x3f, 0xff, + 0xff, 0xf3, 0x23, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x33, 0x39, 0xff, 0x8e, 0x4f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc3, 0x0e, 0x67, 0xf8, 0x70, 0xe3, 0x87, 0x33, 0xe7, 0x27, 0x0f, 0xf9, 0x33, 0xc9, 0x93, 0x87, + 0xe7, 0x0f, 0x9f, 0xf8, 0x70, 0xc1, 0x93, 0xcf, 0xe7, 0x0f, 0x0f, 0xf8, 0x73, 0xc9, 0x93, 0xcf, + 0xe7, 0x26, 0x67, 0xf9, 0x30, 0xc9, 0x87, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_nfr [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff, + 0xff, 0x9f, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0x6f, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff, + 0xff, 0x6f, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xfe, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x9b, 0xe6, 0x71, 0xc3, 0xe1, 0xc7, 0x0f, + 0xfd, 0x9b, 0xe2, 0x64, 0xe7, 0xe7, 0x93, 0x27, 0xfb, 0x9d, 0xe0, 0x64, 0xe7, 0xe1, 0x93, 0x0f, + 0xfb, 0x9d, 0xe4, 0x64, 0xe7, 0xe7, 0x93, 0x0f, 0xf7, 0xfe, 0xe6, 0x71, 0xe7, 0xe7, 0xc7, 0x27, + 0xf7, 0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x9f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xef, 0xff, 0x7f, 0xf8, 0x71, 0xcf, 0x0f, 0xff, 0xf0, 0x00, 0xff, 0xf3, 0xe4, 0xcf, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xe0, 0xcf, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x64, 0xcf, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf0, 0xe4, 0xc3, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_online [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x1c, 0x66, 0x61, 0x8c, 0x24, 0x90, 0x87, + 0xe6, 0x49, 0x22, 0x4f, 0x24, 0xe4, 0x93, 0x93, 0xe6, 0x18, 0x20, 0x63, 0x3c, 0x26, 0x30, 0x87, + 0xe6, 0x19, 0x24, 0x79, 0x24, 0xe6, 0x33, 0x87, 0xe6, 0x49, 0x26, 0x43, 0x8c, 0x27, 0x70, 0x93, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe6, 0x73, 0xe7, 0x33, 0x87, 0xff, + 0xff, 0xe4, 0xe2, 0x73, 0xe7, 0x13, 0x9f, 0xff, 0xff, 0xe4, 0xe0, 0x73, 0xe7, 0x03, 0x87, 0xff, + 0xff, 0xe4, 0xe4, 0x73, 0xe7, 0x23, 0x9f, 0xff, 0xff, 0xf1, 0xe6, 0x70, 0xe7, 0x33, 0x87, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_pairing [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0xe3, 0x98, 0x73, 0x33, 0x87, 0xff, 0xff, 0xf2, 0xc9, 0x99, 0x33, 0x13, 0x3f, 0xff, + 0xff, 0xf0, 0xc1, 0x98, 0x73, 0x03, 0x27, 0xff, 0xff, 0xf3, 0xc9, 0x98, 0x73, 0x23, 0x27, 0xff, + 0xff, 0xf3, 0xc9, 0x99, 0x33, 0x33, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_n_uh [] PROGMEM = { + 0x07, 0x27, 0x27, 0x27, 0x07, 0x8f, 0x8f, 0xcf, 0xcf, 0xcf, 0x07, 0xe7, 0x07, 0x3f, 0x07, 0x07, + 0xe7, 0xc7, 0xe7, 0x07, 0x27, 0x27, 0x07, 0xe7, 0xe7, 0x07, 0x3f, 0x07, 0xe7, 0x07, 0x07, 0x3f, + 0x07, 0x27, 0x07, 0x07, 0xc7, 0xcf, 0x9f, 0x1f, 0x07, 0x27, 0x07, 0x27, 0x07, 0x07, 0x27, 0x07, + 0xe7, 0xe7 +}; + +const unsigned char bm_plug [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x7f, 0x80, 0x55, 0xfc, 0x00, 0xaa, 0xfc, 0x00, 0x00, + 0x7f, 0x80, 0x00, 0x1c, 0x00 +}; + +const unsigned char bm_hg_low [] PROGMEM = { + 0xf8, 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0xf8, 0xf8 +}; + +const unsigned char bm_hg_high [] PROGMEM = { + 0xf8, 0x88, 0xf8, 0x70, 0x20, 0x70, 0xf8, 0xf8, 0xf8 +}; \ No newline at end of file diff --git a/vendor/rnode_firmware/Input.h b/vendor/rnode_firmware/Input.h new file mode 100644 index 0000000..af8bea7 --- /dev/null +++ b/vendor/rnode_firmware/Input.h @@ -0,0 +1,186 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef INPUT_H + #define INPUT_H + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + #include + #endif + + #define PIN_BUTTON pin_btn_usr1 + + #define PRESSED LOW + #define RELEASED HIGH + + #define EVENT_ALL 0x00 + #define EVENT_CLICKS 0x01 + #define EVENT_BUTTON_DOWN 0x11 + #define EVENT_BUTTON_UP 0x12 + #define EVENT_BUTTON_CLICK 0x13 + #define EVENT_BUTTON_DOUBLE_CLICK 0x14 + #define EVENT_BUTTON_TRIPLE_CLICK 0x15 + + int button_events = EVENT_CLICKS; + int button_state = RELEASED; + int debounce_state = button_state; + unsigned long button_debounce_last = 0; + unsigned long button_debounce_delay = 25; + unsigned long button_down_last = 0; + unsigned long button_up_last = 0; + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + #define CARDPUTER_PAIR_HOLD_MS 3000 + bool cardputer_enter_down = false; + bool cardputer_enter_pairing_sent = false; + unsigned long cardputer_enter_down_last = 0; + bool cardputer_p_down = false; + bool cardputer_p_pairing_sent = false; + unsigned long cardputer_p_down_last = 0; + bool cardputer_b_down = false; + bool cardputer_b_toggle_sent = false; + unsigned long cardputer_b_down_last = 0; + #endif + + // Forward declaration + void button_event(uint8_t event, unsigned long duration); + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + void cardputer_ble_toggle_event(); + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV && HAS_DISPLAY + void cardputer_show_pairing_tip(); + #endif + + void input_init() { + pinMode(PIN_BUTTON, INPUT_PULLUP); + } + + void input_get_all_events() { + button_events = EVENT_ALL; + } + + void input_get_click_events() { + button_events = EVENT_CLICKS; + } + + void input_read() { + int button_reading = digitalRead(PIN_BUTTON); + if (button_reading != debounce_state) { + button_debounce_last = millis(); + debounce_state = button_reading; + } + + if ((millis() - button_debounce_last) > button_debounce_delay) { + if (button_reading != button_state) { + // State changed + int previous_state = button_state; + button_state = button_reading; + + if (button_events == EVENT_ALL) { + if (button_state == PRESSED) { + button_event(EVENT_BUTTON_DOWN, 0); + } else if (button_state == RELEASED) { + button_event(EVENT_BUTTON_UP, 0); + } + } else if (button_events == EVENT_CLICKS) { + if (previous_state == PRESSED && button_state == RELEASED) { + button_up_last = millis(); + button_event(EVENT_BUTTON_CLICK, button_up_last-button_down_last); + } else if (previous_state == RELEASED && button_state == PRESSED) { + button_down_last = millis(); + } + } + } + } + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + M5Cardputer.update(); + Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); + bool keyboard_changed = M5Cardputer.Keyboard.isChange(); + bool keyboard_pressed = M5Cardputer.Keyboard.isPressed() > 0; + bool display_was_blanked = display_blanked; + bool p_pressed = false; + bool b_pressed = false; + + for (auto key : status.word) { + if (key == 'p' || key == 'P') { + p_pressed = true; + } else if (key == 'b' || key == 'B') { + b_pressed = true; + } + } + + if (keyboard_pressed) { + display_unblank(); + } + + if (status.enter) { + if (!cardputer_enter_down) { + cardputer_enter_down = true; + cardputer_enter_pairing_sent = false; + cardputer_enter_down_last = millis(); + } else if (!cardputer_enter_pairing_sent && millis() - cardputer_enter_down_last >= CARDPUTER_PAIR_HOLD_MS) { + button_event(EVENT_BUTTON_CLICK, 5500); + cardputer_enter_pairing_sent = true; + } + } else { + cardputer_enter_down = false; + cardputer_enter_pairing_sent = false; + } + + if (p_pressed) { + if (!cardputer_p_down) { + cardputer_p_down = true; + cardputer_p_pairing_sent = false; + cardputer_p_down_last = millis(); + } else if (!cardputer_p_pairing_sent && millis() - cardputer_p_down_last >= CARDPUTER_PAIR_HOLD_MS) { + button_event(EVENT_BUTTON_CLICK, 5500); + cardputer_p_pairing_sent = true; + } + } else { + cardputer_p_down = false; + cardputer_p_pairing_sent = false; + } + + if (b_pressed) { + if (!cardputer_b_down) { + cardputer_b_down = true; + cardputer_b_toggle_sent = false; + cardputer_b_down_last = millis(); + } else if (!cardputer_b_toggle_sent && millis() - cardputer_b_down_last >= CARDPUTER_PAIR_HOLD_MS) { + cardputer_ble_toggle_event(); + cardputer_b_toggle_sent = true; + } + } else { + cardputer_b_down = false; + cardputer_b_toggle_sent = false; + } + + if (keyboard_changed && keyboard_pressed && !display_was_blanked && !status.enter && !p_pressed && !b_pressed) { + cardputer_show_pairing_tip(); + } + #endif + } + + bool button_pressed() { + if (button_state == PRESSED) { + return true; + } else { + return false; + } + } + +#endif diff --git a/vendor/rnode_firmware/LICENSE b/vendor/rnode_firmware/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/vendor/rnode_firmware/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/vendor/rnode_firmware/MD5.cpp b/vendor/rnode_firmware/MD5.cpp new file mode 100644 index 0000000..56daa77 --- /dev/null +++ b/vendor/rnode_firmware/MD5.cpp @@ -0,0 +1,301 @@ +#include "MD5.h" + +MD5::MD5() +{ + //nothing + return; +} + +char* MD5::make_digest(const unsigned char *digest, int len) /* {{{ */ +{ + char * md5str = (char*) malloc(sizeof(char)*(len*2+1)); + static const char hexits[17] = "0123456789abcdef"; + int i; + + for (i = 0; i < len; i++) { + md5str[i * 2] = hexits[digest[i] >> 4]; + md5str[(i * 2) + 1] = hexits[digest[i] & 0x0F]; + } + md5str[len * 2] = '\0'; + return md5str; +} + +/* + * The basic MD5 functions. + * + * E and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + * E() has been used instead of F() because F() is already defined in the Arduino core + */ +#define E(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +# define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +# define GET(n) \ + SET(n) +#else +# define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +# define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There are no alignment requirements. + */ +const void *MD5::body(void *ctxBuf, const void *data, size_t size) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (unsigned char*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 + * E() has been used instead of F() because F() is already defined in the Arduino core + */ + STEP(E, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(E, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(E, c, d, a, b, SET(2), 0x242070db, 17) + STEP(E, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(E, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(E, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(E, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(E, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(E, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(E, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(E, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(E, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(E, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(E, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(E, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(E, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5::MD5Init(void *ctxBuf) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; + + memset(ctx->block, 0, sizeof(ctx->block)); + memset(ctx->buffer, 0, sizeof(ctx->buffer)); +} + +void MD5::MD5Update(void *ctxBuf, const void *data, size_t size) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + MD5_u32plus saved_lo; + MD5_u32plus used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { + ctx->hi++; + } + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = (unsigned char *)data + free; + size -= free; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(size_t)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +void MD5::MD5Final(unsigned char *result, void *ctxBuf) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + MD5_u32plus used, free; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + memset(&ctx->buffer[used], 0, free); + body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + memset(ctx, 0, sizeof(*ctx)); +} +unsigned char* MD5::make_hash(char *arg) +{ + MD5_CTX context; + unsigned char * hash = (unsigned char *) malloc(16); + MD5Init(&context); + MD5Update(&context, arg, strlen(arg)); + MD5Final(hash, &context); + return hash; +} +unsigned char* MD5::make_hash(char *arg,size_t size) +{ + MD5_CTX context; + unsigned char * hash = (unsigned char *) malloc(16); + MD5Init(&context); + MD5Update(&context, arg, size); + MD5Final(hash, &context); + return hash; +} \ No newline at end of file diff --git a/vendor/rnode_firmware/MD5.h b/vendor/rnode_firmware/MD5.h new file mode 100644 index 0000000..3ec8d81 --- /dev/null +++ b/vendor/rnode_firmware/MD5.h @@ -0,0 +1,52 @@ +#ifndef MD5_h +#define MD5_h + +#include "Arduino.h" + +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, + * Inc. MD5 Message-Digest Algorithm (RFC 1321). + * + * Written by Solar Designer in 2001, and placed + * in the public domain. There's absolutely no warranty. + * + * This differs from Colin Plumb's older public domain implementation in + * that no 32-bit integer data type is required, there's no compile-time + * endianness configuration, and the function prototypes match OpenSSL's. + * The primary goals are portability and ease of use. + * + * This implementation is meant to be fast, but not as fast as possible. + * Some known optimizations are not included to reduce source code size + * and avoid compile-time configuration. + */ + +/* + * Updated by Scott MacVicar for arduino + * + */ + +#include + +typedef unsigned long MD5_u32plus; + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +class MD5 +{ +public: + MD5(); + static unsigned char* make_hash(char *arg); + static unsigned char* make_hash(char *arg,size_t size); + static char* make_digest(const unsigned char *digest, int len); + static const void *body(void *ctxBuf, const void *data, size_t size); + static void MD5Init(void *ctxBuf); + static void MD5Final(unsigned char *result, void *ctxBuf); + static void MD5Update(void *ctxBuf, const void *data, size_t size); +}; + +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/MIRROR.md b/vendor/rnode_firmware/MIRROR.md new file mode 100644 index 0000000..ef2fdd4 --- /dev/null +++ b/vendor/rnode_firmware/MIRROR.md @@ -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. + +--- + +असतो मा सद्गमय +तमसो मा ज्योतिर्गमय +मृत्योर्मा अमृतं गमय diff --git a/vendor/rnode_firmware/Makefile b/vendor/rnode_firmware/Makefile new file mode 100644 index 0000000..4447f6c --- /dev/null +++ b/vendor/rnode_firmware/Makefile @@ -0,0 +1,587 @@ +# Copyright (C) 2024, Mark Qvist + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Version 2.0.17 of the Arduino ESP core is based on ESP-IDF v4.4.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 + +CARDPUTER_ADV_FQBN = esp32:esp32:esp32s3:CDCOnBoot=cdc,USBMode=hwcdc,FlashSize=8M,FlashMode=dio,PartitionScheme=default_8MB +CARDPUTER_ADV_SKETCH = build/cardputer_adv_sketch/RNode_Firmware +CARDPUTER_ADV_BUILD = build/cardputer_adv_build +CARDPUTER_ADV_OUTPUT = build/esp32.esp32.esp32s3 +CARDPUTER_ADV_BIN = $(CARDPUTER_ADV_OUTPUT)/RNode_Firmware.ino.bin +CARDPUTER_ADV_PORT = $(if $(port),$(port),/dev/ttyACM0) +CARDPUTER_ADV_HASH_TOOL = tools/cardputer_adv_firmware_hash.py +CARDPUTER_ADV_DEFAULT_FREQ ?= 915000000UL +CARDPUTER_ADV_DEFAULT_BW ?= 250000UL +CARDPUTER_ADV_DEFAULT_SF ?= 11 +CARDPUTER_ADV_DEFAULT_CR ?= 5 +CARDPUTER_ADV_DEFAULT_TXP ?= 14 +CARDPUTER_ADV_EXTRA_FLAGS = \"-DBOARD_MODEL=0x43\" \"-DCARDPUTER_ADV_DEFAULT_FREQ=$(CARDPUTER_ADV_DEFAULT_FREQ)\" \"-DCARDPUTER_ADV_DEFAULT_BW=$(CARDPUTER_ADV_DEFAULT_BW)\" \"-DCARDPUTER_ADV_DEFAULT_SF=$(CARDPUTER_ADV_DEFAULT_SF)\" \"-DCARDPUTER_ADV_DEFAULT_CR=$(CARDPUTER_ADV_DEFAULT_CR)\" \"-DCARDPUTER_ADV_DEFAULT_TXP=$(CARDPUTER_ADV_DEFAULT_TXP)\" + +# T-Deck (Plus) dual-boot build — same staging pattern as the Cardputer Adv. +# Defaults reuse the CARDPUTER_ADV_DEFAULT_* fallbacks in Utilities.h. +TDECK_FQBN = esp32:esp32:esp32s3:CDCOnBoot=cdc,USBMode=hwcdc,FlashSize=16M,FlashMode=qio,PartitionScheme=no_ota +TDECK_SKETCH = build/tdeck_sketch/RNode_Firmware +TDECK_BUILD = build/tdeck_build +TDECK_OUTPUT = build/tdeck.esp32.esp32s3 +TDECK_BIN = $(TDECK_OUTPUT)/RNode_Firmware.ino.bin +TDECK_EXTRA_FLAGS = \"-DBOARD_MODEL=0x3B\" +ARDUINO_DATA_DIR ?= $(shell if [ -d "$(HOME)/Library/Arduino15/packages" ]; then printf "$(HOME)/Library/Arduino15"; else printf "$(HOME)/.arduino15"; fi) +ESP32_CORE_DIR = $(ARDUINO_DATA_DIR)/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER) + +all: firmware-cardputer_adv + +clean: + -rm -r ./build + -rm ./Release/rnode_firmware* + +prep: prep-avr prep-esp32 prep-samd + +prep-avr: + arduino-cli core update-index --config-file arduino-cli.yaml + arduino-cli core install arduino:avr --config-file arduino-cli.yaml + +prep-esp32: + arduino-cli core update-index --config-file arduino-cli.yaml + arduino-cli core install esp32:esp32@$(ARDUINO_ESP_CORE_VER) --config-file arduino-cli.yaml + arduino-cli lib install "Adafruit SSD1306" + arduino-cli lib install "Adafruit SH110X" + arduino-cli lib install "Adafruit ST7735 and ST7789 Library" + arduino-cli lib install "Adafruit NeoPixel" + arduino-cli lib install "XPowersLib" + arduino-cli lib install "Crypto" + +prep-m5stack: + arduino-cli lib install "M5Unified" + arduino-cli lib install "M5GFX" + arduino-cli lib install "M5Cardputer" + +prep-cardputer_adv: prep-esp32 prep-m5stack + +prepare-cardputer_adv-sketch: + rm -rf $(CARDPUTER_ADV_SKETCH) + mkdir -p $(CARDPUTER_ADV_SKETCH) + cp *.h *.cpp RNode_Firmware.ino $(CARDPUTER_ADV_SKETCH)/ + cp -R Fonts $(CARDPUTER_ADV_SKETCH)/ + +prep-samd: + arduino-cli core update-index --config-file arduino-cli.yaml + arduino-cli core install adafruit:samd --config-file arduino-cli.yaml + +prep-nrf: + arduino-cli core update-index --config-file arduino-cli.yaml + 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 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 + +console-site: + make -C Console clean site + +spiffs: console-site spiffs-image + +spiffs-image: + python Release/esptool/spiffsgen.py 1966080 ./Console/build Release/console_image.bin + +upload-spiffs: + @echo Deploying SPIFFS image... + python ./Release/esptool/esptool.py --chip esp32s3 --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 + +check_bt_buffers: + @./esp32_btbufs.py ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/libraries/BluetoothSerial/src/BluetoothSerial.cpp + +firmware: firmware-cardputer_adv + +firmware-mega2560: + arduino-cli compile --log --fqbn arduino:avr:mega + +firmware-tbeam: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\"" + +firmware-tbeam_sx126x: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\" \"-DMODEM=0x03\"" + +firmware-t3s3: + 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=0x42\" \"-DMODEM=0x03\"" + +firmware-t3s3_sx127x: + 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=0x42\" \"-DMODEM=0x01\"" + +firmware-t3s3_sx1280_pa: + 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=0x42\" \"-DMODEM=0x04\"" + +prepare-tdeck-sketch: + rm -rf $(TDECK_SKETCH) + mkdir -p $(TDECK_SKETCH) + cp *.h *.cpp RNode_Firmware.ino $(TDECK_SKETCH)/ + cp -R Fonts $(TDECK_SKETCH)/ + +firmware-tdeck: prepare-tdeck-sketch + arduino-cli compile --log --fqbn "$(TDECK_FQBN)" --build-path $(TDECK_BUILD) --output-dir $(TDECK_OUTPUT) --build-property "compiler.cpp.extra_flags=$(TDECK_EXTRA_FLAGS)" $(TDECK_SKETCH) + +firmware-cardputer_adv: prepare-cardputer_adv-sketch + arduino-cli compile --log --fqbn "$(CARDPUTER_ADV_FQBN)" --build-path $(CARDPUTER_ADV_BUILD) --output-dir $(CARDPUTER_ADV_OUTPUT) --build-property "compiler.cpp.extra_flags=$(CARDPUTER_ADV_EXTRA_FLAGS)" $(CARDPUTER_ADV_SKETCH) + +hash-cardputer_adv: firmware-cardputer_adv + python3 $(CARDPUTER_ADV_HASH_TOOL) --firmware $(CARDPUTER_ADV_BIN) --print-only + +set-hash-cardputer_adv: firmware-cardputer_adv + python3 $(CARDPUTER_ADV_HASH_TOOL) --port $(CARDPUTER_ADV_PORT) --firmware $(CARDPUTER_ADV_BIN) --reset + +set-running-hash-cardputer_adv: + python3 $(CARDPUTER_ADV_HASH_TOOL) --port $(CARDPUTER_ADV_PORT) --from-device --reset + +firmware-tbeam_supreme: + 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=0x3D" + +firmware-lora32_v10: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\"" + +firmware-lora32_v10_extled: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\" \"-DEXTERNAL_LEDS=true\"" + +firmware-lora32_v20: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\" \"-DEXTERNAL_LEDS=true\"" + +firmware-lora32_v21: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\"" + +firmware-lora32_v21_extled: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DEXTERNAL_LEDS=true\"" + +firmware-lora32_v21_tcxo: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DENABLE_TCXO=true\"" + +firmware-heltec32_v2: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\"" + +firmware-heltec32_v2_extled: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\" \"-DEXTERNAL_LEDS=true\"" + +firmware-heltec32_v3: + arduino-cli compile --log --fqbn esp32:esp32:heltec_wifi_lora_32_V3 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3A\"" + +firmware-heltec32_v4: + 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=0x3F\"" + +firmware-rnode_ng_20: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\"" + +firmware-rnode_ng_21: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x41\"" + +firmware-featheresp32: check_bt_buffers + arduino-cli compile --log --fqbn esp32:esp32:featheresp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x34\"" + +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-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-techo: + arduino-cli compile --log --fqbn adafruit:nrf52:pca10056 -e --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x44\"" + +firmware-xiao_s3: + arduino-cli compile --log --fqbn "esp32:esp32:XIAO_ESP32S3" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3E\"" + +upload: + arduino-cli upload -p /dev/ttyUSB0 --fqbn unsignedio:avr:rnode + +upload-mega2560: + arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:mega + +upload-tbeam: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:t-beam + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.t-beam/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-tbeam_sx1262: + arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:t-beam + @sleep 1 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.t-beam/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-lora32_v10: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32 + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/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-lora32_v20: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32 + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/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-lora32_v21: + arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:ttgo-lora32 + @sleep 1 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/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-heltec32_v2: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:heltec_wifi_lora_32_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 + +upload-heltec32_v3: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:heltec_wifi_lora_32_V3 + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bin) + @sleep 3 + python ./Release/esptool/esptool.py --chip esp32-s3 --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_v4: + 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-s3 --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-tdeck: + 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-s3 --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-cardputer_adv: firmware-cardputer_adv + python3 -m esptool --chip esp32s3 merge-bin --output build/cardputer_adv_merged.bin --flash-mode dio --flash-size 8MB 0x0 $(CARDPUTER_ADV_OUTPUT)/RNode_Firmware.ino.bootloader.bin 0x8000 $(CARDPUTER_ADV_OUTPUT)/RNode_Firmware.ino.partitions.bin 0xe000 $(ESP32_CORE_DIR)/tools/partitions/boot_app0.bin 0x10000 $(CARDPUTER_ADV_BIN) + python3 -m esptool --chip esp32s3 --port $(CARDPUTER_ADV_PORT) --baud 460800 --before default_reset --after hard_reset write-flash 0x0 build/cardputer_adv_merged.bin + @sleep 2 + python3 $(CARDPUTER_ADV_HASH_TOOL) --port $(CARDPUTER_ADV_PORT) --firmware $(CARDPUTER_ADV_BIN) --reset + +upload-tbeam_supreme: + 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-s3 --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-rnode_ng_20: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:ttgo-lora32 + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/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-rnode_ng_21: + arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:ttgo-lora32 + @sleep 1 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.ttgo-lora32/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-t3s3: + 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 esp32s3 --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-featheresp32: + arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:featheresp32 + @sleep 1 + rnodeconf /dev/ttyUSB0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.featheresp32/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-rak4631: + arduino-cli upload -p /dev/ttyACM0 --fqbn rakwireless:nrf52:WisCoreRAK4631Board + @sleep 1 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0) + +upload-heltec_t114: + arduino-cli upload -p /dev/ttyACM0 --fqbn Heltec_nRF52:Heltec_nRF52:HT-n5262 + @sleep 1 + 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 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes from_device /dev/ttyACM0) + +upload-xiao_s3: + arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:XIAO_ESP32S3 + @sleep 1 + rnodeconf /dev/ttyACM0 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bin) + @sleep 3 + python ./Release/esptool/esptool.py --chip esp32s3 --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 + +release: release-cardputer_adv + +release-all: release-cardputer_adv + +release-hashes: + python ./release_hashes.py > ./Release/release.json + +release-rnode: + arduino-cli compile --fqbn unsignedio:avr:rnode -e + cp build/unsignedio.avr.rnode/RNode_Firmware.ino.hex Release/rnode_firmware.hex + rm -r build + +release-tbeam: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam.boot_app0 + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bin build/rnode_firmware_tbeam.bin + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam.bootloader + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam.partitions + zip --junk-paths ./Release/rnode_firmware_tbeam.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam.boot_app0 build/rnode_firmware_tbeam.bin build/rnode_firmware_tbeam.bootloader build/rnode_firmware_tbeam.partitions + rm -r build + +release-tbeam_sx1262: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:t-beam -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x33\" \"-DMODEM=0x03\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam_sx1262.boot_app0 + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bin build/rnode_firmware_tbeam_sx1262.bin + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam_sx1262.bootloader + cp build/esp32.esp32.t-beam/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam_sx1262.partitions + zip --junk-paths ./Release/rnode_firmware_tbeam_sx1262.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam_sx1262.boot_app0 build/rnode_firmware_tbeam_sx1262.bin build/rnode_firmware_tbeam_sx1262.bootloader build/rnode_firmware_tbeam_sx1262.partitions + rm -r build + +release-lora32_v10: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v10.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v10.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v10.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v10.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v10.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v10.boot_app0 build/rnode_firmware_lora32v10.bin build/rnode_firmware_lora32v10.bootloader build/rnode_firmware_lora32v10.partitions + rm -r build + +release-lora32_v20: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v20.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v20.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v20.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v20.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v20.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v20.boot_app0 build/rnode_firmware_lora32v20.bin build/rnode_firmware_lora32v20.bootloader build/rnode_firmware_lora32v20.partitions + rm -r build + +release-lora32_v21: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v21.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21.boot_app0 build/rnode_firmware_lora32v21.bin build/rnode_firmware_lora32v21.bootloader build/rnode_firmware_lora32v21.partitions + rm -r build + +release-lora32_v10_extled: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\" \"-DEXTERNAL_LEDS=true\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v10.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v10.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v10.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v10.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v10.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v10.boot_app0 build/rnode_firmware_lora32v10.bin build/rnode_firmware_lora32v10.bootloader build/rnode_firmware_lora32v10.partitions + rm -r build + +release-lora32_v20_extled: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x36\" \"-DEXTERNAL_LEDS=true\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v20.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v20.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v20.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v20.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v20_extled.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v20.boot_app0 build/rnode_firmware_lora32v20.bin build/rnode_firmware_lora32v20.bootloader build/rnode_firmware_lora32v20.partitions + rm -r build + +release-lora32_v21_extled: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DEXTERNAL_LEDS=true\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v21_extled.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21.boot_app0 build/rnode_firmware_lora32v21.bin build/rnode_firmware_lora32v21.bootloader build/rnode_firmware_lora32v21.partitions + rm -r build + +release-lora32_v21_tcxo: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x37\" \"-DENABLE_TCXO=true\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_lora32v21_tcxo.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_lora32v21_tcxo.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_lora32v21_tcxo.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_lora32v21_tcxo.partitions + zip --junk-paths ./Release/rnode_firmware_lora32v21_tcxo.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_lora32v21_tcxo.boot_app0 build/rnode_firmware_lora32v21_tcxo.bin build/rnode_firmware_lora32v21_tcxo.bootloader build/rnode_firmware_lora32v21_tcxo.partitions + rm -r build + +release-heltec32_v2: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v2.boot_app0 + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v2.bin + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v2.bootloader + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v2.partitions + zip --junk-paths ./Release/rnode_firmware_heltec32v2.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v2.boot_app0 build/rnode_firmware_heltec32v2.bin build/rnode_firmware_heltec32v2.bootloader build/rnode_firmware_heltec32v2.partitions + rm -r build + +release-heltec32_v3: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V3 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3A\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v3.boot_app0 + cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v3.bin + cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v3.bootloader + cp build/esp32.esp32.heltec_wifi_lora_32_V3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v3.partitions + zip --junk-paths ./Release/rnode_firmware_heltec32v3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v3.boot_app0 build/rnode_firmware_heltec32v3.bin build/rnode_firmware_heltec32v3.bootloader build/rnode_firmware_heltec32v3.partitions + rm -r build + +release-heltec32_v4: check_bt_buffers + arduino-cli compile --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=0x3F\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v4pa.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v4pa.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v4pa.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v4pa.partitions + zip --junk-paths ./Release/rnode_firmware_heltec32v4pa.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v4pa.boot_app0 build/rnode_firmware_heltec32v4pa.bin build/rnode_firmware_heltec32v4pa.bootloader build/rnode_firmware_heltec32v4pa.partitions + rm -r build + +release-heltec32_v2_extled: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\" \"-DEXTERNAL_LEDS=true\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v2.boot_app0 + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bin build/rnode_firmware_heltec32v2.bin + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_heltec32v2.bootloader + cp build/esp32.esp32.heltec_wifi_lora_32_V2/RNode_Firmware.ino.partitions.bin build/rnode_firmware_heltec32v2.partitions + zip --junk-paths ./Release/rnode_firmware_heltec32v2.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v2.boot_app0 build/rnode_firmware_heltec32v2.bin build/rnode_firmware_heltec32v2.bootloader build/rnode_firmware_heltec32v2.partitions + rm -r build + +release-rnode_ng_20: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_ng20.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_ng20.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_ng20.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_ng20.partitions + zip --junk-paths ./Release/rnode_firmware_ng20.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_ng20.boot_app0 build/rnode_firmware_ng20.bin build/rnode_firmware_ng20.bootloader build/rnode_firmware_ng20.partitions + rm -r build + +release-rnode_ng_21: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x41\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_ng21.boot_app0 + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bin build/rnode_firmware_ng21.bin + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_ng21.bootloader + cp build/esp32.esp32.ttgo-lora32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_ng21.partitions + zip --junk-paths ./Release/rnode_firmware_ng21.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_ng21.boot_app0 build/rnode_firmware_ng21.bin build/rnode_firmware_ng21.bootloader build/rnode_firmware_ng21.partitions + rm -r build + +release-t3s3: + arduino-cli compile --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=0x42\" \"-DMODEM=0x03\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3.partitions + zip --junk-paths ./Release/rnode_firmware_t3s3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3.boot_app0 build/rnode_firmware_t3s3.bin build/rnode_firmware_t3s3.bootloader build/rnode_firmware_t3s3.partitions + rm -r build + +release-t3s3_sx1280_pa: + arduino-cli compile --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=0x42\" \"-DMODEM=0x04\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3_sx1280_pa.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3_sx1280_pa.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3_sx1280_pa.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3_sx1280_pa.partitions + zip --junk-paths ./Release/rnode_firmware_t3s3_sx1280_pa.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3_sx1280_pa.boot_app0 build/rnode_firmware_t3s3_sx1280_pa.bin build/rnode_firmware_t3s3_sx1280_pa.bootloader build/rnode_firmware_t3s3_sx1280_pa.partitions + rm -r build + +release-t3s3_sx127x: + arduino-cli compile --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=0x42\" \"-DMODEM=0x01\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_t3s3_sx127x.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_t3s3_sx127x.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_t3s3_sx127x.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_t3s3_sx127x.partitions + zip --junk-paths ./Release/rnode_firmware_t3s3_sx127x.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_t3s3_sx127x.boot_app0 build/rnode_firmware_t3s3_sx127x.bin build/rnode_firmware_t3s3_sx127x.bootloader build/rnode_firmware_t3s3_sx127x.partitions + rm -r build + +release-tdeck: + arduino-cli compile --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=0x3B\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tdeck.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_tdeck.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tdeck.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tdeck.partitions + zip --junk-paths ./Release/rnode_firmware_tdeck.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tdeck.boot_app0 build/rnode_firmware_tdeck.bin build/rnode_firmware_tdeck.bootloader build/rnode_firmware_tdeck.partitions + rm -r build + +release-cardputer_adv: prepare-cardputer_adv-sketch + arduino-cli compile --fqbn "$(CARDPUTER_ADV_FQBN)" --build-path $(CARDPUTER_ADV_BUILD) --output-dir $(CARDPUTER_ADV_OUTPUT) --build-property "compiler.cpp.extra_flags=$(CARDPUTER_ADV_EXTRA_FLAGS)" $(CARDPUTER_ADV_SKETCH) + cp $(ESP32_CORE_DIR)/tools/partitions/boot_app0.bin build/rnode_firmware_cardputer_adv.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_cardputer_adv.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_cardputer_adv.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_cardputer_adv.partitions + zip --junk-paths ./Release/rnode_firmware_cardputer_adv.zip ./Release/esptool/esptool.py build/rnode_firmware_cardputer_adv.boot_app0 build/rnode_firmware_cardputer_adv.bin build/rnode_firmware_cardputer_adv.bootloader build/rnode_firmware_cardputer_adv.partitions + rm -r build + +release-tbeam_supreme: + arduino-cli compile --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=0x3D\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_tbeam_supreme.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_tbeam_supreme.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_tbeam_supreme.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_tbeam_supreme.partitions + zip --junk-paths ./Release/rnode_firmware_tbeam_supreme.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_tbeam_supreme.boot_app0 build/rnode_firmware_tbeam_supreme.bin build/rnode_firmware_tbeam_supreme.bootloader build/rnode_firmware_tbeam_supreme.partitions + rm -r build + +release-featheresp32: check_bt_buffers + arduino-cli compile --fqbn esp32:esp32:featheresp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x34\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_featheresp32.boot_app0 + cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.bin build/rnode_firmware_featheresp32.bin + cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_featheresp32.bootloader + cp build/esp32.esp32.featheresp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_featheresp32.partitions + zip --junk-paths ./Release/rnode_firmware_featheresp32.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_featheresp32.boot_app0 build/rnode_firmware_featheresp32.bin build/rnode_firmware_featheresp32.bootloader build/rnode_firmware_featheresp32.partitions + rm -r build + +release-genericesp32: 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_esp32_generic.boot_app0 + cp build/esp32.esp32.esp32/RNode_Firmware.ino.bin build/rnode_firmware_esp32_generic.bin + cp build/esp32.esp32.esp32/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_esp32_generic.bootloader + cp build/esp32.esp32.esp32/RNode_Firmware.ino.partitions.bin build/rnode_firmware_esp32_generic.partitions + 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-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 + rm -r build + +release-rak4631: + arduino-cli compile --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\"" + cp build/rakwireless.nrf52.WisCoreRAK4631Board/RNode_Firmware.ino.hex build/rnode_firmware_rak4631.hex + adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_rak4631.hex Release/rnode_firmware_rak4631.zip + +release-heltec_t114: + arduino-cli compile --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\"" + 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-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 + adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application build/rnode_firmware_techo.hex Release/rnode_firmware_techo.zip + +release-xiao_s3: + arduino-cli compile --fqbn "esp32:esp32:XIAO_ESP32S3" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3E\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_xiao_esp32s3.boot_app0 + cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bin build/rnode_firmware_xiao_esp32s3.bin + cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_xiao_esp32s3.bootloader + cp build/esp32.esp32.XIAO_ESP32S3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_xiao_esp32s3.partitions + zip --junk-paths ./Release/rnode_firmware_xiao_esp32s3.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_xiao_esp32s3.boot_app0 build/rnode_firmware_xiao_esp32s3.bin build/rnode_firmware_xiao_esp32s3.bootloader build/rnode_firmware_xiao_esp32s3.partitions + rm -r build diff --git a/vendor/rnode_firmware/Modem.h b/vendor/rnode_firmware/Modem.h new file mode 100644 index 0000000..027e314 --- /dev/null +++ b/vendor/rnode_firmware/Modem.h @@ -0,0 +1,4 @@ +#define SX1276 0x01 +#define SX1278 0x02 +#define SX1262 0x03 +#define SX1280 0x04 diff --git a/vendor/rnode_firmware/Power.h b/vendor/rnode_firmware/Power.h new file mode 100644 index 0000000..5cbd7ad --- /dev/null +++ b/vendor/rnode_firmware/Power.h @@ -0,0 +1,700 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#define PMU_TEMP_MIN -30 +#define PMU_TEMP_MAX 90 +#define PMU_TEMP_OFFSET 120 +bool pmu_temp_sensor_ready = false; +float pmu_temperature = PMU_TEMP_MIN-1; + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + #include +#endif + +#if BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 + #include + XPowersLibInterface* PMU = NULL; + + #ifndef PMU_WIRE_PORT + #if BOARD_MODEL == BOARD_TBEAM_S_V1 + #define PMU_WIRE_PORT Wire1 + #else + #define PMU_WIRE_PORT Wire + #endif + #endif + + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.14 + + void disablePeripherals() { + if (PMU) { + // GNSS RTC PowerVDD + PMU->enablePowerOutput(XPOWERS_VBACKUP); + + // LoRa VDD + PMU->disablePowerOutput(XPOWERS_ALDO2); + + // GNSS VDD + PMU->disablePowerOutput(XPOWERS_ALDO3); + } + } + + bool pmuInterrupt; + void setPmuFlag() + { + pmuInterrupt = true; + } +#elif BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.3 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 35; + 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_T3S3 + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.217 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 1; + 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_TDECK + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.3 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 4; + 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_HELTEC32_V3 + // Unless we implement some real voodoo + // on these boards, we can't say with + // any certainty whether we are actually + // charging and have reached a charge + // complete state. The *only* data point + // we have to go from is the bus voltage. + // The BAT_V_CHG and BAT_V_FLOAT values + // are set high here to avoid the display + // indication confusingly flapping + // between charge completed, charging and + // discharging states. + // Update: Vodoo implemented. Hopefully + // it will work accross different boards. + #define BAT_V_MIN 3.05 + #define BAT_V_MAX 4.0 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 1; + const uint8_t pin_ctrl = 37; + 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_HELTEC32_V4 + #define BAT_V_MIN 3.05 + #define BAT_V_MAX 4.0 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 1; + const uint8_t pin_ctrl = 37; + 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_HELTEC_T114 + #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 = 4; + const uint8_t pin_ctrl = 6; + 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 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 4; + 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; +#endif + +uint32_t last_pmu_update = 0; +uint8_t pmu_target_pps = 1; +int pmu_update_interval = 1000/pmu_target_pps; +uint8_t pmu_charged_ascertain = 0; +uint8_t pmu_rc = 0; +uint8_t pmu_sc = 0; +float bat_delay_diff = 0; +bool bat_diff_positive = false; +#define PMU_R_INTERVAL 5 +#define PMU_SCV_RESET_INTERVAL 3 +void kiss_indicate_battery(); +void kiss_indicate_temperature(); + +void measure_temperature() { + #if PLATFORM == PLATFORM_ESP32 + if (pmu_temp_sensor_ready) { pmu_temperature = temperatureRead(); } else { pmu_temperature = PMU_TEMP_MIN-1; } + #endif +} + +void measure_battery() { + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + battery_installed = true; + battery_indeterminate = true; + external_power = false; + + int battery_level = M5Cardputer.Power.getBatteryLevel(); + int battery_mv = M5Cardputer.Power.getBatteryVoltage(); + + if (battery_mv > 0) { + battery_voltage = battery_mv / 1000.0f; + } + + if (battery_level >= 0) { + battery_percent = battery_level; + battery_ready = true; + } else if (battery_mv > 0) { + battery_percent = ((battery_mv - 3300.0f) * 100.0f) / (4150.0f - 3350.0f); + battery_ready = true; + } else { + battery_percent = 0.0f; + battery_voltage = 0.0f; + battery_ready = false; + } + + if (battery_percent > 100.0f) battery_percent = 100.0f; + if (battery_percent < 0.0f) battery_percent = 0.0f; + + m5::Power_Class::is_charging_t charging_state = M5Cardputer.Power.isCharging(); + if (charging_state == m5::Power_Class::is_charging_t::is_charging) { + battery_state = BATTERY_STATE_CHARGING; + battery_indeterminate = false; + } else if (charging_state == m5::Power_Class::is_charging_t::is_discharging) { + battery_state = BATTERY_STATE_DISCHARGING; + battery_indeterminate = false; + } else { + battery_state = BATTERY_STATE_UNKNOWN; + } + + #elif 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 + battery_installed = true; + #if BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 + battery_indeterminate = false; + #else + battery_indeterminate = true; + #endif + + #if BOARD_MODEL == BOARD_HELTEC32_V3 + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.0041; + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.00418; + #elif BOARD_MODEL == BOARD_T3S3 + 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_TECHO + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.007067; + #else + float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*7.26; + #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++; + if (!battery_ready && bat_samples_count >= BAT_SAMPLES) { + battery_ready = true; + } + + if (battery_ready) { + + battery_percent = 0; + for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) { + battery_percent += bat_p_samples[bi]; + } + battery_percent = battery_percent/BAT_SAMPLES; + + battery_voltage = 0; + for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) { + battery_voltage += bat_v_samples[bi]; + } + battery_voltage = battery_voltage/BAT_SAMPLES; + + if (bat_delay_v == 0) bat_delay_v = battery_voltage; + if (bat_state_change_v == 0) bat_state_change_v = battery_voltage; + if (battery_percent > 100.0) battery_percent = 100.0; + if (battery_percent < 0.0) battery_percent = 0.0; + + if (bat_samples_count%BAT_SAMPLES == 0) { + pmu_sc++; + bat_delay_diff = battery_voltage-bat_state_change_v; + + if (battery_voltage < bat_delay_v && battery_voltage < BAT_V_FLOAT) { + if (bat_voltage_dropping == false) { + if (bat_delay_diff < -0.008) { + bat_voltage_dropping = true; + bat_state_change_v = battery_voltage; + } + } else { + if (pmu_sc%PMU_SCV_RESET_INTERVAL == 0) { bat_state_change_v = battery_voltage; } + } + } else { + if (bat_voltage_dropping == true) { + if (bat_delay_diff > 0.01) { + bat_voltage_dropping = false; + bat_state_change_v = battery_voltage; + } + } + } + bat_samples_count = 0; + bat_delay_v = battery_voltage; + } + + if (bat_voltage_dropping && battery_voltage < BAT_V_FLOAT) { + // if (battery_state != BATTERY_STATE_DISCHARGING) { SerialBT.printf("STATE CHANGE to DISCHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_DISCHARGING; + pmu_charged_ascertain = 0; + } else { + if (pmu_charged_ascertain < 8) { pmu_charged_ascertain++; } + else { + if (battery_percent < 100.0) { + // if (battery_state != BATTERY_STATE_CHARGING) { SerialBT.printf("STATE CHANGE to CHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_CHARGING; + } else { + // if (battery_state != BATTERY_STATE_CHARGED) { SerialBT.printf("STATE CHANGE to CHARGED at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_CHARGED; + } + } + } + + #if MCU_VARIANT == MCU_NRF52 + if (bt_state != BT_STATE_OFF) { blebas.write(battery_percent); } + #endif + + // if (bt_state == BT_STATE_CONNECTED) { + // SerialBT.printf("\nBus voltage %.3fv. Unfiltered %.3fv. Diff %.3f", battery_voltage, bat_v_samples[BAT_SAMPLES-1], bat_delay_diff); + // if (bat_voltage_dropping) { SerialBT.printf("\n Voltage is dropping. Percentage %.1f%%.", battery_percent); } + // else { SerialBT.printf("\n Voltage is not dropping. Percentage %.1f%%.", battery_percent); } + // if (battery_state == BATTERY_STATE_DISCHARGING) { SerialBT.printf("\n Battery discharging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); } + // if (battery_state == BATTERY_STATE_CHARGING) { SerialBT.printf("\n Battery charging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); } + // if (battery_state == BATTERY_STATE_CHARGED) { SerialBT.print("\n Battery is charged."); } + // SerialBT.print("\n"); + // } + } + + #elif BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 + if (PMU) { + float discharge_current = 0; + float charge_current = 0; + float ext_voltage = 0; + float ext_current = 0; + if (PMU->getChipModel() == XPOWERS_AXP192) { + discharge_current = ((XPowersAXP192*)PMU)->getBattDischargeCurrent(); + charge_current = ((XPowersAXP192*)PMU)->getBatteryChargeCurrent(); + battery_voltage = PMU->getBattVoltage()/1000.0; + // battery_percent = PMU->getBattPercentage()*1.0; + battery_installed = PMU->isBatteryConnect(); + external_power = PMU->isVbusIn(); + ext_voltage = PMU->getVbusVoltage()/1000.0; + ext_current = ((XPowersAXP192*)PMU)->getVbusCurrent(); + } + else if (PMU->getChipModel() == XPOWERS_AXP2101) { + battery_voltage = PMU->getBattVoltage()/1000.0; + // battery_percent = PMU->getBattPercentage()*1.0; + battery_installed = PMU->isBatteryConnect(); + external_power = PMU->isVbusIn(); + ext_voltage = PMU->getVbusVoltage()/1000.0; + } + + if (battery_installed) { + if (PMU->isCharging()) { + battery_state = BATTERY_STATE_CHARGING; + battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0; + } else { + if (PMU->isDischarge()) { + battery_state = BATTERY_STATE_DISCHARGING; + battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0; + } else { + battery_state = BATTERY_STATE_CHARGED; + battery_percent = 100.0; + } + } + } else { + battery_state = BATTERY_STATE_UNKNOWN; + battery_percent = 0.0; + battery_voltage = 0.0; + } + + if (battery_percent > 100.0) battery_percent = 100.0; + if (battery_percent < 0.0) battery_percent = 0.0; + + float charge_watts = battery_voltage*(charge_current/1000.0); + float discharge_watts = battery_voltage*(discharge_current/1000.0); + float ext_watts = ext_voltage*(ext_current/1000.0); + + battery_ready = true; + + // if (bt_state == BT_STATE_CONNECTED) { + // if (battery_installed) { + // if (external_power) { + // SerialBT.printf("External power connected, drawing %.2fw, %.1fmA at %.1fV\n", ext_watts, ext_current, ext_voltage); + // } else { + // SerialBT.println("Running on battery"); + // } + // SerialBT.printf("Battery percentage %.1f%%\n", battery_percent); + // SerialBT.printf("Battery voltage %.2fv\n", battery_voltage); + // // SerialBT.printf("Temperature %.1f%\n", auxillary_temperature); + + // if (battery_state == BATTERY_STATE_CHARGING) { + // SerialBT.printf("Charging with %.2fw, %.1fmA at %.1fV\n", charge_watts, charge_current, battery_voltage); + // } else if (battery_state == BATTERY_STATE_DISCHARGING) { + // SerialBT.printf("Discharging at %.2fw, %.1fmA at %.1fV\n", discharge_watts, discharge_current, battery_voltage); + // } else if (battery_state == BATTERY_STATE_CHARGED) { + // SerialBT.printf("Battery charged\n"); + // } + // } else { + // SerialBT.println("No battery installed"); + // } + // SerialBT.println(""); + // } + } + else { + battery_ready = false; + } + #endif + + if (battery_ready) { + pmu_rc++; + if (pmu_rc%PMU_R_INTERVAL == 0) { + kiss_indicate_battery(); + if (pmu_temp_sensor_ready) { kiss_indicate_temperature(); } + } + } +} + +void update_pmu() { + if (millis()-last_pmu_update >= pmu_update_interval) { + measure_battery(); + measure_temperature(); + last_pmu_update = millis(); + } +} + +bool init_pmu() { + #if IS_ESP32S3 + pmu_temp_sensor_ready = true; + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + battery_installed = true; + battery_indeterminate = true; + battery_state = BATTERY_STATE_UNKNOWN; + external_power = false; + measure_battery(); + last_pmu_update = millis(); + return true; + #elif BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_TECHO + pinMode(pin_vbat, INPUT); + return true; + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + // there are three version of V3: V3, V3.1, and V3.2 + // V3 and V3.1 have a pull up on pin_ctrl and are active low + // V3.2 has a transistor and active high + // put the pin input mode and read it. if it's high, we have V3 or V3.1 + // other wise, it's a V3.2 + uint16_t pin_ctrl_value; + uint8_t pin_ctrl_active = LOW; + pinMode(pin_ctrl, INPUT); + pin_ctrl_value = digitalRead(pin_ctrl); + if(pin_ctrl_value == HIGH) { + // We have either a V3 or V3.1 + pin_ctrl_active = LOW; + } + else { + // We have a V3.2 + pin_ctrl_active = HIGH; + } + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, pin_ctrl_active); + return true; + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, HIGH); + return true; + #elif BOARD_MODEL == BOARD_HELTEC_T114 + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, HIGH); + return true; + #elif BOARD_MODEL == BOARD_TBEAM + Wire.begin(I2C_SDA, I2C_SCL); + + if (!PMU) { + PMU = new XPowersAXP2101(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + PMU = new XPowersAXP192(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + return false; + } + + // Configure charging indicator + PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); + + pinMode(PMU_IRQ, INPUT_PULLUP); + attachInterrupt(PMU_IRQ, setPmuFlag, FALLING); + + if (PMU->getChipModel() == XPOWERS_AXP192) { + + // Turn off unused power sources to save power + PMU->disablePowerOutput(XPOWERS_DCDC1); + PMU->disablePowerOutput(XPOWERS_DCDC2); + PMU->disablePowerOutput(XPOWERS_LDO2); + PMU->disablePowerOutput(XPOWERS_LDO3); + + // Set the power of LoRa and GPS module to 3.3V + // LoRa + PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300); + // GPS + PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300); + // OLED + PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); + + // Turn on LoRa + PMU->enablePowerOutput(XPOWERS_LDO2); + + // Turn on GPS + //PMU->enablePowerOutput(XPOWERS_LDO3); + + // protected oled power source + PMU->setProtectedChannel(XPOWERS_DCDC1); + // protected esp32 power source + PMU->setProtectedChannel(XPOWERS_DCDC3); + // enable oled power + PMU->enablePowerOutput(XPOWERS_DCDC1); + + PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ); + + PMU->enableIRQ(XPOWERS_AXP192_VBUS_REMOVE_IRQ | + XPOWERS_AXP192_VBUS_INSERT_IRQ | + XPOWERS_AXP192_BAT_CHG_DONE_IRQ | + XPOWERS_AXP192_BAT_CHG_START_IRQ | + XPOWERS_AXP192_BAT_REMOVE_IRQ | + XPOWERS_AXP192_BAT_INSERT_IRQ | + XPOWERS_AXP192_PKEY_SHORT_IRQ + ); + + } + else if (PMU->getChipModel() == XPOWERS_AXP2101) { + + // Turn off unused power sources to save power + PMU->disablePowerOutput(XPOWERS_DCDC2); + PMU->disablePowerOutput(XPOWERS_DCDC3); + PMU->disablePowerOutput(XPOWERS_DCDC4); + PMU->disablePowerOutput(XPOWERS_DCDC5); + PMU->disablePowerOutput(XPOWERS_ALDO1); + PMU->disablePowerOutput(XPOWERS_ALDO2); + PMU->disablePowerOutput(XPOWERS_ALDO3); + PMU->disablePowerOutput(XPOWERS_ALDO4); + PMU->disablePowerOutput(XPOWERS_BLDO1); + PMU->disablePowerOutput(XPOWERS_BLDO2); + PMU->disablePowerOutput(XPOWERS_DLDO1); + PMU->disablePowerOutput(XPOWERS_DLDO2); + PMU->disablePowerOutput(XPOWERS_VBACKUP); + + // Set the power of LoRa and GPS module to 3.3V + // LoRa + PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); + // GPS + PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); + PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300); + + // ESP32 VDD + // ! No need to set, automatically open , Don't close it + // PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); + // PMU->setProtectedChannel(XPOWERS_DCDC1); + PMU->setProtectedChannel(XPOWERS_DCDC1); + + // LoRa VDD + PMU->enablePowerOutput(XPOWERS_ALDO2); + + // GNSS VDD + //PMU->enablePowerOutput(XPOWERS_ALDO3); + + // GNSS RTC PowerVDD + //PMU->enablePowerOutput(XPOWERS_VBACKUP); + } + + PMU->enableSystemVoltageMeasure(); + PMU->enableVbusVoltageMeasure(); + PMU->enableBattVoltageMeasure(); + // It is necessary to disable the detection function of the TS pin on the board + // without the battery temperature detection function, otherwise it will cause abnormal charging + PMU->disableTSPinMeasure(); + + // Set the time of pressing the button to turn off + PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); + + return true; + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Wire1.begin(I2C_SDA, I2C_SCL); + + if (!PMU) { + PMU = new XPowersAXP2101(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + return false; + } + + /** + * gnss module power channel + * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during + * initialization + */ + PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO4); + + // lora radio power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO3); + + // m.2 interface + PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300); + PMU->enablePowerOutput(XPOWERS_DCDC3); + + /** + * ALDO2 cannot be turned off. + * It is a necessary condition for sensor communication. + * It must be turned on to properly access the sensor and screen + * It is also responsible for the power supply of PCF8563 + */ + PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO2); + + // 6-axis , magnetometer ,bme280 , oled screen power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO1); + + // sdcard power channle + PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); + PMU->enablePowerOutput(XPOWERS_BLDO1); + + // PMU->setPowerChannelVoltage(XPOWERS_DCDC4, 3300); + // PMU->enablePowerOutput(XPOWERS_DCDC4); + + // not use channel + PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited + PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited + PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_VBACKUP); + + // Configure charging + PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); + PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); + // TODO: Reset + PMU->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); + + // Set the time of pressing the button to turn off + PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); + PMU->setPowerKeyPressOnTime(XPOWERS_POWERON_128MS); + + // disable all axp chip interrupt + PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + PMU->clearIrqStatus(); + + // It is necessary to disable the detection function of the TS pin on the board + // without the battery temperature detection function, otherwise it will cause abnormal charging + PMU->disableTSPinMeasure(); + PMU->enableVbusVoltageMeasure(); + PMU->enableBattVoltageMeasure(); + + + return true; + #else + return false; + #endif +} diff --git a/vendor/rnode_firmware/Python Module/Example.py b/vendor/rnode_firmware/Python Module/Example.py new file mode 100644 index 0000000..0748736 --- /dev/null +++ b/vendor/rnode_firmware/Python Module/Example.py @@ -0,0 +1,47 @@ +# This is a short example program that +# demonstrates the bare minimum of using +# RNode in a Python program. +# +# The example and the RNode.py library is +# written for Python 3, so be sure to run +# it with: python3 Example.py + +# First we'll import the RNodeInterface class. +from RNode import RNodeInterface + +# We'll also define which serial port the +# RNode is attached to. +serialPort = "/dev/ttyUSB0" + +# This function gets called every time a +# packet is received +def gotPacket(data, rnode): + message = data.decode("utf-8") + print("Received a packet: "+message) + print("RSSI: "+str(rnode.r_stat_rssi)+" dBm") + print("SNR: "+str(rnode.r_stat_snr)+" dB") + +# Create an RNode instance. This configures +# and powers up the radio. +rnode = RNodeInterface( + callback = gotPacket, + name = "My RNode", + port = serialPort, + frequency = 868000000, + bandwidth = 125000, + txpower = 2, + sf = 7, + cr = 5, + loglevel = RNodeInterface.LOG_DEBUG) + +# Enter a loop waiting for user input. +try: + print("Waiting for packets, hit enter to send a packet, Ctrl-C to exit") + while True: + input() + message = "Hello World!" + data = message.encode("utf-8") + rnode.send(data) +except KeyboardInterrupt as e: + print("") + exit() \ No newline at end of file diff --git a/vendor/rnode_firmware/Python Module/RNode.py b/vendor/rnode_firmware/Python Module/RNode.py new file mode 100644 index 0000000..e63e214 --- /dev/null +++ b/vendor/rnode_firmware/Python Module/RNode.py @@ -0,0 +1,536 @@ +# RNode interface class for Python 3 +# +# MIT License +# +# Copyright (c) 2020 Mark Qvist - unsigned.io +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from time import sleep +import sys +import serial +import threading +import time +import math + +class KISS(): + FEND = 0xC0 + FESC = 0xDB + TFEND = 0xDC + TFESC = 0xDD + CMD_UNKNOWN = 0xFE + CMD_DATA = 0x00 + CMD_FREQUENCY = 0x01 + CMD_BANDWIDTH = 0x02 + CMD_TXPOWER = 0x03 + CMD_SF = 0x04 + CMD_CR = 0x05 + CMD_RADIO_STATE = 0x06 + CMD_RADIO_LOCK = 0x07 + CMD_DETECT = 0x08 + CMD_PROMISC = 0x0E + CMD_READY = 0x0F + CMD_STAT_RX = 0x21 + CMD_STAT_TX = 0x22 + CMD_STAT_RSSI = 0x23 + CMD_STAT_SNR = 0x24 + CMD_BLINK = 0x30 + CMD_RANDOM = 0x40 + CMD_FW_VERSION = 0x50 + CMD_ROM_READ = 0x51 + + DETECT_REQ = 0x73 + DETECT_RESP = 0x46 + + RADIO_STATE_OFF = 0x00 + RADIO_STATE_ON = 0x01 + RADIO_STATE_ASK = 0xFF + + CMD_ERROR = 0x90 + ERROR_INITRADIO = 0x01 + ERROR_TXFAILED = 0x02 + ERROR_EEPROM_LOCKED = 0x03 + + @staticmethod + def escape(data): + data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd])) + data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc])) + return data + + +class RNodeInterface(): + MTU = 500 + MAX_CHUNK = 32768 + FREQ_MIN = 137000000 + FREQ_MAX = 1020000000 + + LOG_CRITICAL = 0 + LOG_ERROR = 1 + LOG_WARNING = 2 + LOG_NOTICE = 3 + LOG_INFO = 4 + LOG_VERBOSE = 5 + LOG_DEBUG = 6 + LOG_EXTREME = 7 + + FREQ_MIN = 137000000 + FREQ_MAX = 1020000000 + + RSSI_OFFSET = 157 + + CALLSIGN_MAX_LEN = 32 + + def __init__(self, callback, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, loglevel = LOG_NOTICE, flow_control = False, id_interval = None, id_callsign = None): + self.serial = None + self.loglevel = loglevel + self.callback = callback + self.name = name + self.port = port + self.speed = 115200 + self.databits = 8 + self.parity = serial.PARITY_NONE + self.stopbits = 1 + self.timeout = 100 + self.online = False + + self.frequency = frequency + self.bandwidth = bandwidth + self.txpower = txpower + self.sf = sf + self.cr = cr + self.state = KISS.RADIO_STATE_OFF + self.bitrate = 0 + + self.last_id = 0 + + self.r_frequency = None + self.r_bandwidth = None + self.r_txpower = None + self.r_sf = None + self.r_cr = None + self.r_state = None + self.r_lock = None + self.r_stat_rx = None + self.r_stat_tx = None + self.r_stat_rssi = None + self.r_stat_snr = None + self.r_random = None + + self.packet_queue = [] + self.flow_control = flow_control + self.interface_ready = False + + self.validcfg = True + if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX): + self.log("Invalid frequency configured for "+str(self), RNodeInterface.LOG_ERROR) + self.validcfg = False + + if (self.txpower < 0 or self.txpower > 17): + self.log("Invalid TX power configured for "+str(self), RNodeInterface.LOG_ERROR) + self.validcfg = False + + if (self.bandwidth < 7800 or self.bandwidth > 500000): + self.log("Invalid bandwidth configured for "+str(self), RNodeInterface.LOG_ERROR) + self.validcfg = False + + if (self.sf < 7 or self.sf > 12): + self.log("Invalid spreading factor configured for "+str(self), RNodeInterface.LOG_ERROR) + self.validcfg = False + + if (self.cr < 5 or self.cr > 8): + self.log("Invalid coding rate configured for "+str(self), RNodeInterface.LOG_ERROR) + self.validcfg = False + + if id_interval != None and id_callsign != None: + if (len(id_callsign.encode("utf-8")) <= RNodeInterface.CALLSIGN_MAX_LEN): + self.should_id = True + self.id_callsign = id_callsign + self.id_interval = id_interval + else: + self.log("The encoded ID callsign for "+str(self)+" exceeds the max length of "+str(RNodeInterface.CALLSIGN_MAX_LEN)+" bytes.", RNodeInterface.LOG_ERROR) + self.validcfg = False + else: + self.id_interval = None + self.id_callsign = None + + if (not self.validcfg): + raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline") + + try: + self.log("Opening serial port "+self.port+"...") + self.serial = serial.Serial( + port = self.port, + baudrate = self.speed, + bytesize = self.databits, + parity = self.parity, + stopbits = self.stopbits, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False, + ) + except Exception as e: + self.log("Could not open serial port for interface "+str(self), RNodeInterface.LOG_ERROR) + raise e + + if self.serial.is_open: + sleep(2.0) + thread = threading.Thread(target=self.readLoop) + thread.setDaemon(True) + thread.start() + self.online = True + self.log("Serial port "+self.port+" is now open") + self.log("Configuring RNode interface...", RNodeInterface.LOG_VERBOSE) + self.initRadio() + if (self.validateRadioState()): + self.interface_ready = True + self.log(str(self)+" is configured and powered up") + sleep(1.0) + else: + self.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNodeInterface.LOG_ERROR) + self.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNodeInterface.LOG_ERROR) + self.log("Aborting RNode startup", RNodeInterface.LOG_ERROR) + self.serial.close() + raise IOError("RNode interface did not pass validation") + else: + raise IOError("Could not open serial port") + + def log(self, message, level): + pass + + def initRadio(self): + self.setFrequency() + self.setBandwidth() + self.setTXPower() + self.setSpreadingFactor() + self.setCodingRate() + self.setRadioState(KISS.RADIO_STATE_ON) + + def setFrequency(self): + c1 = self.frequency >> 24 + c2 = self.frequency >> 16 & 0xFF + c3 = self.frequency >> 8 & 0xFF + c4 = self.frequency & 0xFF + data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) + + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FREQUENCY])+data+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring frequency for "+self(str)) + + def setBandwidth(self): + c1 = self.bandwidth >> 24 + c2 = self.bandwidth >> 16 & 0xFF + c3 = self.bandwidth >> 8 & 0xFF + c4 = self.bandwidth & 0xFF + data = KISS.escape(bytes([c1])+bytes([c2])+bytes([c3])+bytes([c4])) + + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_BANDWIDTH])+data+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring bandwidth for "+self(str)) + + def setTXPower(self): + txp = bytes([self.txpower]) + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_TXPOWER])+txp+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring TX power for "+self(str)) + + def setSpreadingFactor(self): + sf = bytes([self.sf]) + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_SF])+sf+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring spreading factor for "+self(str)) + + def setCodingRate(self): + cr = bytes([self.cr]) + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_CR])+cr+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring coding rate for "+self(str)) + + def setRadioState(self, state): + kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND]) + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring radio state for "+self(str)) + + def validateRadioState(self): + self.log("Validating radio configuration for "+str(self)+"...", RNodeInterface.LOG_VERBOSE) + sleep(0.25); + if (self.frequency != self.r_frequency): + self.log("Frequency mismatch", RNodeInterface.LOG_ERROR) + self.validcfg = False + if (self.bandwidth != self.r_bandwidth): + self.log("Bandwidth mismatch", RNodeInterface.LOG_ERROR) + self.validcfg = False + if (self.txpower != self.r_txpower): + self.log("TX power mismatch", RNodeInterface.LOG_ERROR) + self.validcfg = False + if (self.sf != self.r_sf): + self.log("Spreading factor mismatch", RNodeInterface.LOG_ERROR) + self.validcfg = False + + if (self.validcfg): + return True + else: + return False + + def setPromiscuousMode(self, state): + if state == True: + kiss_command = bytes([KISS.FEND,KISS.CMD_PROMISC, 0x01, KISS.FEND]) + else: + kiss_command = bytes([KISS.FEND,KISS.CMD_PROMISC, 0x00, KISS.FEND]) + + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("An IO error occurred while configuring promiscuous mode for "+self(str)) + + + def updateBitrate(self): + try: + self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000 + self.bitrate_kbps = round(self.bitrate/1000.0, 2) + self.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNodeInterface.LOG_DEBUG) + except: + self.bitrate = 0 + + def processIncoming(self, data): + self.callback(data, self) + + def send(self, data): + self.processOutgoing(data) + + def processOutgoing(self,data): + if self.online: + if self.interface_ready: + if self.flow_control: + self.interface_ready = False + + frame = b"" + + if self.id_interval != None and self.id_callsign != None: + if self.last_id + self.id_interval < time.time(): + self.last_id = time.time() + frame = bytes([0xc0])+bytes([0x00])+KISS.escape(self.id_callsign.encode("utf-8"))+bytes([0xc0]) + + data = KISS.escape(data) + frame += bytes([0xc0])+bytes([0x00])+data+bytes([0xc0]) + written = self.serial.write(frame) + + if written != len(frame): + raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) + else: + self.queue(data) + + def queue(self, data): + self.packet_queue.append(data) + + def process_queue(self): + if len(self.packet_queue) > 0: + data = self.packet_queue.pop(0) + self.interface_ready = True + self.processOutgoing(data) + elif len(self.packet_queue) == 0: + self.interface_ready = True + + def readLoop(self): + try: + in_frame = False + escape = False + command = KISS.CMD_UNKNOWN + data_buffer = b"" + command_buffer = b"" + last_read_ms = int(time.time()*1000) + + while self.serial.is_open: + if self.serial.in_waiting: + byte = ord(self.serial.read(1)) + last_read_ms = int(time.time()*1000) + + if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA): + in_frame = False + self.processIncoming(data_buffer) + data_buffer = b"" + command_buffer = b"" + elif (byte == KISS.FEND): + in_frame = True + command = KISS.CMD_UNKNOWN + data_buffer = b"" + command_buffer = b"" + elif (in_frame and len(data_buffer) < RNodeInterface.MTU): + if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): + command = byte + elif (command == KISS.CMD_DATA): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + escape = False + data_buffer = data_buffer+bytes([byte]) + elif (command == KISS.CMD_FREQUENCY): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + escape = False + command_buffer = command_buffer+bytes([byte]) + if (len(command_buffer) == 4): + self.r_frequency = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] + self.log(str(self)+" Radio reporting frequency is "+str(self.r_frequency/1000000.0)+" MHz", RNodeInterface.LOG_DEBUG) + self.updateBitrate() + + elif (command == KISS.CMD_BANDWIDTH): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + escape = False + command_buffer = command_buffer+bytes([byte]) + if (len(command_buffer) == 4): + self.r_bandwidth = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3] + self.log(str(self)+" Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz", RNodeInterface.LOG_DEBUG) + self.updateBitrate() + + elif (command == KISS.CMD_TXPOWER): + self.r_txpower = byte + self.log(str(self)+" Radio reporting TX power is "+str(self.r_txpower)+" dBm", RNodeInterface.LOG_DEBUG) + elif (command == KISS.CMD_SF): + self.r_sf = byte + self.log(str(self)+" Radio reporting spreading factor is "+str(self.r_sf), RNodeInterface.LOG_DEBUG) + self.updateBitrate() + elif (command == KISS.CMD_CR): + self.r_cr = byte + self.log(str(self)+" Radio reporting coding rate is "+str(self.r_cr), RNodeInterface.LOG_DEBUG) + self.updateBitrate() + elif (command == KISS.CMD_RADIO_STATE): + self.r_state = byte + elif (command == KISS.CMD_RADIO_LOCK): + self.r_lock = byte + elif (command == KISS.CMD_STAT_RX): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + escape = False + command_buffer = command_buffer+bytes([byte]) + if (len(command_buffer) == 4): + self.r_stat_rx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) + + elif (command == KISS.CMD_STAT_TX): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + escape = False + command_buffer = command_buffer+bytes([byte]) + if (len(command_buffer) == 4): + self.r_stat_tx = ord(command_buffer[0]) << 24 | ord(command_buffer[1]) << 16 | ord(command_buffer[2]) << 8 | ord(command_buffer[3]) + + elif (command == KISS.CMD_STAT_RSSI): + self.r_stat_rssi = byte-RNodeInterface.RSSI_OFFSET + elif (command == KISS.CMD_STAT_SNR): + self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25 + elif (command == KISS.CMD_RANDOM): + self.r_random = byte + elif (command == KISS.CMD_ERROR): + if (byte == KISS.ERROR_INITRADIO): + self.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR) + elif (byte == KISS.ERROR_INITRADIO): + self.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR) + else: + self.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNodeInterface.LOG_ERROR) + elif (command == KISS.CMD_READY): + self.process_queue() + + else: + time_since_last = int(time.time()*1000) - last_read_ms + if len(data_buffer) > 0 and time_since_last > self.timeout: + self.log(str(self)+" serial read timeout", RNodeInterface.LOG_DEBUG) + data_buffer = b"" + in_frame = False + command = KISS.CMD_UNKNOWN + escape = False + sleep(0.08) + + except Exception as e: + self.online = False + self.log("A serial port error occurred, the contained exception was: "+str(e), RNodeInterface.LOG_ERROR) + self.log("The interface "+str(self.name)+" is now offline.", RNodeInterface.LOG_ERROR) + + def log(self, msg, level=3): + logtimefmt = "%Y-%m-%d %H:%M:%S" + if self.loglevel >= level: + timestamp = time.time() + logstring = "["+time.strftime(logtimefmt)+"] ["+self.loglevelname(level)+"] "+msg + print(logstring) + + def loglevelname(self, level): + if (level == RNodeInterface.LOG_CRITICAL): + return "Critical" + if (level == RNodeInterface.LOG_ERROR): + return "Error" + if (level == RNodeInterface.LOG_WARNING): + return "Warning" + if (level == RNodeInterface.LOG_NOTICE): + return "Notice" + if (level == RNodeInterface.LOG_INFO): + return "Info" + if (level == RNodeInterface.LOG_VERBOSE): + return "Verbose" + if (level == RNodeInterface.LOG_DEBUG): + return "Debug" + if (level == RNodeInterface.LOG_EXTREME): + return "Extra" + + def hexrep(data, delimit=True): + delimiter = ":" + if not delimit: + delimiter = "" + hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data) + return hexrep + + def __str__(self): + return "RNodeInterface["+self.name+"]" + diff --git a/vendor/rnode_firmware/README.md b/vendor/rnode_firmware/README.md new file mode 100644 index 0000000..3cd0d0a --- /dev/null +++ b/vendor/rnode_firmware/README.md @@ -0,0 +1,150 @@ +*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 + +This is the open firmware that powers RNode devices. + +An RNode is an open, free and unrestricted digital radio transceiver. It enables anyone to send and receive any kind of data over both short and very long distances. RNodes can be used with many different kinds of programs and systems, but they are especially well suited for use with [Reticulum](https://reticulum.network). + +RNode is not a product, and not any *one* specific device in particular. It is a system that is easy to replicate across space and time, that produces highly functional communications tools, which respects user autonomy and empowers individuals and communities to protect their sovereignty, privacy and ability to communicate and exchange data and ideas freely. + + + +
An RNode made from readily available and cheap parts, in a durable 3D printed case


+ +The RNode system is primarily software, which *transforms* different kinds of available hardware devices into functional, physical RNodes, which can then be used to solve a wide range of communications tasks. Such RNodes can be modified and built to suit the specific time, locale and environment they need to exist in. + +## Latest Release + +The latest release, installable through `rnodeconf`, is version `1.82`. You must have at least version `2.4.1` of `rnodeconf` installed to update the RNode Firmware to version `1.82`. Get it by updating the `rns` package to at least version `0.9.4`. + +## A Self-Replicating System + +If you notice the presence of a circularity in the naming of the system as a whole, and the physical devices, it is no coincidence. Every RNode contains the seeds necessary to reproduce the system, the [RNode Bootstrap Console](https://unsigned.io/rnode_bootstrap_console), which is hosted locally on every RNode, and can be activated and accesses at any time - no Internet required. + +The designs, guides and software stored within allows users to create more RNodes, and even to bootstrap entire communications networks, completely independently of existing infrastructure, or in situations where infrastructure has become unreliable or is broken. + + + +
Where there is no Internet, RNodes will still communicate


+ +The production of one particular RNode device is not an end, but the potential starting point of a new branch of devices on the tree of the RNode system as a whole. + +This tree fits into the larger biome of Free & Open Communications Systems, which I hope that you - by using communications tools like RNode - will help grow and prosper. + +## One Tool, Many Uses + +The RNode design is meant to flexible and hackable. At it's core, it is a low-power, but extremely long-range digital radio transceiver. Coupled with Reticulum, it provides encrypted and secure communications. + +Depending on configuration, it can be used for local networking purposes, or to send data over very long distances. Once you have an RNode, there is a wide variety of possible uses: + +- As a network adapter for [Reticulum](https://reticulum.network) +- Messaging using [Sideband](https://unsigned.io/software/Sideband.html) +- Information sharing and communication using [Nomad Network](https://unsigned.io/software/Nomad_Network.html) +- LoRa-based [KISS-compatible amateur radio TNC](https://unsigned.io/guides/2020_05_03_using_rnodes_with_amateur_radio_software.html) +- LoRa development platform +- [Packet sniffer](https://unsigned.io/software/LoRaMon.html) for LoRa networks +- Long range [Ethernet and IP network interface](https://unsigned.io/guides/2020_05_27_ethernet-and-ip-over-packet-radio-tncs.html) on Linux +- As a general-purpose long-range data radio + +## Types & Performance + +RNodes can be made in many different configurations, and can use many different radio bands, but they will generally operate in the **433 MHz**, **868 MHz**, **915 MHZ** and **2.4 GHz** bands. They will usually offer configurable on-air data speeds between just a **few hundred bits per second**, up to **a couple of megabits per second**. Maximum output power will depend on the transceiver and PA setup used, but will generally lie between **17 dbm** and **27 dBm**. + +The RNode system has been designed to allow reliable systems for basic human communications, over very wide areas, while using very little power, being cheap to build, free to operate, and near impossible to censor. + +While **speeds are lower** than WiFi, typical communication **ranges are many times higher**. Several kilometers can be acheived with usable bitrates, even in urban areas, and over **100 kilometers** can be achieved in line-of-sight conditions. + +## Supported Boards & Devices +It's easy to create your own RNodes from one of the supported development boards and devices. If a device or board you want to use is not yet supported, you are welcome to join the effort and help creating a board definition and pin mapping for it! + + + +The RNode Firmware supports the following boards: + +- LilyGO T-Beam v1.1 devices with SX1276/8 LoRa chips +- LilyGO T-Beam v1.1 devices with SX1262/8 LoRa chips +- LilyGO T-Beam Supreme devices +- LilyGO T-Deck devices (currently display is disabled) +- LilyGO LoRa32 v1.0 devices +- LilyGO LoRa32 v2.0 devices +- LilyGO LoRa32 v2.1 devices (with and without TCXO) +- LilyGO T3S3 devices with SX1276/8 LoRa chips +- LilyGO T3S3 devices with SX1262/8 LoRa chips +- LilyGO T3S3 devices with SX1280 LoRa chips +- 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) +- Homebrew RNodes based on ATmega1284p boards +- Homebrew RNodes based on ATmega2560 boards +- Homebrew RNodes based on Adafruit Feather ESP32 boards +- Homebrew RNodes based on generic ESP32 boards + +## Supported Transceiver Modules +The RNode Firmware supports all transceiver modules based on Semtech **SX1276**, **SX1278**, **SX1262**, **SX1268** and **SX1280** chips, that have an **SPI interface** and expose the relevant **DIO** interrupt pins from the chip. + +## Getting Started Fast +You can download and flash the firmware to all the supported boards using the [RNode Config Utility](https://github.com/markqvist/rnodeconfigutil). All firmware releases are now handled and installed directly through the `rnodeconf` utility, which is included 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 +``` + +For most of the supported device types, it is also possible to use [Liam Cottle's Web-based RNode Flasher](https://liamcottle.github.io/rnode-flasher/). This option may be easier if you're not familiar with using a command line interface. + +For more detailed instruction and in-depth guides, you can have a look at some of these resources: + +- Create a [basic RNode from readily available development boards](https://unsigned.io/guides/2022_01_25_installing-rnode-firmware-on-supported-devices.html) +- Follow a complete build recipe for [making a handheld RNode](https://unsigned.io/guides/2023_01_14_Making_A_Handheld_RNode.html), like the one pictured above +- Learn the basics on how to [create and build your own RNode designs](https://unsigned.io/guides/2022_01_26_how-to-make-your-own-rnodes.html) from scratch +- Once you've got the hang of it, start building RNodes for your community, or [even for selling them](https://unsigned.io/sell_rnodes.html) + +If you would rather just buy a pre-made unit, you can visit one of the community vendors that produce and sell RNodes: + +- [Liberated Embedded Systems](https://store.liberatedsystems.co.uk/) +- [Simply Equipped](https://simplyequipped.com/) + +If you'd like to have your shop added to this list, let me know. + +## Support RNode Development +You can help support the continued development of open, free and private communications systems by donating via one of the following channels: + +- Monero: + ``` + 84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w + ``` +- Bitcoin + ``` + bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6 + ``` +- Ethereum + ``` + 0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E + ``` +- Liberapay: https://liberapay.com/Reticulum/ + +- Ko-Fi: https://ko-fi.com/markqvist + + +## License & Use +The RNode Firmware is Copyright © 2024 Mark Qvist / [unsigned.io](https://unsigned.io), and is made available under the **GNU General Public License v3.0**. The source code includes an SX1276 driver that is released under MIT License, and Copyright © 2018 Sandeep Mistry / Mark Qvist. + +You can obtain the source code from [git.unsigned.io](https://git.unsigned.io/markqvist/RNode_Firmware) or [GitHub](https://github.com/markqvist/rnode_firmware). + +Every RNode also includes an internal copy of it's own firmware source code, that can be downloaded through the [RNode Bootstrap Console](https://unsigned.io/rnode_bootstrap_console), by putting the RNode into Console Mode (which can be activated by pressing the reset button two times within two seconds). + +The RNode Ecosystem is free and non-proprietary, and actively seeks to distribute it's ownership and control. If you want to build RNodes for commercial purposes, including selling them, you must do so adhering to the Open Source licenses that the various parts of the RNode project is released under, and under your own responsibility. + +If you distribute or modify this work, you **must** adhere to the terms of the GPLv3, including, but not limited to, providing up-to-date source code upon distribution, displaying appropriate copyright and license notices in prominent positions of all conveyed works, and making users aware of their rights to the software under the GPLv3. + +In practice, this means that you can use the firmware commercially, but you must understand your obligation to provide all future users of the system with the same rights, that you have been provided by the GPLv3. If you intend using the RNode Firmware commercially, it is worth reading [this page](https://unsigned.io/sell_rnodes.html). diff --git a/vendor/rnode_firmware/README_CARDPUTER_ADV.md b/vendor/rnode_firmware/README_CARDPUTER_ADV.md new file mode 100644 index 0000000..97f5c99 --- /dev/null +++ b/vendor/rnode_firmware/README_CARDPUTER_ADV.md @@ -0,0 +1,151 @@ +# Cardputer Adv RNode Firmware + +This repo is a local Cardputer Adv firmware port based on upstream +`markqvist/RNode_Firmware` tag `1.86`, not the RNode Firmware CE fork. + +It targets the M5Stack Cardputer Adv with the M5Stack Cap LoRa-1262 as a +regular host-controlled RNode. + +## Build + +One-time dependency setup: + +```sh +make prep-cardputer_adv +``` + +Build the firmware: + +```sh +make firmware-cardputer_adv +``` + +The first-boot radio seed can be overridden at build time without editing +source. For example: + +```sh +make firmware-cardputer_adv CARDPUTER_ADV_DEFAULT_FREQ=868000000UL +``` + +The build target stages a temporary Arduino sketch under `build/` because +Arduino CLI requires the sketch folder name to match `RNode_Firmware.ino`. +Compiled artifacts are emitted to: + +```text +build/esp32.esp32.esp32s3/ +``` + +Create a release zip: + +```sh +make release-cardputer_adv +``` + +## Flash + +With the Cardputer connected in bootloader mode: + +```sh +make upload-cardputer_adv port=/dev/ttyACM0 +``` + +On macOS the port is usually `/dev/cu.usbmodem*`. + +The upload target writes the ESP32-S3 image and then writes the expected RNode +firmware hash over KISS. This avoids the stock firmware-corrupt screen after a +successful flash. + +If firmware is already flashed and the display only needs its hash repaired: + +```sh +make set-running-hash-cardputer_adv port=/dev/ttyACM0 +``` + +That reads the running app hash from the device and writes it back as the trusted +target hash. To write the hash for a freshly built local binary instead: + +```sh +make set-hash-cardputer_adv port=/dev/ttyACM0 +``` + +## Host Connection + +Cardputer Adv boots as a host-controlled RNode. First-boot provisioning writes a +valid EEPROM identity and a default radio profile, but the SX1262 remains idle +until a USB or BLE host sends the normal RNode radio-on command. + +- USB CDC serial is enabled by the Cardputer Adv build flags and carries the + normal RNode KISS protocol. +- BLE is enabled by default. Hold `p` or Enter/OK for three seconds to enter + pairing mode, or hold `b` for three seconds to toggle BLE on or off. Pairing + mode times out after 30 seconds if no client connects. +- The display's LoRa icon is dim while the radio is idle. The waterfall only + updates after host software starts the radio. + +## Hardware Map + +- ESP32-S3, 8 MB flash +- SX1262 LoRa radio +- NSS: `GPIO5` +- IRQ/DIO1: `GPIO4` +- RST: `GPIO3` +- BUSY: `GPIO6` +- SCK: `GPIO40` +- MOSI: `GPIO14` +- MISO: `GPIO39` +- TCXO: DIO3-controlled, 3.0 V +- DIO2: RF switch control +- Cap LoRa-1262 antenna path: PI4IOE5V6408 I2C expander `0x43`, P0 driven high +- Radio SPI: dedicated ESP32-S3 FSPI bus at 8 MHz + +SD is intentionally disabled for this dumb RNode target. It shares the LoRa SPI +bus and is not needed for host-controlled operation. + +## Provisioning Note + +The firmware self-provisions Cardputer Adv EEPROM on first boot if the hardware +identity block is missing or invalid. It writes: + +- product `PRODUCT_CARDPUTER_ADV=0xEC` +- model `MODEL_EC=0xEC` +- hardware revision `0x01` +- a serial derived from the ESP32-S3 efuse MAC +- the required RNode info checksum and lock byte + +If no radio config exists, it also writes a default US 915 MHz profile: + +- frequency `915000000` +- bandwidth `250000` +- spreading factor `11` +- coding rate `5` +- transmit power `14` + +Host clients can overwrite the radio config later through normal RNode KISS +commands; once a config is present, the firmware does not replace it on later +boots. The saved profile is not started automatically on Cardputer Adv; it is +used when host software starts the radio without first sending replacement +settings. The Cardputer Adv target is for the 850-950 MHz SX1262 LoRa cap, so 868 +MHz and 915 MHz are valid software profiles for the supported hardware. 433 MHz +requires a 433 MHz radio module and should be handled as a separate hardware +target. Upstream `rnodeconf` does not currently have friendly metadata for +product/model `0xEC`, so first-class autoinstall and friendly `rnodeconf -i` +output still require matching tooling changes outside this firmware repo. + +## Firmware Hash + +RNode firmware validates the running app partition against a SHA-256 hash stored +in EEPROM. The Cardputer Adv build self-stores the running app hash if the +stored target hash does not match, so a fresh flash can recover from the stock +firmware-corrupt state without upstream `rnodeconf` support. + +`tools/cardputer_adv_firmware_hash.py` also implements the minimal KISS commands +needed by this repo: + +- compute the ESP32 app hash from `RNode_Firmware.ino.bin` +- write `CMD_FW_HASH` +- optionally read the device's current running hash and trust it +- optionally reset the device after the write + +It mirrors the hash calculation used by `rnodeconf` and the Ratspeak web +flasher: if an ESP32 binary ends with a matching SHA-256 trailer, that trailer is +the partition hash; otherwise the whole file is hashed. diff --git a/vendor/rnode_firmware/RNode_Firmware.ino b/vendor/rnode_firmware/RNode_Firmware.ino new file mode 100644 index 0000000..fa73591 --- /dev/null +++ b/vendor/rnode_firmware/RNode_Firmware.ino @@ -0,0 +1,2005 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include "Utilities.h" +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + #include "RsCardputerModeSwitch.h" +#elif BOARD_MODEL == BOARD_TDECK + #include "RsDeckModeSwitch.h" +#endif + +FIFOBuffer serialFIFO; +uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1]; + +FIFOBuffer16 packet_starts; +uint16_t packet_starts_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +FIFOBuffer16 packet_lengths; +uint16_t packet_lengths_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +uint8_t packet_queue[CONFIG_QUEUE_SIZE]; + +volatile uint8_t queue_height = 0; +volatile uint16_t queued_bytes = 0; +volatile uint16_t queue_cursor = 0; +volatile uint16_t current_packet_start = 0; +volatile bool serial_buffering = false; +#if HAS_BLUETOOTH || HAS_BLE == true + bool bt_init_ran = false; +#endif + +#if HAS_CONSOLE + #include "Console.h" +#endif + +#if PLATFORM == PLATFORM_ESP32 || PLATFORM == PLATFORM_NRF52 + #define MODEM_QUEUE_SIZE 8 + typedef struct { + size_t len; + int rssi; + int snr_raw; + uint8_t data[]; + } modem_packet_t; + static xQueueHandle modem_packet_queue = NULL; +#endif + +char sbuf[128]; + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + bool packet_ready = false; +#endif + +void setup() { + #if MCU_VARIANT == MCU_ESP32 + boot_seq(); + EEPROM.begin(EEPROM_SIZE); + Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE); + + #if BOARD_MODEL == BOARD_TDECK + pinMode(pin_poweron, OUTPUT); + digitalWrite(pin_poweron, HIGH); + + pinMode(SD_CS, OUTPUT); + pinMode(DISPLAY_CS, OUTPUT); + digitalWrite(SD_CS, HIGH); + digitalWrite(DISPLAY_CS, HIGH); + + pinMode(DISPLAY_BL_PIN, OUTPUT); + #endif + #endif + + #if MCU_VARIANT == MCU_NRF52 + #if BOARD_MODEL == BOARD_TECHO + delay(200); + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, HIGH); + pinMode(pin_btn_usr1, INPUT_PULLUP); + pinMode(pin_btn_touch, INPUT_PULLUP); + pinMode(PIN_LED_RED, OUTPUT); + pinMode(PIN_LED_GREEN, OUTPUT); + pinMode(PIN_LED_BLUE, OUTPUT); + delay(200); + #endif + + if (!eeprom_begin()) { Serial.write("EEPROM initialisation failed.\r\n"); } + #endif + + // Seed the PRNG for CSMA R-value selection + #if MCU_VARIANT == MCU_ESP32 + // On ESP32, get the seed value from the + // hardware RNG + unsigned long seed_val = (unsigned long)esp_random(); + #elif MCU_VARIANT == MCU_NRF52 + // On nRF, get the seed value from the + // hardware RNG + unsigned long seed_val = get_rng_seed(); + #else + // Otherwise, get a pseudo-random seed + // value from an unconnected analog pin + // + // CAUTION! If you are implementing the + // firmware on a platform that does not + // have a hardware RNG, you MUST take + // care to get a seed value with enough + // entropy at each device reset! + unsigned long seed_val = analogRead(0); + #endif + randomSeed(seed_val); + + // Initialise serial communication + memset(serialBuffer, 0, sizeof(serialBuffer)); + fifo_init(&serialFIFO, serialBuffer, CONFIG_UART_BUFFER_SIZE); + + Serial.begin(serial_baudrate); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + rs_cardputer_adv::return_to_launcher_next_boot(); + #elif BOARD_MODEL == BOARD_TDECK + rs_deck::return_to_launcher_next_boot(); + #endif + + #if HAS_NP + led_init(); + #endif + + #if MCU_VARIANT == MCU_NRF52 && HAS_NP == true + 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 && BOARD_MODEL != BOARD_CARDPUTER_ADV && BOARD_MODEL != BOARD_TDECK + // 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 + // is disabled on this platform. + while (!Serial); + #endif + + serial_interrupt_init(); + + // Configure input and output pins + #if HAS_INPUT + input_init(); + #endif + + #if HAS_NP == false + if (pin_led_rx >= 0) pinMode(pin_led_rx, OUTPUT); + if (pin_led_tx >= 0) pinMode(pin_led_tx, OUTPUT); + #endif + + #if HAS_TCXO == true + if (pin_tcxo_enable != -1) { + pinMode(pin_tcxo_enable, OUTPUT); + digitalWrite(pin_tcxo_enable, HIGH); + } + #endif + + // Initialise buffers + memset(pbuf, 0, sizeof(pbuf)); + memset(cmdbuf, 0, sizeof(cmdbuf)); + + memset(packet_queue, 0, sizeof(packet_queue)); + + memset(packet_starts_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_starts, packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH); + + memset(packet_lengths_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_lengths, packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH); + + #if PLATFORM == PLATFORM_ESP32 || PLATFORM == PLATFORM_NRF52 + modem_packet_queue = xQueueCreate(MODEM_QUEUE_SIZE, sizeof(modem_packet_t*)); + #endif + + // Set chip select, reset and interrupt + // pins for the LoRa module + #if MODEM == SX1276 || MODEM == SX1278 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy); + #elif MODEM == SX1262 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen); + #elif MODEM == SX1280 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + init_channel_stats(); + + #if BOARD_MODEL == BOARD_T3S3 + #if MODEM == SX1280 + delay(300); + LoRa->reset(); + delay(100); + #endif + #endif + + #if BOARD_MODEL == BOARD_XIAO_S3 + // Improve wakeup from sleep + delay(300); + LoRa->reset(); + delay(100); + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + // Cardputer Adv is host-controlled and leaves RF idle at boot. Avoid + // probing the SX1262 before the M5 display/BLE stack is fully running. + modem_installed = true; + #else + // Check installed transceiver chip and + // probe boot parameters. + if (LoRa->preInit()) { + modem_installed = true; + + #if HAS_INPUT + // Skip quick-reset console activation + #else + uint32_t lfr = LoRa->getFrequency(); + if (lfr == 0) { + // Normal boot + } else if (lfr == M_FRQ_R) { + // Quick reboot + #if HAS_CONSOLE + if (rtc_get_reset_reason(0) == POWERON_RESET) { + console_active = true; + } + #endif + } else { + // Unknown boot + } + LoRa->setFrequency(M_FRQ_S); + #endif + + } else { + modem_installed = false; + } + #endif + #else + // Older variants only came with SX1276/78 chips, + // so assume that to be the case for now. + modem_installed = true; + #endif + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK + cardputer_adv_self_provision(); + #endif + + #if HAS_DISPLAY + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #endif + eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE); + #if BOARD_MODEL == BOARD_TECHO + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0x03); + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + eeprom_update(eeprom_addr(ADDR_CONF_DINT), CARDPUTER_ADV_DISPLAY_INTENSITY_DEFAULT); + #else + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF); + #endif + } + #if BOARD_MODEL == BOARD_TECHO + display_add_callback(work_while_waiting); + #endif + + display_unblank(); + disp_ready = display_init(); + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_PMU == true + if (!pmu_ready) pmu_ready = init_pmu(); + #endif + #endif + update_display(); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_PMU == true + if (!pmu_ready) pmu_ready = init_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + bt_init(); + bt_init_ran = true; + #if (BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK) && HAS_BLE == true + if (!console_active && bt_ready && bt_enabled && bt_state != BT_STATE_OFF && bt_bond_count() == 0) { + bt_enable_pairing_on_request(); + } + #endif + #endif + + if (console_active) { + #if HAS_CONSOLE + console_start(); + #else + kiss_indicate_reset(); + #endif + } else { + #if HAS_WIFI + wifi_mode = EEPROM.read(eeprom_addr(ADDR_CONF_WIFI)); + if (wifi_mode == WR_WIFI_STA || wifi_mode == WR_WIFI_AP) { wifi_remote_init(); } + #endif + kiss_indicate_reset(); + } + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if MODEM == SX1280 + avoid_interference = false; + #else + #if HAS_EEPROM + uint8_t ia_conf = EEPROM.read(eeprom_addr(ADDR_CONF_DIA)); + if (ia_conf == 0x00) { avoid_interference = true; } + else { avoid_interference = false; } + #elif MCU_VARIANT == MCU_NRF52 + uint8_t ia_conf = eeprom_read(eeprom_addr(ADDR_CONF_DIA)); + if (ia_conf == 0x00) { avoid_interference = true; } + else { avoid_interference = false; } + #endif + #endif + #endif + + // Validate board health, EEPROM and config + validate_status(); + + #if BOARD_MODEL != BOARD_CARDPUTER_ADV + if (op_mode != MODE_TNC) LoRa->setFrequency(0); + #endif +} + +void lora_receive() { + if (!implicit) { + LoRa->receive(); + } else { + LoRa->receive(implicit_l); + } +} + +inline void kiss_write_packet() { + serial_write(FEND); + serial_write(CMD_DATA); + + for (uint16_t i = 0; i < host_write_len; i++) { + #if MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); + uint8_t byte = pbuf[i]; + portEXIT_CRITICAL(); + #else + uint8_t byte = pbuf[i]; + #endif + + if (byte == FEND) { serial_write(FESC); byte = TFEND; } + if (byte == FESC) { serial_write(FESC); byte = TFESC; } + serial_write(byte); + } + + serial_write(FEND); + host_write_len = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + packet_ready = false; + #endif + + #if MCU_VARIANT == MCU_ESP32 + #if HAS_BLE + bt_flush(); + #endif + #endif +} + +inline void getPacketData(uint16_t len) { + #if MCU_VARIANT != MCU_NRF52 + while (len-- && read_len < MTU) { + pbuf[read_len++] = LoRa->read(); + } + #else + BaseType_t int_mask = taskENTER_CRITICAL_FROM_ISR(); + while (len-- && read_len < MTU) { + pbuf[read_len++] = LoRa->read(); + } + taskEXIT_CRITICAL_FROM_ISR(int_mask); + #endif +} + +void ISR_VECT receive_callback(int packet_size) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + BaseType_t int_mask; + #endif + + if (!promisc) { + // The standard operating mode allows large + // packets with a payload up to 500 bytes, + // by combining two raw LoRa packets. + // We read the 1-byte header and extract + // packet sequence number and split flags + uint8_t header = LoRa->read(); packet_size--; + uint8_t sequence = packetSequence(header); + bool ready = false; + + if (isSplitPacket(header) && seq == SEQ_UNSET) { + // This is the first part of a split + // packet, so we set the seq variable + // and add the data to the buffer + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (isSplitPacket(header) && seq == sequence) { + // This is the second part of a split + // packet, so we add it to the buffer + // and set the ready flag. + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = (last_rssi+LoRa->packetRssi())/2; + last_snr_raw = (last_snr_raw+LoRa->packetSnrRaw())/2; + #endif + + getPacketData(packet_size); + seq = SEQ_UNSET; + ready = true; + + } else if (isSplitPacket(header) && seq != sequence) { + // This split packet does not carry the + // same sequence id, so we must assume + // that we are seeing the first part of + // a new split packet. + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (!isSplitPacket(header)) { + // This is not a split packet, so we + // just read it and set the ready + // flag to true. + + if (seq != SEQ_UNSET) { + // If we already had part of a split + // packet in the buffer, we clear it. + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + seq = SEQ_UNSET; + } + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + ready = true; + } + + if (ready) { + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + // We first signal the RSSI of the + // recieved packet to the host. + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + host_write_len = read_len; + kiss_write_packet(); read_len = 0; + + #else + // Allocate packet struct, but abort if there + // is not enough memory available. + modem_packet_t *modem_packet = (modem_packet_t*)malloc(sizeof(modem_packet_t) + read_len); + if(!modem_packet) { memory_low = true; return; } + + // Get packet RSSI and SNR + #if MCU_VARIANT == MCU_ESP32 + modem_packet->snr_raw = LoRa->packetSnrRaw(); + modem_packet->rssi = LoRa->packetRssi(modem_packet->snr_raw); + #endif + + // Send packet to event queue, but free the + // allocated memory again if the queue is + // unable to receive the packet. + modem_packet->len = read_len; + memcpy(modem_packet->data, pbuf, read_len); read_len = 0; + if (!modem_packet_queue || xQueueSendFromISR(modem_packet_queue, &modem_packet, NULL) != pdPASS) { + free(modem_packet); + } + #endif + } + } else { + // In promiscuous mode, raw packets are + // output directly to the host + read_len = 0; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + getPacketData(packet_size); + + // We first signal the RSSI of the + // recieved packet to the host. + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + kiss_write_packet(); + + #else + getPacketData(packet_size); + packet_ready = true; + #endif + } +} + +bool startRadio() { + update_radio_lock(); + if (!radio_online && !console_active) { + if (!radio_locked && hw_ready) { + if (!LoRa->begin(lora_freq)) { + // The radio could not be started. + // Indicate this failure over both the + // serial port and with the onboard LEDs + radio_error = true; + kiss_indicate_error(ERROR_INITRADIO); + led_indicate_error(0); + return false; + } else { + radio_online = true; + + init_channel_stats(); + + setTXPower(); + setBandwidth(); + setSpreadingFactor(); + setCodingRate(); + getFrequency(); + + LoRa->enableCrc(); + LoRa->onReceive(receive_callback); + lora_receive(); + + // Flash an info pattern to indicate + // that the radio is now on + kiss_indicate_radiostate(); + led_indicate_info(3); + return true; + } + + } else { + // Flash a warning pattern to indicate + // that the radio was locked, and thus + // not started + radio_online = false; + kiss_indicate_radiostate(); + led_indicate_warning(3); + return false; + } + } else { + // If radio is already on, we silently + // ignore the request. + kiss_indicate_radiostate(); + return true; + } +} + +void stopRadio() { + LoRa->end(); + radio_online = false; +} + +void handleModemTimeout() { + kiss_indicate_error(ERROR_MODEM_TIMEOUT); + kiss_indicate_error(ERROR_TXFAILED); + led_indicate_error(5); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + stopRadio(); + delay(50); + startRadio(); + #else + hard_reset(); + #endif +} + +void update_radio_lock() { + if (lora_freq != 0 && lora_bw != 0 && lora_txp != 0xFF && lora_sf != 0) { + radio_locked = false; + } else { + radio_locked = true; + } +} + +bool queue_full() { return (queue_height >= CONFIG_QUEUE_MAX_LENGTH || queued_bytes >= CONFIG_QUEUE_SIZE); } + +volatile bool queue_flushing = false; +void flush_queue(void) { + if (!queue_flushing) { + queue_flushing = true; + led_tx_on(); + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + while (!fifo16_isempty(&packet_starts)) { + #else + while (!fifo16_isempty_locked(&packet_starts)) { + #endif + + uint16_t start = fifo16_pop(&packet_starts); + uint16_t length = fifo16_pop(&packet_lengths); + + if (length >= MIN_L && length <= MTU) { + for (uint16_t i = 0; i < length; i++) { + uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; + tbuf[i] = packet_queue[pos]; + } + + transmit(length); + } + } + + lora_receive(); led_tx_off(); + } + + queue_height = 0; + queued_bytes = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_airtime(); + #endif + + queue_flushing = false; + + #if HAS_DISPLAY + display_tx = true; + #endif +} + +void pop_queue() { + if (!queue_flushing) { + queue_flushing = true; led_tx_on(); + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (!fifo16_isempty(&packet_starts)) { + #else + if (!fifo16_isempty_locked(&packet_starts)) { + #endif + + uint16_t start = fifo16_pop(&packet_starts); + uint16_t length = fifo16_pop(&packet_lengths); + if (length >= MIN_L && length <= MTU) { + for (uint16_t i = 0; i < length; i++) { + uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; + tbuf[i] = packet_queue[pos]; + } + + transmit(length); + } + queue_height -= 1; + queued_bytes -= length; + } + + lora_receive(); led_tx_off(); + } + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_airtime(); + #endif + + queue_flushing = false; + + #if HAS_DISPLAY + display_tx = true; + #endif +} + +void add_airtime(uint16_t written) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + float lora_symbols = 0; + float packet_cost_ms = 0.0; + int ldr_opt = 0; if (lora_low_datarate) ldr_opt = 1; + + #if MODEM == SX1276 || MODEM == SX1278 + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + 8 + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*(lora_sf-2*ldr_opt); + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 0.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + + #elif MODEM == SX1262 || MODEM == SX1280 + if (lora_sf < 7) { + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*lora_sf; + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 2.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + + } else { + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + 8 + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*(lora_sf-2*ldr_opt); + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 0.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + } + + #endif + + uint16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + airtime_bins[cb] += packet_cost_ms; + airtime_bins[nb] = 0; + + #endif +} + +void update_airtime() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t cb = current_airtime_bin(); + uint16_t pb = cb-1; if (cb-1 < 0) { pb = AIRTIME_BINS-1; } + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + airtime_bins[nb] = 0; airtime = (float)(airtime_bins[cb]+airtime_bins[pb])/(2.0*AIRTIME_BINLEN_MS); + + uint32_t longterm_airtime_sum = 0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { longterm_airtime_sum += airtime_bins[bin]; } + longterm_airtime = (float)longterm_airtime_sum/(float)AIRTIME_LONGTERM_MS; + + float longterm_channel_util_sum = 0.0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { longterm_channel_util_sum += longterm_bins[bin]; } + longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_csma_parameters(); + #endif + + kiss_indicate_channel_stats(); + #endif +} + +void transmit(uint16_t size) { + if (radio_online) { + if (!promisc) { + uint16_t written = 0; + uint8_t header = random(256) & 0xF0; + if (size > SINGLE_MTU - HEADER_L) { header = header | FLAG_SPLIT; } + + LoRa->beginPacket(); + LoRa->write(header); written++; + + for (uint16_t i=0; i < size; i++) { + LoRa->write(tbuf[i]); written++; + + if (written == 255 && isSplitPacket(header)) { + if (!LoRa->endPacket()) { + handleModemTimeout(); + return; + } + + add_airtime(written); + LoRa->beginPacket(); + LoRa->write(header); + written = 1; + } + } + + if (!LoRa->endPacket()) { + handleModemTimeout(); + return; + } + + add_airtime(written); + + } else { + led_tx_on(); uint16_t written = 0; + if (size > SINGLE_MTU) { size = SINGLE_MTU; } + if (!implicit) { LoRa->beginPacket(); } + else { LoRa->beginPacket(size); } + for (uint16_t i=0; i < size; i++) { LoRa->write(tbuf[i]); written++; } + LoRa->endPacket(); add_airtime(written); + } + + } else { kiss_indicate_error(ERROR_TXFAILED); led_indicate_error(5); } +} + +void serial_callback(uint8_t sbyte) { + if (IN_FRAME && sbyte == FEND && command == CMD_DATA) { + IN_FRAME = false; + + if (!fifo16_isfull(&packet_starts) && queued_bytes < CONFIG_QUEUE_SIZE) { + uint16_t s = current_packet_start; + int16_t e = queue_cursor-1; if (e == -1) e = CONFIG_QUEUE_SIZE-1; + uint16_t l; + + if (s != e) { l = (s < e) ? e - s + 1 : CONFIG_QUEUE_SIZE - s + e + 1; } + else { l = 1; } + + if (l >= MIN_L) { + queue_height++; + fifo16_push(&packet_starts, s); + fifo16_push(&packet_lengths, l); + current_packet_start = queue_cursor; + } + } + + } else if (sbyte == FEND) { + IN_FRAME = true; + command = CMD_UNKNOWN; + frame_len = 0; + } else if (IN_FRAME && frame_len < MTU) { + // Have a look at the command byte first + if (frame_len == 0 && command == CMD_UNKNOWN) { + command = sbyte; + } else if (command == CMD_DATA) { + if (bt_state != BT_STATE_CONNECTED) { + cable_state = CABLE_STATE_CONNECTED; + } + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (queue_height < CONFIG_QUEUE_MAX_LENGTH && queued_bytes < CONFIG_QUEUE_SIZE) { + queued_bytes++; + packet_queue[queue_cursor++] = sbyte; + if (queue_cursor == CONFIG_QUEUE_SIZE) queue_cursor = 0; + } + } + } else if (command == CMD_FREQUENCY) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t freq = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (freq == 0) { + kiss_indicate_frequency(); + } else { + lora_freq = freq; + if (op_mode == MODE_HOST) setFrequency(); + kiss_indicate_frequency(); + } + } + } else if (command == CMD_BANDWIDTH) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t bw = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (bw == 0) { + kiss_indicate_bandwidth(); + } else { + lora_bw = bw; + if (op_mode == MODE_HOST) setBandwidth(); + kiss_indicate_bandwidth(); + } + } + } else if (command == CMD_TXPOWER) { + if (sbyte == 0xFF) { + kiss_indicate_txpower(); + } else { + int txp = sbyte; + #if MODEM == SX1262 + #if HAS_LORA_PA + if (txp > PA_MAX_OUTPUT) txp = PA_MAX_OUTPUT; + #else + if (txp > 22) txp = 22; + #endif + #elif MODEM == SX1280 + #if HAS_PA + if (txp > 20) txp = 20; + #else + if (txp > 13) txp = 13; + #endif + #else + if (txp > 17) txp = 17; + #endif + + lora_txp = txp; + if (op_mode == MODE_HOST) setTXPower(); + kiss_indicate_txpower(); + } + } else if (command == CMD_SF) { + if (sbyte == 0xFF) { + kiss_indicate_spreadingfactor(); + } else { + int sf = sbyte; + if (sf < 5) sf = 5; + if (sf > 12) sf = 12; + + lora_sf = sf; + if (op_mode == MODE_HOST) setSpreadingFactor(); + kiss_indicate_spreadingfactor(); + } + } else if (command == CMD_CR) { + if (sbyte == 0xFF) { + kiss_indicate_codingrate(); + } else { + int cr = sbyte; + if (cr < 5) cr = 5; + if (cr > 8) cr = 8; + + lora_cr = cr; + if (op_mode == MODE_HOST) setCodingRate(); + kiss_indicate_codingrate(); + } + } else if (command == CMD_IMPLICIT) { + set_implicit_length(sbyte); + kiss_indicate_implicit_length(); + } else if (command == CMD_LEAVE) { + if (sbyte == 0xFF) { + display_unblank(); + cable_state = CABLE_STATE_DISCONNECTED; + current_rssi = -292; + last_rssi = -292; + last_rssi_raw = 0x00; + last_snr_raw = 0x80; + } + } else if (command == CMD_RADIO_STATE) { + if (bt_state != BT_STATE_CONNECTED) { + cable_state = CABLE_STATE_CONNECTED; + display_unblank(); + } + if (sbyte == 0xFF) { + kiss_indicate_radiostate(); + } else if (sbyte == 0x00) { + stopRadio(); + kiss_indicate_radiostate(); + } else if (sbyte == 0x01) { + startRadio(); + kiss_indicate_radiostate(); + } + } else if (command == CMD_ST_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + st_airtime_limit = 0.0; + } else { + st_airtime_limit = (float)at/(100.0*100.0); + if (st_airtime_limit >= 1.0) { st_airtime_limit = 0.0; } + } + kiss_indicate_st_alock(); + } + } else if (command == CMD_LT_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + lt_airtime_limit = 0.0; + } else { + lt_airtime_limit = (float)at/(100.0*100.0); + if (lt_airtime_limit >= 1.0) { lt_airtime_limit = 0.0; } + } + kiss_indicate_lt_alock(); + } + } else if (command == CMD_STAT_RX) { + kiss_indicate_stat_rx(); + } else if (command == CMD_STAT_TX) { + kiss_indicate_stat_tx(); + } else if (command == CMD_STAT_RSSI) { + kiss_indicate_stat_rssi(); + } else if (command == CMD_RADIO_LOCK) { + update_radio_lock(); + kiss_indicate_radio_lock(); + } else if (command == CMD_BLINK) { + led_indicate_info(sbyte); + } else if (command == CMD_RANDOM) { + kiss_indicate_random(getRandom()); + } else if (command == CMD_DETECT) { + if (sbyte == DETECT_REQ) { + if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; + kiss_indicate_detect(); + } + } else if (command == CMD_PROMISC) { + if (sbyte == 0x01) { + promisc_enable(); + } else if (sbyte == 0x00) { + promisc_disable(); + } + kiss_indicate_promisc(); + } else if (command == CMD_READY) { + if (!queue_full()) { + kiss_indicate_ready(); + } else { + kiss_indicate_not_ready(); + } + } else if (command == CMD_UNLOCK_ROM) { + if (sbyte == ROM_UNLOCK_BYTE) { + unlock_rom(); + } + } else if (command == CMD_RESET) { + if (sbyte == CMD_RESET_BYTE) { + hard_reset(); + } + } else if (command == CMD_ROM_READ) { + kiss_dump_eeprom(); + } else if (command == CMD_CFG_READ) { + kiss_dump_config(); + } else if (command == CMD_ROM_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + eeprom_write(cmdbuf[0], cmdbuf[1]); + } + } else if (command == CMD_FW_VERSION) { + kiss_indicate_version(); + } else if (command == CMD_PLATFORM) { + kiss_indicate_platform(); + } else if (command == CMD_MCU) { + kiss_indicate_mcu(); + } else if (command == CMD_BOARD) { + kiss_indicate_board(); + } else if (command == CMD_CONF_SAVE) { + eeprom_conf_save(); + } else if (command == CMD_CONF_DELETE) { + eeprom_conf_delete(); + } else if (command == CMD_FB_EXT) { + #if HAS_DISPLAY == true + if (sbyte == 0xFF) { + kiss_indicate_fbstate(); + } else if (sbyte == 0x00) { + ext_fb_disable(); + kiss_indicate_fbstate(); + } else if (sbyte == 0x01) { + ext_fb_enable(); + kiss_indicate_fbstate(); + } + #endif + } else if (command == CMD_FB_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + #if HAS_DISPLAY + if (frame_len == 9) { + uint8_t line = cmdbuf[0]; + if (line > 63) line = 63; + int fb_o = line*8; + memcpy(fb+fb_o, cmdbuf+1, 8); + } + #endif + } else if (command == CMD_FB_READ) { + if (sbyte != 0x00) { kiss_indicate_fb(); } + } else if (command == CMD_DISP_READ) { + if (sbyte != 0x00) { kiss_indicate_disp(); } + } else if (command == CMD_DEV_HASH) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte != 0x00) { + kiss_indicate_device_hash(); + } + #endif + } else if (command == CMD_DEV_SIG) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_SIG_LEN) { + memcpy(dev_sig, cmdbuf, DEV_SIG_LEN); + device_save_signature(); + } + #endif + } else if (command == CMD_FW_UPD) { + if (sbyte == 0x01) { + firmware_update_mode = true; + } else { + firmware_update_mode = false; + } + } else if (command == CMD_HASHES) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == 0x01) { + kiss_indicate_target_fw_hash(); + } else if (sbyte == 0x02) { + kiss_indicate_fw_hash(); + } else if (sbyte == 0x03) { + kiss_indicate_bootloader_hash(); + } else if (sbyte == 0x04) { + kiss_indicate_partition_table_hash(); + } + #endif + } else if (command == CMD_FW_HASH) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_HASH_LEN) { + memcpy(dev_firmware_hash_target, cmdbuf, DEV_HASH_LEN); + device_save_firmware_hash(); + } + #endif + } else if (command == CMD_WIFI_CHN) { + #if HAS_WIFI + if (sbyte > 0 && sbyte < 14) { eeprom_update(eeprom_addr(ADDR_CONF_WCHN), sbyte); } + #endif + } else if (command == CMD_WIFI_MODE) { + #if HAS_WIFI + if (sbyte == WR_WIFI_OFF || sbyte == WR_WIFI_STA || sbyte == WR_WIFI_AP) { + wr_conf_save(sbyte); + wifi_mode = sbyte; + wifi_remote_init(); + } + #endif + } else if (command == CMD_WIFI_SSID) { + #if HAS_WIFI + if (sbyte == FESC) { ESCAPE = true; } + else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (sbyte == 0x00) { + for (uint8_t i = 0; i<33; i++) { + if (i 0x00) recondition_display = true; + } + #endif + } else if (command == CMD_NP_INT) { + #if HAS_NP + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + sbyte; + led_set_intensity(sbyte); + np_int_conf_save(sbyte); + } + + #endif + } + } +} + +#if MCU_VARIANT == MCU_ESP32 + portMUX_TYPE update_lock = portMUX_INITIALIZER_UNLOCKED; +#endif + +bool medium_free() { + update_modem_status(); + if (avoid_interference && interference_detected) { return false; } + return !dcd; +} + +bool noise_floor_sampled = false; +int noise_floor_sample = 0; +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 && 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; + } + } + } + #endif +} + +#define LED_ID_TRIG 16 +uint8_t led_id_filter = 0; +uint32_t interference_start = 0; +bool interference_persists = false; +void update_modem_status() { + #if MCU_VARIANT == MCU_ESP32 + portENTER_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); + #endif + + bool carrier_detected = LoRa->dcd(); + current_rssi = LoRa->currentRssi(); + last_status_update = millis(); + + #if MCU_VARIANT == MCU_ESP32 + portEXIT_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portEXIT_CRITICAL(); + #endif + + #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; } } + + // Handle potential false interference detection due to + // 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 (millis()-interference_start >= CSMA_RFENV_RECAL_MS) { noise_floor_sampled = false; interference_persists = false; } + } + } else { interference_persists = false; } + + if (carrier_detected) { dcd = true; } else { dcd = false; } + + dcd_led = dcd; + if (dcd_led) { led_rx_on(); } + else { + if (interference_detected) { + if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled) { led_id_on(); } + } else { + if (airtime_lock) { led_indicate_airtime_lock(); } + else { led_rx_off(); led_id_off(); } + } + } +} + +void check_modem_status() { + if (millis()-last_status_update >= status_interval_ms) { + update_modem_status(); + update_noise_floor(); + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + util_samples[dcd_sample] = dcd; + dcd_sample = (dcd_sample+1)%DCD_SAMPLES; + if (dcd_sample % UTIL_UPDATE_INTERVAL == 0) { + int util_count = 0; + for (int ui = 0; ui < DCD_SAMPLES; ui++) { + if (util_samples[ui]) util_count++; + } + local_channel_util = (float)util_count / (float)DCD_SAMPLES; + total_channel_util = local_channel_util + airtime; + if (total_channel_util > 1.0) total_channel_util = 1.0; + + int16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + if (total_channel_util > longterm_bins[cb]) longterm_bins[cb] = total_channel_util; + longterm_bins[nb] = 0.0; + + update_airtime(); + } + #endif + } +} + +void validate_status() { + #if MCU_VARIANT == MCU_1284P + uint8_t boot_flags = OPTIBOOT_MCUSR; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_2560 + uint8_t boot_flags = OPTIBOOT_MCUSR; + if (boot_flags == 0x00) boot_flags = 0x03; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_ESP32 + // TODO: Get ESP32 boot flags + uint8_t boot_flags = 0x02; + uint8_t F_POR = 0x00; + uint8_t F_BOR = 0x00; + uint8_t F_WDR = 0x01; + #elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags + uint8_t boot_flags = 0x02; + uint8_t F_POR = 0x00; + uint8_t F_BOR = 0x00; + uint8_t F_WDR = 0x01; + #endif + + if (hw_ready || device_init_done) { + hw_ready = false; + Serial.write("Error, invalid hardware check state\r\n"); + #if HAS_DISPLAY + if (disp_ready) { + device_init_done = true; + update_display(); + } + #endif + led_indicate_boot_error(); + } + + if (boot_flags & (1< CSMA_CW_BANDS) { new_cw_band = CSMA_CW_BANDS; } + if (new_cw_band != cw_band) { + cw_band = (uint8_t)(new_cw_band); + cw_min = (cw_band-1) * CSMA_CW_PER_BAND_WINDOWS; + cw_max = (cw_band) * CSMA_CW_PER_BAND_WINDOWS - 1; + kiss_indicate_csma_stats(); + } + } +#endif + +void tx_queue_handler() { + if (!airtime_lock && queue_height > 0) { + if (csma_cw == -1) { + csma_cw = random(cw_min, cw_max); + cw_wait_target = csma_cw * csma_slot_ms; + } + + if (difs_wait_start == -1) { // DIFS wait not yet started + if (medium_free()) { difs_wait_start = millis(); return; } // Set DIFS wait start time + else { return; } } // Medium not yet free, continue waiting + + else { // We are waiting for DIFS or CW to pass + if (!medium_free()) { difs_wait_start = -1; cw_wait_start = -1; return; } // Medium became occupied while in DIFS wait, restart waiting when free again + else { // Medium is free, so continue waiting + if (millis() < difs_wait_start+difs_ms) { return; } // DIFS has not yet passed, continue waiting + else { // DIFS has passed, and we are now in CW wait + if (cw_wait_start == -1) { cw_wait_start = millis(); return; } // If we haven't started counting CW wait time, do it from now + else { // If we are already counting CW wait time, add it to the counter + cw_wait_passed += millis()-cw_wait_start; cw_wait_start = millis(); + if (cw_wait_passed < cw_wait_target) { return; } // Contention window wait time has not yet passed, continue waiting + else { // Wait time has passed, flush the queue + bool should_flush = !lora_limit_rate && !lora_guard_rate; + if (should_flush) { flush_queue(); } else { pop_queue(); } + cw_wait_passed = 0; csma_cw = -1; difs_wait_start = -1; } + } + } + } + } + } +} + +void work_while_waiting() { loop(); } + +void loop() { + if (radio_online) { + #if MCU_VARIANT == MCU_ESP32 + modem_packet_t *modem_packet = NULL; + if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) { + host_write_len = modem_packet->len; + last_rssi = modem_packet->rssi; + last_snr_raw = modem_packet->snr_raw; + memcpy(&pbuf, modem_packet->data, modem_packet->len); + free(modem_packet); + modem_packet = NULL; + + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + kiss_write_packet(); + } + + airtime_lock = false; + if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; + if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; + + #elif MCU_VARIANT == MCU_NRF52 + modem_packet_t *modem_packet = NULL; + if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) { + memcpy(&pbuf, modem_packet->data, modem_packet->len); + host_write_len = modem_packet->len; + free(modem_packet); + modem_packet = NULL; + + portENTER_CRITICAL(); + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + portEXIT_CRITICAL(); + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + kiss_write_packet(); + } + + airtime_lock = false; + if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; + if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; + + #endif + + tx_queue_handler(); + check_modem_status(); + + } else { + if (hw_ready) { + if (console_active) { + #if HAS_CONSOLE + console_loop(); + #endif + } else { + led_indicate_standby(); + } + } else { + + led_indicate_not_ready(); + stopRadio(); + } + } + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + buffer_serial(); + if (!fifo_isempty(&serialFIFO)) serial_poll(); + #else + if (!fifo_isempty_locked(&serialFIFO)) serial_poll(); + #endif + + #if HAS_DISPLAY + if (disp_ready && !display_updating) update_display(); + #endif + + #if HAS_PMU + if (pmu_ready) update_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + if (!console_active && bt_ready) update_bt(); + #endif + + #if HAS_WIFI + if (wifi_initialized) update_wifi(); + #endif + + #if HAS_INPUT + input_read(); + #endif + + if (memory_low) { + #if PLATFORM == PLATFORM_ESP32 + if (esp_get_free_heap_size() < 8192) { + kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; + } else { + memory_low = false; + } + #else + kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; + #endif + } +} + +void sleep_now() { + #if HAS_SLEEP == true + stopRadio(); // TODO: Check this on all platforms + #if PLATFORM == PLATFORM_ESP32 + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_CARDPUTER_ADV + #if HAS_DISPLAY + display_intensity = 0; + update_display(true); + #endif + #endif + #if BOARD_MODEL == BOARD_HELTEC32_V4 + digitalWrite(LORA_PA_CPS, LOW); + digitalWrite(LORA_PA_CSD, LOW); + digitalWrite(LORA_PA_PWR_EN, LOW); + digitalWrite(Vext, HIGH); + #endif + #if PIN_DISP_SLEEP >= 0 + pinMode(PIN_DISP_SLEEP, OUTPUT); + digitalWrite(PIN_DISP_SLEEP, DISP_SLEEP_LEVEL); + #endif + #if HAS_BLUETOOTH + if (bt_state == BT_STATE_CONNECTED) { + bt_stop(); + delay(100); + } + #endif + esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, WAKEUP_LEVEL); + esp_deep_sleep_start(); + #elif PLATFORM == PLATFORM_NRF52 + #if BOARD_MODEL == BOARD_HELTEC_T114 + npset(0,0,0); + digitalWrite(PIN_VEXT_EN, LOW); + digitalWrite(PIN_T114_TFT_BLGT, HIGH); + digitalWrite(PIN_T114_TFT_EN, HIGH); + #elif BOARD_MODEL == BOARD_TECHO + for (uint8_t i = display_intensity; i > 0; i--) { analogWrite(pin_backlight, i-1); delay(1); } + epd_black(true); delay(300); epd_black(true); delay(300); epd_black(false); + delay(2000); + analogWrite(PIN_VEXT_EN, 0); + delay(100); + #endif + sd_power_gpregret_set(0, 0x6d); + nrf_gpio_cfg_sense_input(pin_btn_usr1, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); + NRF_POWER->SYSTEMOFF = 1; + #endif + #endif +} + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV +void cardputer_ble_toggle_event() { + #if HAS_BLUETOOTH || HAS_BLE + bool enable_ble = (bt_state == BT_STATE_OFF); + if (enable_ble) { + bt_start(); + bt_conf_save(true); + } else { + bt_stop(); + bt_conf_save(false); + } + #if HAS_DISPLAY + cardputer_show_ble_notice(enable_ble); + #endif + #endif +} +#endif + +void button_event(uint8_t event, unsigned long duration) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (display_blanked) { + display_unblank(); + } else { + if (duration > 10000) { + #if HAS_CONSOLE + #if HAS_BLUETOOTH || HAS_BLE + bt_stop(); + #endif + console_active = true; + console_start(); + #endif + } else if (duration > 5000) { + #if HAS_BLUETOOTH || HAS_BLE + if (bt_state != BT_STATE_CONNECTED) { bt_enable_pairing(); } + #endif + } else if (duration > 700) { + #if HAS_SLEEP + sleep_now(); + #endif + } else { + #if HAS_BLUETOOTH || HAS_BLE + if (bt_state != BT_STATE_CONNECTED) { + if (bt_state == BT_STATE_OFF) { + bt_start(); + bt_conf_save(true); + } else { + bt_stop(); + bt_conf_save(false); + } + } + #endif + } + } + #endif +} + +volatile bool serial_polling = false; +void serial_poll() { + serial_polling = true; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + while (!fifo_isempty_locked(&serialFIFO)) { + #else + while (!fifo_isempty(&serialFIFO)) { + #endif + char sbyte = fifo_pop(&serialFIFO); + serial_callback(sbyte); + } + + serial_polling = false; +} + +#if MCU_VARIANT != MCU_ESP32 + #define MAX_CYCLES 20 +#else + #define MAX_CYCLES 10 +#endif +void buffer_serial() { + if (!serial_buffering) { + serial_buffering = true; + + uint8_t c = 0; + + #if HAS_BLUETOOTH || HAS_BLE == true + while ( + c < MAX_CYCLES && + #if HAS_WIFI + ( (bt_state != BT_STATE_CONNECTED && Serial.available()) || (bt_state == BT_STATE_CONNECTED && SerialBT.available()) || (wr_state >= WR_STATE_ON && wifi_remote_available()) ) + #else + ( (bt_state != BT_STATE_CONNECTED && Serial.available()) || (bt_state == BT_STATE_CONNECTED && SerialBT.available()) ) + #endif + ) + #else + while (c < MAX_CYCLES && Serial.available()) + #endif + { + c++; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + if (!fifo_isfull_locked(&serialFIFO)) { fifo_push_locked(&serialFIFO, Serial.read()); } + #elif HAS_BLUETOOTH || HAS_BLE == true || HAS_WIFI + if (bt_state == BT_STATE_CONNECTED) { if (!fifo_isfull(&serialFIFO)) { fifo_push(&serialFIFO, SerialBT.read()); } } + #if HAS_WIFI + else if (wifi_host_is_connected()) { if (!fifo_isfull(&serialFIFO)) { fifo_push(&serialFIFO, wifi_remote_read()); } } + #endif + else { if (!fifo_isfull(&serialFIFO)) { fifo_push(&serialFIFO, Serial.read()); } } + #else + if (!fifo_isfull(&serialFIFO)) { fifo_push(&serialFIFO, Serial.read()); } + #endif + } + + serial_buffering = false; + } +} + +void serial_interrupt_init() { + #if MCU_VARIANT == MCU_1284P + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_2560 + // TODO: This should probably be updated for + // atmega2560 support. Might be source of + // reported issues from snh. + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_ESP32 + // No interrupt-based polling on ESP32 + #endif + +} + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + ISR(TIMER3_CAPT_vect) { buffer_serial(); } +#endif diff --git a/vendor/rnode_firmware/ROM.h b/vendor/rnode_firmware/ROM.h new file mode 100644 index 0000000..c883434 --- /dev/null +++ b/vendor/rnode_firmware/ROM.h @@ -0,0 +1,63 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef ROM_H + #define ROM_H + #define CHECKSUMMED_SIZE 0x0B + + // ROM address map /////////////// + #define ADDR_PRODUCT 0x00 + #define ADDR_MODEL 0x01 + #define ADDR_HW_REV 0x02 + #define ADDR_SERIAL 0x03 + #define ADDR_MADE 0x07 + #define ADDR_CHKSUM 0x0B + #define ADDR_SIGNATURE 0x1B + #define ADDR_INFO_LOCK 0x9B + + #define ADDR_CONF_SF 0x9C + #define ADDR_CONF_CR 0x9D + #define ADDR_CONF_TXP 0x9E + #define ADDR_CONF_BW 0x9F + #define ADDR_CONF_FREQ 0xA3 + #define ADDR_CONF_OK 0xA7 + + #define ADDR_CONF_BT 0xB0 + #define ADDR_CONF_DSET 0xB1 + #define ADDR_CONF_DINT 0xB2 + #define ADDR_CONF_DADR 0xB3 + #define ADDR_CONF_DBLK 0xB4 + #define ADDR_CONF_DROT 0xB8 + #define ADDR_CONF_PSET 0xB5 + #define ADDR_CONF_PINT 0xB6 + #define ADDR_CONF_BSET 0xB7 + #define ADDR_CONF_DIA 0xB9 + #define ADDR_CONF_WIFI 0xBA + #define ADDR_CONF_WCHN 0xBB + + #define INFO_LOCK_BYTE 0x73 + #define CONF_OK_BYTE 0x73 + #define BT_ENABLE_BYTE 0x73 + + #define EEPROM_RESERVED 200 + + #define CONFIG_SIZE 256 + #define ADDR_CONF_SSID 0x00 + #define ADDR_CONF_PSK 0x21 + #define ADDR_CONF_IP 0x42 + #define ADDR_CONF_NM 0x46 + ////////////////////////////////// + +#endif diff --git a/vendor/rnode_firmware/Remote.h b/vendor/rnode_firmware/Remote.h new file mode 100644 index 0000000..43eddd7 --- /dev/null +++ b/vendor/rnode_firmware/Remote.h @@ -0,0 +1,213 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#if CONFIG_IDF_TARGET_ESP32 + #include "esp32/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 + #include "esp32s2/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C3 + #include "esp32c3/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 + #include "esp32s3/rom/rtc.h" +#else + #error Target CONFIG_IDF_TARGET is not supported +#endif + +#define WIFI_UPDATE_INTERVAL_MS 500 +#define WR_SOCKET_TIMEOUT 6 +#define WR_READ_TIMEOUT_MS 6500 +#define WR_RECONNECT_INTERVAL_MS 10000 + +uint32_t wifi_update_interval_ms = WIFI_UPDATE_INTERVAL_MS; +uint32_t last_wifi_update = 0; +uint32_t wr_last_connect_try = 0; +uint32_t wr_last_read = 0; + +WiFiClient connection; +WiFiServer remote_listener(7633, 1); +IPAddress ap_ip(10, 0, 0, 1); +IPAddress ap_nm(255, 255, 255, 0); +IPAddress wr_device_ip; +char wr_hostname[10]; +wl_status_t wr_wifi_status = WL_IDLE_STATUS; + +uint8_t wifi_mode = WIFI_OFF; +bool wifi_init_ran = false; +bool wifi_initialized = false; + +char wr_ssid[33]; +char wr_psk[33]; + +extern void host_disconnected(); + +void wifi_dbg(String msg) { Serial.print("[WiFi] "); Serial.println(msg); } + +uint8_t wifi_remote_mode() { return wifi_mode; } + +bool wifi_is_connected() { return (wr_wifi_status == WL_CONNECTED); } +bool wifi_host_is_connected() { if (connection) { return true; } else { return false; } } + +void wifi_remote_start_ap() { + WiFi.mode(WIFI_AP); + if (wr_ssid[0] != 0x00) { + if (wr_psk[0] != 0x00) { WiFi.softAP(wr_ssid, wr_psk, wr_channel); } + else { WiFi.softAP(wr_ssid, NULL, wr_channel); } + } else { + if (wr_psk[0] != 0x00) { WiFi.softAP(bt_devname, wr_psk, wr_channel); } + else { WiFi.softAP(bt_devname, NULL, wr_channel); } + } + delay(150); + WiFi.softAPConfig(ap_ip, ap_ip, ap_nm); + wifi_initialized = true; +} + +void wifi_remote_start_sta() { + WiFi.mode(WIFI_STA); + + uint8_t ip[4]; bool ip_ok = true; + for (uint8_t i = 0; i < 4; i++) { ip[i] = EEPROM.read(config_addr(ADDR_CONF_IP+i)); } + if (ip[0]==0x00 && ip[1]==0x00 && ip[2]==0x00 && ip[3]==0x00) { ip_ok = false; } + if (ip[0]==0xFF && ip[1]==0xFF && ip[2]==0xFF && ip[3]==0xFF) { ip_ok = false; } + + uint8_t nm[4]; bool nm_ok = true; + for (uint8_t i = 0; i < 4; i++) { nm[i] = EEPROM.read(config_addr(ADDR_CONF_NM+i)); } + if (nm[0]==0x00 && nm[1]==0x00 && nm[2]==0x00 && nm[3]==0x00) { nm_ok = false; } + if (nm[0]==0xFF && nm[1]==0xFF && nm[2]==0xFF && nm[3]==0xFF) { nm_ok = false; } + + if (ip_ok && nm_ok) { + IPAddress sta_ip(ip[0], ip[1], ip[2], ip[3]); + IPAddress sta_nm(nm[0], nm[1], nm[2], nm[3]); + WiFi.config(sta_ip, sta_ip, sta_nm); + } + + delay(100); + if (wr_ssid[0] != 0x00) { + if (wr_psk[0] != 0x00) { WiFi.begin(wr_ssid, wr_psk); } + else { WiFi.begin(wr_ssid); } + } + + delay(500); + wr_wifi_status = WiFi.status(); + wifi_initialized = true; + wr_last_connect_try = millis(); +} + +void wifi_remote_stop() { + WiFi.softAPdisconnect(true); + WiFi.disconnect(true, true); + WiFi.mode(WIFI_MODE_NULL); + wifi_initialized = false; +} + +void wifi_remote_start() { + if (wifi_mode == WR_WIFI_AP) { wifi_remote_start_ap(); } + else if (wifi_mode == WR_WIFI_STA) { wifi_remote_start_sta(); } + else { wifi_remote_stop(); } + + if (wifi_initialized == true) { + remote_listener.begin(); + remote_listener.setTimeout(WR_SOCKET_TIMEOUT); + wr_state = WR_STATE_ON; + } else { remote_listener.end(); wr_state = WR_STATE_OFF; } +} + +void wifi_remote_init() { + memcpy(wr_hostname, bt_devname, 5); + memcpy(wr_hostname+5, bt_devname+6, 4); + wr_hostname[9] = 0x00; + WiFi.softAPdisconnect(true); + WiFi.disconnect(true, true); + WiFi.mode(WIFI_MODE_NULL); + WiFi.setHostname(wr_hostname); + + wr_ssid[32] = 0x00; wr_psk[32] = 0x00; + for (uint8_t i = 0; i < 32; i++) { wr_ssid[i] = EEPROM.read(config_addr(ADDR_CONF_SSID+i)); if (wr_ssid[i] == 0xFF) { wr_ssid[i] = 0x00; } } + for (uint8_t i = 0; i < 32; i++) { wr_psk[i] = EEPROM.read(config_addr(ADDR_CONF_PSK+i)); if (wr_psk[i] == 0xFF) { wr_psk[i] = 0x00; } } + wr_channel = EEPROM.read(eeprom_addr(ADDR_CONF_WCHN)); if (wr_channel < 1 || wr_channel > 14) { wr_channel = WR_CHANNEL_DEFAULT; } + wifi_remote_start(); + wifi_init_ran = true; +} + +void wifi_remote_close_all() { + // wifi_dbg("Close all"); // TODO: Remove debug + if (connection) { connection.stop(); } + WiFiClient client = remote_listener.available(); + while (client) { client.stop(); client = remote_listener.available(); } + wr_state = WR_STATE_ON; +} + +void wifi_remote_check_active() { + if (millis()-wr_last_read >= WR_READ_TIMEOUT_MS) { + // wifi_dbg("Connection activity timed out"); // TODO: Remove debug + if (connection && connection.connected()) { + connection.stop(); + wifi_remote_close_all(); + host_disconnected(); + } + } +} + +bool wifi_remote_available() { + if (connection) { + if (connection.connected()) { + if (connection.available()) { wr_last_read = millis(); return true; } + else { wifi_remote_check_active(); return false; } + } else { + // wifi_dbg("Client disconnected"); // TODO: Remove debug + wifi_remote_close_all(); + return false; + } + } else { + WiFiClient client = remote_listener.available(); + if (!client) { return false; } + else { + // wifi_dbg("Client connected"); // TODO: Remove debug + connection = client; + wr_state = WR_STATE_CONNECTED; + wr_last_read = millis(); + if (connection.available()) { return true; } + else { return false; } + } + } +} + +uint8_t wifi_remote_read() { + if (connection && connection.available()) { return connection.read(); } + else { + // wifi_dbg("Error: No data to read from TCP socket"); // TODO: Remove debug + if (connection) { wifi_remote_close_all(); } + return 0xC0; + } +} + +void wifi_remote_write(uint8_t byte) { if (connection) { connection.write(byte); } } + +void wifi_update_status() { + wr_wifi_status = WiFi.status(); + if (wr_wifi_status == WL_CONNECTED) { wr_device_ip = WiFi.localIP(); } + if (wifi_mode == WR_WIFI_AP && wifi_initialized) { wr_device_ip = WiFi.softAPIP(); wr_wifi_status = WL_CONNECTED; } + if (wifi_init_ran && wifi_mode == WR_WIFI_STA && wr_wifi_status != WL_CONNECTED) { + if (millis()-wr_last_connect_try >= WR_RECONNECT_INTERVAL_MS) { wifi_remote_init(); } + } +} + +void update_wifi() { + if (millis()-last_wifi_update >= wifi_update_interval_ms) { + wifi_update_status(); + last_wifi_update = millis(); + } +} \ No newline at end of file diff --git a/vendor/rnode_firmware/RsCardputerModeSwitch.h b/vendor/rnode_firmware/RsCardputerModeSwitch.h new file mode 100644 index 0000000..d75f37d --- /dev/null +++ b/vendor/rnode_firmware/RsCardputerModeSwitch.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "esp_ota_ops.h" +#include "esp_partition.h" + +namespace rs_cardputer_adv { + +enum class FirmwareMode : uint8_t { + Launcher, + Ratcom, + RNode, +}; + +struct SwitchResult { + bool ok; + const char *message; +}; + +inline esp_partition_subtype_t mode_subtype(FirmwareMode mode) { + switch (mode) { + case FirmwareMode::Launcher: + return ESP_PARTITION_SUBTYPE_APP_OTA_0; + case FirmwareMode::Ratcom: + return ESP_PARTITION_SUBTYPE_APP_OTA_1; + case FirmwareMode::RNode: + return ESP_PARTITION_SUBTYPE_APP_OTA_2; + } + return ESP_PARTITION_SUBTYPE_APP_OTA_0; +} + +inline const esp_partition_t *mode_partition(FirmwareMode mode) { + return esp_partition_find_first(ESP_PARTITION_TYPE_APP, mode_subtype(mode), nullptr); +} + +inline SwitchResult set_next_boot(FirmwareMode mode) { + const esp_partition_t *target = mode_partition(mode); + if (target == nullptr) { + return {false, "target partition not found"}; + } + + esp_err_t err = esp_ota_set_boot_partition(target); + if (err != ESP_OK) { + return {false, esp_err_to_name(err)}; + } + + return {true, "boot partition updated"}; +} + +inline SwitchResult return_to_launcher_next_boot() { + return set_next_boot(FirmwareMode::Launcher); +} + +} // namespace rs_cardputer_adv diff --git a/vendor/rnode_firmware/RsDeckModeSwitch.h b/vendor/rnode_firmware/RsDeckModeSwitch.h new file mode 100644 index 0000000..fc29f8e --- /dev/null +++ b/vendor/rnode_firmware/RsDeckModeSwitch.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include "esp_ota_ops.h" +#include "esp_partition.h" + +// Boot-mode selection for the rsDeck dual-boot layout (launcher/standalone/rnode +// in ota_0/1/2). On the legacy single-app layout ota_0 is the running standalone, +// so re-arming the launcher is a harmless self-select. +namespace rs_deck { + +enum class FirmwareMode : uint8_t { + Launcher, + Standalone, + RNode, +}; + +struct SwitchResult { + bool ok; + const char* message; +}; + +inline esp_partition_subtype_t modeSubtype(FirmwareMode mode) { + switch (mode) { + case FirmwareMode::Launcher: + return ESP_PARTITION_SUBTYPE_APP_OTA_0; + case FirmwareMode::Standalone: + return ESP_PARTITION_SUBTYPE_APP_OTA_1; + case FirmwareMode::RNode: + return ESP_PARTITION_SUBTYPE_APP_OTA_2; + } + return ESP_PARTITION_SUBTYPE_APP_OTA_0; +} + +inline const esp_partition_t* modePartition(FirmwareMode mode) { + return esp_partition_find_first(ESP_PARTITION_TYPE_APP, modeSubtype(mode), nullptr); +} + +inline const char* modeName(FirmwareMode mode) { + switch (mode) { + case FirmwareMode::Launcher: + return "Launcher"; + case FirmwareMode::Standalone: + return "Standalone"; + case FirmwareMode::RNode: + return "RNode"; + } + return "Unknown"; +} + +inline SwitchResult setNextBoot(FirmwareMode mode) { + const esp_partition_t* target = modePartition(mode); + if (!target) { + return {false, "target partition not found"}; + } + + esp_err_t err = esp_ota_set_boot_partition(target); + if (err != ESP_OK) { + return {false, esp_err_to_name(err)}; + } + + return {true, "boot partition updated"}; +} + +inline SwitchResult returnToLauncherNextBoot() { + return setNextBoot(FirmwareMode::Launcher); +} + +// Snake_case aliases for the launcher and RNode helper. +inline esp_partition_subtype_t mode_subtype(FirmwareMode mode) { return modeSubtype(mode); } +inline const esp_partition_t* mode_partition(FirmwareMode mode) { return modePartition(mode); } +inline const char* mode_name(FirmwareMode mode) { return modeName(mode); } +inline SwitchResult set_next_boot(FirmwareMode mode) { return setNextBoot(mode); } +inline SwitchResult return_to_launcher_next_boot() { return returnToLauncherNextBoot(); } + +} // namespace rs_deck diff --git a/vendor/rnode_firmware/ST7789.h b/vendor/rnode_firmware/ST7789.h new file mode 100644 index 0000000..85012e8 --- /dev/null +++ b/vendor/rnode_firmware/ST7789.h @@ -0,0 +1,440 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2024 by Heltec AutoMation + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef ST7789Spi_h +#define ST7789Spi_h + +#include "OLEDDisplay.h" +#include + + +#define ST_CMD_DELAY 0x80 // special signifier for command lists + +#define ST77XX_NOP 0x00 +#define ST77XX_SWRESET 0x01 +#define ST77XX_RDDID 0x04 +#define ST77XX_RDDST 0x09 + +#define ST77XX_SLPIN 0x10 +#define ST77XX_SLPOUT 0x11 +#define ST77XX_PTLON 0x12 +#define ST77XX_NORON 0x13 + +#define ST77XX_INVOFF 0x20 +#define ST77XX_INVON 0x21 +#define ST77XX_DISPOFF 0x28 +#define ST77XX_DISPON 0x29 +#define ST77XX_CASET 0x2A +#define ST77XX_RASET 0x2B +#define ST77XX_RAMWR 0x2C +#define ST77XX_RAMRD 0x2E + +#define ST77XX_PTLAR 0x30 +#define ST77XX_TEOFF 0x34 +#define ST77XX_TEON 0x35 +#define ST77XX_MADCTL 0x36 +#define ST77XX_COLMOD 0x3A + +#define ST77XX_MADCTL_MY 0x80 +#define ST77XX_MADCTL_MX 0x40 +#define ST77XX_MADCTL_MV 0x20 +#define ST77XX_MADCTL_ML 0x10 +#define ST77XX_MADCTL_RGB 0x00 + +#define ST77XX_RDID1 0xDA +#define ST77XX_RDID2 0xDB +#define ST77XX_RDID3 0xDC +#define ST77XX_RDID4 0xDD + +// Some ready-made 16-bit ('565') color settings: +#define ST77XX_BLACK 0x0000 +#define ST77XX_WHITE 0xFFFF +#define ST77XX_RED 0xF800 +#define ST77XX_GREEN 0x07E0 +#define ST77XX_BLUE 0x001F +#define ST77XX_CYAN 0x07FF +#define ST77XX_MAGENTA 0xF81F +#define ST77XX_YELLOW 0xFFE0 +#define ST77XX_ORANGE 0xFC00 + +#define LED_A_ON LOW + +#ifdef ESP_PLATFORM +#undef LED_A_ON +#define LED_A_ON HIGH +#define rtos_free free +#define rtos_malloc malloc +//SPIClass SPI1(HSPI); +#endif +class ST7789Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + uint8_t _ledA; + int _miso; + int _mosi; + int _clk; + SPIClass * _spi; + SPISettings _spiSettings; + uint16_t _RGB=0xFFFF; + uint8_t _buffheight; + public: + /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ + ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=320,int mosi=-1,int miso=-1,int clk=-1) { + this->_spi = spiClass; + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + this->_mosi=mosi; + this->_miso=miso; + this->_clk=clk; + //this->_ledA = _ledA; + _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + setGeometry(g,width,height); + } + + bool connect(){ + this->_buffheight=displayHeight / 8; + this->_buffheight+=displayHeight % 8 ? 1:0; + pinMode(_cs, OUTPUT); + pinMode(_dc, OUTPUT); + //pinMode(_ledA, OUTPUT); + if (_cs != (uint8_t) -1) { + pinMode(_cs, OUTPUT); + } + pinMode(_rst, OUTPUT); + +#ifdef ESP_PLATFORM + _spi->begin(_clk,_miso,_mosi,-1); +#else + _spi->begin(); +#endif + _spi->setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + _spi->begin (); + //digitalWrite(_ledA, LED_A_ON); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + + uint16_t minBoundY = UINT16_MAX; + uint16_t maxBoundY = 0; + + uint16_t minBoundX = UINT16_MAX; + uint16_t maxBoundX = 0; + + uint16_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < _buffheight; y++) { + for (x = 0; x < displayWidth; x++) { + //Serial.printf("x %d y %d\r\n",x,y); + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = min(minBoundY, y); + maxBoundY = max(maxBoundY, y); + minBoundX = min(minBoundX, x); + maxBoundX = max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT16_MAX) return; + + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + + for (y = minBoundY; y <= maxBoundY; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1); + uint32_t const pixbufcount = maxBoundX-minBoundX+1; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = minBoundX; x <= maxBoundX; x++) + { + pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #else + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + uint8_t x, y; + for (y = 0; y < _buffheight; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(y*8+temp,0,1,displayWidth); + uint32_t const pixbufcount = displayWidth; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = 0; x < displayWidth; x++) + { + pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #endif + } + + virtual void resetOrientation() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void flipScreenVertically() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void mirrorScreen() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void setRotation(uint8_t r) { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + if (r == 1) { madctl = 0xC0; } + if (r == 2) { madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; } + if (r == 3) { madctl = 0x00; } + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + void setRGB(uint16_t c) + { + + this->_RGB=0x00|c>>8|c<<8&0xFF00; + } + + void displayOn(void) { + //sendCommand(DISPLAYON); + } + + void displayOff(void) { + //sendCommand(DISPLAYOFF); + } + +//#define ST77XX_MADCTL_MY 0x80 +//#define ST77XX_MADCTL_MX 0x40 +//#define ST77XX_MADCTL_MV 0x20 +//#define ST77XX_MADCTL_ML 0x10 + protected: + // Send all the init commands + virtual void sendInitCommands() + { + sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay + delay(150); + + sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay + delay(10); + + sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color + WriteData(0x55); + delay(10); + + sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh + WriteData(0x08); + + sendCommand(ST77XX_CASET); // 5: Column addr set, + WriteData(0x00); + WriteData(0x00); // XSTART = 0 + WriteData(0x00); + WriteData(240); // XEND = 240 + + sendCommand(ST77XX_RASET); // 6: Row addr set, + WriteData(0x00); + WriteData(0x00); // YSTART = 0 + WriteData(320>>8); + WriteData(320&0xFF); // YSTART = 320 + + sendCommand(ST77XX_SLPOUT); // 7: hack + delay(10); + + sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay + delay(10); + + sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay + delay(10); + + sendCommand(ST77XX_INVON); // 10: invert + delay(10); + + //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + setRGB(ST77XX_GREEN); + } + + + private: + + void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + x += (320-displayWidth)/2; + y += (240-displayHeight)/2; + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); + uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); + + writeCommand(ST77XX_CASET); // Column addr set + SPI_WRITE32(xa); + + writeCommand(ST77XX_RASET); // Row addr set + SPI_WRITE32(ya); + + writeCommand(ST77XX_RAMWR); // write to RAM + } + int getBufferOffset(void) { + return 0; + } + inline void set_CS(bool level) { + if (_cs != (uint8_t) -1) { + digitalWrite(_cs, level); + } + }; + inline void sendCommand(uint8_t com) __attribute__((always_inline)){ + set_CS(HIGH); + digitalWrite(_dc, LOW); + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(com); + _spi->endTransaction(); + set_CS(HIGH); + digitalWrite(_dc, HIGH); + } + + inline void WriteData(uint8_t data) __attribute__((always_inline)){ + digitalWrite(_cs, LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(data); + _spi->endTransaction(); + digitalWrite(_cs, HIGH); + } + void SPI_WRITE32(uint32_t l) + { + _spi->transfer(l >> 24); + _spi->transfer(l >> 16); + _spi->transfer(l >> 8); + _spi->transfer(l); + } + void writeCommand(uint8_t cmd) { + digitalWrite(_dc, LOW); + _spi->transfer(cmd); + digitalWrite(_dc, HIGH); + } + +// Private functions + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_128: + this->displayWidth = 128; + this->displayHeight = 128; + break; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + uint8_t tmp=displayHeight % 8; + uint8_t _buffheight=displayHeight / 8; + + if(tmp!=0) + _buffheight++; + this->displayBufferSize = displayWidth * _buffheight ; + } + + + +}; + +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/Utilities.h b/vendor/rnode_firmware/Utilities.h new file mode 100644 index 0000000..5c9ae11 --- /dev/null +++ b/vendor/rnode_firmware/Utilities.h @@ -0,0 +1,2186 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Config.h" + +#if HAS_EEPROM + #include +#elif PLATFORM == PLATFORM_NRF52 + #include + #include + #include + using namespace Adafruit_LittleFS_Namespace; + #define EEPROM_FILE "eeprom" + bool file_exists = false; + int written_bytes = 4; + File file(InternalFS); +#endif +#include + +#if MODEM == SX1262 +#include "sx126x.h" +sx126x *LoRa = &sx126x_modem; +#elif MODEM == SX1276 || MODEM == SX1278 +#include "sx127x.h" +sx127x *LoRa = &sx127x_modem; +#elif MODEM == SX1280 +#include "sx128x.h" +sx128x *LoRa = &sx128x_modem; +#endif + +#include "ROM.h" +#include "Framing.h" +#include "MD5.h" + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 +uint8_t eeprom_read(uint32_t mapped_addr); +#endif + +#if HAS_DISPLAY == true + #include "Display.h" +#else + void display_unblank() {} + bool display_blanked = false; +#endif + +#if HAS_BLUETOOTH == true || HAS_BLE == true + void kiss_indicate_btpin(); + #include "Bluetooth.h" +#endif + +#if HAS_WIFI == true + #include "Remote.h" +#endif + +#if HAS_PMU == true + #include "Power.h" +#endif + +#if HAS_INPUT == true + #include "Input.h" +#endif + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #include "Device.h" +#endif +#if MCU_VARIANT == MCU_ESP32 + //https://github.com/espressif/esp-idf/issues/8855 + #if BOARD_MODEL == BOARD_HELTEC32_V3 + #include "hal/wdt_hal.h" + #elif BOARD_MODEL == BOARD_T3S3 + #include "hal/wdt_hal.h" + #else + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + #include + #include +#endif + +uint8_t boot_vector = 0x00; + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + uint8_t OPTIBOOT_MCUSR __attribute__ ((section(".noinit"))); + void resetFlagsInit(void) __attribute__ ((naked)) __attribute__ ((used)) __attribute__ ((section (".init0"))); + void resetFlagsInit(void) { + __asm__ __volatile__ ("sts %0, r2\n" : "=m" (OPTIBOOT_MCUSR) :); + } +#elif MCU_VARIANT == MCU_ESP32 + // TODO: Get ESP32 boot flags +#elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags +#endif + +#if MCU_VARIANT == MCU_NRF52 + unsigned long get_rng_seed() { + nrf_rng_error_correction_enable(NRF_RNG); + nrf_rng_shorts_disable(NRF_RNG, NRF_RNG_SHORT_VALRDY_STOP_MASK); + nrf_rng_task_trigger(NRF_RNG, NRF_RNG_TASK_START); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_a = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_b = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_c = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_d = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + nrf_rng_task_trigger(NRF_RNG, NRF_RNG_TASK_STOP); + return rb_a << 24 | rb_b << 16 | rb_c << 8 | rb_d; + } +#endif + +#if HAS_NP == true + #include + #define NUMPIXELS 1 + Adafruit_NeoPixel pixels(NUMPIXELS, pin_np, NEO_GRB + NEO_KHZ800); + + uint8_t npr = 0; + uint8_t npg = 0; + uint8_t npb = 0; + float npi = NP_M; + bool pixels_started = false; + + void led_set_intensity(uint8_t intensity) { + npi = (float)intensity/255.0; + } + + void led_init() { + #if BOARD_MODEL == BOARD_HELTEC_T114 + // Enable vext power supply to neopixel + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, HIGH); + #endif + + #if MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_PSET)) == CONF_OK_BYTE) { + uint8_t int_val = eeprom_read(eeprom_addr(ADDR_CONF_PINT)); + led_set_intensity(int_val); + } + #else + if (EEPROM.read(eeprom_addr(ADDR_CONF_PSET)) == CONF_OK_BYTE) { + uint8_t int_val = EEPROM.read(eeprom_addr(ADDR_CONF_PINT)); + led_set_intensity(int_val); + } + #endif + } + + void npset(uint8_t r, uint8_t g, uint8_t b) { + if (pixels_started != true) { + pixels.begin(); + pixels_started = true; + } + + if (r != npr || g != npg || b != npb) { + npr = r; npg = g; npb = b; + pixels.setPixelColor(0, pixels.Color(npr*npi, npg*npi, npb*npi)); + pixels.show(); + } + } + + void boot_seq() { + uint8_t rs[] = { 0x00, 0x00, 0x00 }; + uint8_t gs[] = { 0x10, 0x08, 0x00 }; + uint8_t bs[] = { 0x00, 0x08, 0x10 }; + for (int i = 0; i < 1*sizeof(rs); i++) { + npset(rs[i%sizeof(rs)], gs[i%sizeof(gs)], bs[i%sizeof(bs)]); + delay(33); + npset(0x00, 0x00, 0x00); + delay(66); + } + } +#else + void boot_seq() { } +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + 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 MCU_VARIANT == MCU_ESP32 + #if HAS_NP == true + void led_rx_on() { npset(0, 0, 0xFF); } + void led_rx_off() { npset(0, 0, 0); } + void led_tx_on() { npset(0xFF, 0x50, 0x00); } + void led_tx_off() { npset(0, 0, 0); } + void led_id_on() { npset(0x90, 0, 0x70); } + void led_id_off() { npset(0, 0, 0); } + #elif BOARD_MODEL == BOARD_RNODE_NG_20 + 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_RNODE_NG_21 + 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_T3S3 + 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_TBEAM + 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, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TDECK + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + #if defined(EXTERNAL_LEDS) + 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() { } + #else + 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 BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + 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() { } + #else + void led_rx_on() { digitalWrite(pin_led_rx, LOW); } + void led_rx_off() { digitalWrite(pin_led_rx, HIGH); } + void led_tx_on() { digitalWrite(pin_led_tx, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #endif + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + #if defined(EXTERNAL_LEDS) + 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() { } + #else + 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 BOARD_MODEL == BOARD_HELTEC32_V3 + 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_HELTEC32_V4 + 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_LORA32_V2_1 + 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_XIAO_S3 + void led_rx_on() { digitalWrite(pin_led_rx, LED_ON); } + void led_rx_off() { digitalWrite(pin_led_rx, LED_OFF); } + void led_tx_on() { digitalWrite(pin_led_tx, LED_ON); } + void led_tx_off() { digitalWrite(pin_led_tx, LED_OFF); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_HUZZAH32 + 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_GENERIC_ESP32 + 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 + void led_rx_on() { npset(0, 0, 0xFF); } + void led_rx_off() { npset(0, 0, 0); } + void led_tx_on() { npset(0xFF, 0x50, 0x00); } + void led_tx_off() { npset(0, 0, 0); } + void led_id_on() { npset(0x90, 0, 0x70); } + void led_id_off() { npset(0, 0, 0); } + #elif BOARD_MODEL == BOARD_RAK4631 + 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); } + void led_tx_on() { digitalWrite(pin_led_tx, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TECHO + void led_rx_on() { digitalWrite(pin_led_rx, LED_ON); } + void led_rx_off() { digitalWrite(pin_led_rx, LED_OFF); } + void led_tx_on() { digitalWrite(pin_led_tx, LED_ON); } + void led_tx_off() { digitalWrite(pin_led_tx, LED_OFF); } + void led_id_on() { } + void led_id_off() { } + #endif +#endif + +void hard_reset(void) { + #if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + wdt_enable(WDTO_15MS); + while(true) { + led_tx_on(); led_rx_off(); + } + #elif MCU_VARIANT == MCU_ESP32 + ESP.restart(); + #elif MCU_VARIANT == MCU_NRF52 + NVIC_SystemReset(); + #endif +} + +// LED Indication: Error +void led_indicate_error(int cycles) { + #if HAS_NP == true + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0xFF, 0x00, 0x00); + delay(100); + npset(0xFF, 0x50, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + #else + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + digitalWrite(pin_led_rx, HIGH); + digitalWrite(pin_led_tx, LOW); + delay(100); + digitalWrite(pin_led_rx, LOW); + digitalWrite(pin_led_tx, HIGH); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + led_tx_off(); + #endif +} + +// LED Indication: Airtime Lock +void led_indicate_airtime_lock() { + #if HAS_NP == true + npset(32,0,2); + #endif +} + +// LED Indication: Boot Error +void led_indicate_boot_error() { + #if HAS_NP == true + while(true) { + npset(0xFF, 0xFF, 0xFF); + } + #else + while (true) { + led_tx_on(); + led_rx_off(); + delay(10); + led_rx_on(); + led_tx_off(); + delay(5); + } + #endif +} + +// LED Indication: Warning +void led_indicate_warning(int cycles) { + #if HAS_NP == true + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0xFF, 0x50, 0x00); + delay(100); + npset(0x00, 0x00, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + #else + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + digitalWrite(pin_led_tx, HIGH); + while(cycles > 0) { + led_tx_off(); + delay(100); + led_tx_on(); + delay(100); + if (!forever) cycles--; + } + led_tx_off(); + #endif +} + +// LED Indication: Info +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0x00, 0x00, 0xFF); + delay(100); + npset(0x00, 0x00, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + } + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #elif BOARD_MODEL == BOARD_TECHO + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #else + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_tx_off(); + delay(100); + led_tx_on(); + delay(100); + if (!forever) cycles--; + } + led_tx_off(); + } + #endif +#endif + + +unsigned long led_standby_ticks = 0; +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + uint8_t led_standby_min = 1; + uint8_t led_standby_max = 40; + unsigned long led_standby_wait = 11000; + +#elif MCU_VARIANT == MCU_ESP32 + + #if HAS_NP == true + int led_standby_lng = 200; + int led_standby_cut = 100; + int led_standby_min = 0; + int led_standby_max = 375+led_standby_lng; + int led_notready_min = 0; + int led_notready_max = led_standby_max; + int led_notready_value = led_notready_min; + int8_t led_notready_direction = 0; + unsigned long led_notready_ticks = 0; + unsigned long led_standby_wait = 350; + unsigned long led_console_wait = 1; + unsigned long led_notready_wait = 200; + + #else + uint8_t led_standby_min = 200; + uint8_t led_standby_max = 255; + uint8_t led_notready_min = 0; + uint8_t led_notready_max = 255; + uint8_t led_notready_value = led_notready_min; + int8_t led_notready_direction = 0; + unsigned long led_notready_ticks = 0; + unsigned long led_standby_wait = 1768; + unsigned long led_notready_wait = 150; + #endif + +#elif MCU_VARIANT == MCU_NRF52 + int led_standby_lng = 200; + int led_standby_cut = 100; + uint8_t led_standby_min = 200; + uint8_t led_standby_max = 255; + uint8_t led_notready_min = 0; + uint8_t led_notready_max = 255; + uint8_t led_notready_value = led_notready_min; + int8_t led_notready_direction = 0; + unsigned long led_notready_ticks = 0; + unsigned long led_standby_wait = 1768; + unsigned long led_notready_wait = 150; +#endif + +unsigned long led_standby_value = led_standby_min; +int8_t led_standby_direction = 0; + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_standby() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + analogWrite(pin_led_rx, led_standby_value); + led_tx_off(); + } + } + +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_indicate_standby() { + led_standby_ticks++; + + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + + uint8_t led_standby_intensity; + led_standby_value += led_standby_direction; + int led_standby_ti = led_standby_value - led_standby_lng; + + if (led_standby_ti < 0) { + led_standby_intensity = 0; + } else if (led_standby_ti > led_standby_cut) { + led_standby_intensity = led_standby_cut; + } else { + led_standby_intensity = led_standby_ti; + } + npset(led_standby_intensity/3, led_standby_intensity/3, led_standby_intensity/3); + } + } + + void led_indicate_console() { + npset(0x60, 0x00, 0x60); + // led_standby_ticks++; + + // if (led_standby_ticks > led_console_wait) { + // led_standby_ticks = 0; + + // if (led_standby_value <= led_standby_min) { + // led_standby_direction = 1; + // } else if (led_standby_value >= led_standby_max) { + // led_standby_direction = -1; + // } + + // uint8_t led_standby_intensity; + // led_standby_value += led_standby_direction; + // int led_standby_ti = led_standby_value - led_standby_lng; + + // if (led_standby_ti < 0) { + // led_standby_intensity = 0; + // } else if (led_standby_ti > led_standby_cut) { + // led_standby_intensity = led_standby_cut; + // } else { + // led_standby_intensity = led_standby_ti; + // } + // npset(led_standby_intensity, 0x00, led_standby_intensity); + // } + } + + #else + void led_indicate_standby() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + if (led_standby_value > 253) { + #if BOARD_MODEL == BOARD_TECHO + led_rx_on(); + #else + led_tx_on(); + #endif + } else { + #if BOARD_MODEL == BOARD_TECHO + led_rx_off(); + #else + led_tx_off(); + #endif + } + #if BOARD_MODEL == BOARD_LORA32_V2_1 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #else + led_rx_off(); + #endif + } + } + + void led_indicate_console() { + led_indicate_standby(); + } + #endif +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_not_ready() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + analogWrite(pin_led_tx, led_standby_value); + led_rx_off(); + } + } +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_indicate_not_ready() { + led_standby_ticks++; + + if (led_standby_ticks > led_notready_wait) { + led_standby_ticks = 0; + + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + + uint8_t led_standby_intensity; + led_standby_value += led_standby_direction; + int led_standby_ti = led_standby_value - led_standby_lng; + + if (led_standby_ti < 0) { + led_standby_intensity = 0; + } else if (led_standby_ti > led_standby_cut) { + led_standby_intensity = led_standby_cut; + } else { + led_standby_intensity = led_standby_ti; + } + + npset(led_standby_intensity, 0x00, 0x00); + } + } + #else + void led_indicate_not_ready() { + led_notready_ticks++; + if (led_notready_ticks > led_notready_wait) { + led_notready_ticks = 0; + if (led_notready_value <= led_notready_min) { + led_notready_direction = 1; + } else if (led_notready_value >= led_notready_max) { + led_notready_direction = -1; + } + led_notready_value += led_notready_direction; + if (led_notready_value > 128) { + led_tx_on(); + } else { + led_tx_off(); + } + #if BOARD_MODEL == BOARD_LORA32_V2_1 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #else + led_rx_off(); + #endif + } + } + #endif +#endif + +void serial_write(uint8_t byte) { + #if HAS_BLUETOOTH || HAS_BLE == true + if (bt_state != BT_STATE_CONNECTED) { + #if HAS_WIFI + if (wifi_host_is_connected()) { wifi_remote_write(byte); } + else { Serial.write(byte); } + #else + Serial.write(byte); + #endif + } else { + SerialBT.write(byte); + #if MCU_VARIANT == MCU_NRF52 && HAS_BLE + // This ensures that the TX buffer is flushed after a frame is queued in serial. + // serial_in_frame is used to ensure that the flush only happens at the end of the frame + if (serial_in_frame && byte == FEND) { SerialBT.flushTXD(); serial_in_frame = false; } + else if (!serial_in_frame && byte == FEND) { serial_in_frame = true; } + #endif + } + #else + Serial.write(byte); + #endif +} + +void escaped_serial_write(uint8_t byte) { + if (byte == FEND) { serial_write(FESC); byte = TFEND; } + if (byte == FESC) { serial_write(FESC); byte = TFESC; } + serial_write(byte); +} + +void kiss_indicate_reset() { + serial_write(FEND); + serial_write(CMD_RESET); + serial_write(CMD_RESET_BYTE); + serial_write(FEND); +} + +void kiss_indicate_error(uint8_t error_code) { + serial_write(FEND); + serial_write(CMD_ERROR); + serial_write(error_code); + serial_write(FEND); +} + +void kiss_indicate_radiostate() { + serial_write(FEND); + serial_write(CMD_RADIO_STATE); + serial_write(radio_online); + serial_write(FEND); +} + +void kiss_indicate_stat_rx() { + serial_write(FEND); + serial_write(CMD_STAT_RX); + escaped_serial_write(stat_rx>>24); + escaped_serial_write(stat_rx>>16); + escaped_serial_write(stat_rx>>8); + escaped_serial_write(stat_rx); + serial_write(FEND); +} + +void kiss_indicate_stat_tx() { + serial_write(FEND); + serial_write(CMD_STAT_TX); + escaped_serial_write(stat_tx>>24); + escaped_serial_write(stat_tx>>16); + escaped_serial_write(stat_tx>>8); + escaped_serial_write(stat_tx); + serial_write(FEND); +} + +void kiss_indicate_stat_rssi() { + uint8_t packet_rssi_val = (uint8_t)(last_rssi+rssi_offset); + serial_write(FEND); + serial_write(CMD_STAT_RSSI); + escaped_serial_write(packet_rssi_val); + serial_write(FEND); +} + +void kiss_indicate_stat_snr() { + serial_write(FEND); + serial_write(CMD_STAT_SNR); + escaped_serial_write(last_snr_raw); + serial_write(FEND); +} + +void kiss_indicate_radio_lock() { + serial_write(FEND); + serial_write(CMD_RADIO_LOCK); + serial_write(radio_locked); + serial_write(FEND); +} + +void kiss_indicate_spreadingfactor() { + serial_write(FEND); + serial_write(CMD_SF); + serial_write((uint8_t)lora_sf); + serial_write(FEND); +} + +void kiss_indicate_codingrate() { + serial_write(FEND); + serial_write(CMD_CR); + serial_write((uint8_t)lora_cr); + serial_write(FEND); +} + +void kiss_indicate_implicit_length() { + serial_write(FEND); + serial_write(CMD_IMPLICIT); + serial_write(implicit_l); + serial_write(FEND); +} + +void kiss_indicate_txpower() { + serial_write(FEND); + serial_write(CMD_TXPOWER); + serial_write((uint8_t)lora_txp); + serial_write(FEND); +} + +void kiss_indicate_bandwidth() { + serial_write(FEND); + serial_write(CMD_BANDWIDTH); + escaped_serial_write(lora_bw>>24); + escaped_serial_write(lora_bw>>16); + escaped_serial_write(lora_bw>>8); + escaped_serial_write(lora_bw); + serial_write(FEND); +} + +void kiss_indicate_frequency() { + serial_write(FEND); + serial_write(CMD_FREQUENCY); + escaped_serial_write(lora_freq>>24); + escaped_serial_write(lora_freq>>16); + escaped_serial_write(lora_freq>>8); + escaped_serial_write(lora_freq); + serial_write(FEND); +} + +void kiss_indicate_st_alock() { + uint16_t at = (uint16_t)(st_airtime_limit*100*100); + serial_write(FEND); + serial_write(CMD_ST_ALOCK); + escaped_serial_write(at>>8); + escaped_serial_write(at); + serial_write(FEND); +} + +void kiss_indicate_lt_alock() { + uint16_t at = (uint16_t)(lt_airtime_limit*100*100); + serial_write(FEND); + serial_write(CMD_LT_ALOCK); + escaped_serial_write(at>>8); + escaped_serial_write(at); + serial_write(FEND); +} + +void kiss_indicate_channel_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t ats = (uint16_t)(airtime*100*100); + uint16_t atl = (uint16_t)(longterm_airtime*100*100); + uint16_t cls = (uint16_t)(total_channel_util*100*100); + uint16_t cll = (uint16_t)(longterm_channel_util*100*100); + uint8_t crs = (uint8_t)(current_rssi+rssi_offset); + uint8_t nfl = (uint8_t)(noise_floor+rssi_offset); + uint8_t ntf = 0xFF; if (interference_detected) { ntf = (uint8_t)(current_rssi+rssi_offset); } + serial_write(FEND); + serial_write(CMD_STAT_CHTM); + escaped_serial_write(ats>>8); + escaped_serial_write(ats); + escaped_serial_write(atl>>8); + escaped_serial_write(atl); + escaped_serial_write(cls>>8); + escaped_serial_write(cls); + escaped_serial_write(cll>>8); + escaped_serial_write(cll); + escaped_serial_write(crs); + escaped_serial_write(nfl); + escaped_serial_write(ntf); + serial_write(FEND); + #endif +} + +void kiss_indicate_csma_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + serial_write(FEND); + serial_write(CMD_STAT_CSMA); + escaped_serial_write(cw_band); + escaped_serial_write(cw_min); + escaped_serial_write(cw_max); + serial_write(FEND); + #endif +} + +void kiss_indicate_phy_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t lst = (uint16_t)(lora_symbol_time_ms*1000); + uint16_t lsr = (uint16_t)(lora_symbol_rate); + uint16_t prs = (uint16_t)(lora_preamble_symbols); + uint16_t prt = (uint16_t)(lora_preamble_time_ms); + uint16_t cst = (uint16_t)(csma_slot_ms); + uint16_t dft = (uint16_t)(difs_ms); + serial_write(FEND); + serial_write(CMD_STAT_PHYPRM); + escaped_serial_write(lst>>8); escaped_serial_write(lst); + escaped_serial_write(lsr>>8); escaped_serial_write(lsr); + escaped_serial_write(prs>>8); escaped_serial_write(prs); + escaped_serial_write(prt>>8); escaped_serial_write(prt); + escaped_serial_write(cst>>8); escaped_serial_write(cst); + escaped_serial_write(dft>>8); escaped_serial_write(dft); + serial_write(FEND); + #endif +} + +void kiss_indicate_battery() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + serial_write(FEND); + serial_write(CMD_STAT_BAT); + escaped_serial_write(battery_state); + escaped_serial_write((uint8_t)int(battery_percent)); + serial_write(FEND); + #endif +} + +void kiss_indicate_temperature() { + #if HAS_PMU + #if MCU_VARIANT == MCU_ESP32 + float pmu_temp = pmu_temperature+PMU_TEMP_OFFSET; + uint8_t temp = (uint8_t)pmu_temp; + serial_write(FEND); + serial_write(CMD_STAT_TEMP); + escaped_serial_write(pmu_temp); + serial_write(FEND); + #endif + #endif +} + +void kiss_indicate_btpin() { + #if HAS_BLUETOOTH || HAS_BLE == true + serial_write(FEND); + serial_write(CMD_BT_PIN); + escaped_serial_write(bt_ssp_pin>>24); + escaped_serial_write(bt_ssp_pin>>16); + escaped_serial_write(bt_ssp_pin>>8); + escaped_serial_write(bt_ssp_pin); + serial_write(FEND); + #endif +} + +void kiss_indicate_random(uint8_t byte) { + serial_write(FEND); + serial_write(CMD_RANDOM); + serial_write(byte); + serial_write(FEND); +} + +void kiss_indicate_fbstate() { + serial_write(FEND); + serial_write(CMD_FB_EXT); + #if HAS_DISPLAY + if (disp_ext_fb) { + serial_write(0x01); + } else { + serial_write(0x00); + } + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + void kiss_indicate_device_hash() { + serial_write(FEND); + serial_write(CMD_DEV_HASH); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_target_fw_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x01); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_firmware_hash_target[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_fw_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x02); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_firmware_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_bootloader_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x03); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_bootloader_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_partition_table_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x04); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_partition_table_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } +#endif + +void kiss_indicate_fb() { + serial_write(FEND); + serial_write(CMD_FB_READ); + #if HAS_DISPLAY + for (int i = 0; i < 512; i++) { + uint8_t byte = fb[i]; + escaped_serial_write(byte); + } + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +void kiss_indicate_disp() { + serial_write(FEND); + serial_write(CMD_DISP_READ); + #if HAS_DISPLAY + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + for (int i = 0; i < 512; i++) { escaped_serial_write(fb[i]); } + for (int i = 0; i < 512; i++) { escaped_serial_write(0x00); } + #else + uint8_t *da = disp_area.getBuffer(); + uint8_t *sa = stat_area.getBuffer(); + for (int i = 0; i < 512; i++) { escaped_serial_write(da[i]); } + for (int i = 0; i < 512; i++) { escaped_serial_write(sa[i]); } + #endif + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +void kiss_indicate_ready() { + serial_write(FEND); + serial_write(CMD_READY); + serial_write(0x01); + serial_write(FEND); +} + +void kiss_indicate_not_ready() { + serial_write(FEND); + serial_write(CMD_READY); + serial_write(0x00); + serial_write(FEND); +} + +void kiss_indicate_promisc() { + serial_write(FEND); + serial_write(CMD_PROMISC); + if (promisc) { + serial_write(0x01); + } else { + serial_write(0x00); + } + serial_write(FEND); +} + +void kiss_indicate_detect() { + serial_write(FEND); + serial_write(CMD_DETECT); + serial_write(DETECT_RESP); + serial_write(FEND); +} + +void kiss_indicate_version() { + serial_write(FEND); + serial_write(CMD_FW_VERSION); + serial_write(MAJ_VERS); + serial_write(MIN_VERS); + serial_write(FEND); +} + +void kiss_indicate_platform() { + serial_write(FEND); + serial_write(CMD_PLATFORM); + serial_write(PLATFORM); + serial_write(FEND); +} + +void kiss_indicate_board() { + serial_write(FEND); + serial_write(CMD_BOARD); + serial_write(BOARD_MODEL); + serial_write(FEND); +} + +void kiss_indicate_mcu() { + serial_write(FEND); + serial_write(CMD_MCU); + serial_write(MCU_VARIANT); + serial_write(FEND); +} + +inline bool isSplitPacket(uint8_t header) { + return (header & FLAG_SPLIT); +} + +inline uint8_t packetSequence(uint8_t header) { + return header >> 4; +} + +void setPreamble() { + if (radio_online) LoRa->setPreambleLength(lora_preamble_symbols); + kiss_indicate_phy_stats(); +} + +void updateBitrate() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (!radio_online) { lora_bitrate = 0; } + else { + lora_symbol_rate = (float)lora_bw/(float)(pow(2, lora_sf)); + lora_symbol_time_ms = (1.0/lora_symbol_rate)*1000.0; + lora_bitrate = (uint32_t)(lora_sf * ( (4.0/(float)lora_cr) / ((float)(pow(2, lora_sf))/((float)lora_bw/1000.0)) ) * 1000.0); + lora_us_per_byte = 1000000.0/((float)lora_bitrate/8.0); + + bool fast_rate = lora_bitrate > LORA_FAST_THRESHOLD_BPS; + lora_limit_rate = lora_bitrate > LORA_LIMIT_THRESHOLD_BPS; + lora_guard_rate = (!lora_limit_rate && lora_bitrate > LORA_GUARD_THRESHOLD_BPS); + + int csma_slot_min_ms = CSMA_SLOT_MIN_MS; + float lora_preamble_target_ms = LORA_PREAMBLE_TARGET_MS; + if (fast_rate) { csma_slot_min_ms -= CSMA_SLOT_MIN_FAST_DELTA; + lora_preamble_target_ms -= LORA_PREAMBLE_FAST_DELTA; } + + csma_slot_ms = lora_symbol_time_ms*CSMA_SLOT_SYMBOLS; + if (csma_slot_ms > CSMA_SLOT_MAX_MS) { csma_slot_ms = CSMA_SLOT_MAX_MS; } + if (csma_slot_ms < CSMA_SLOT_MIN_MS) { csma_slot_ms = csma_slot_min_ms; } + difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms; + + float target_preamble_symbols = lora_preamble_target_ms/lora_symbol_time_ms; + if (target_preamble_symbols < LORA_PREAMBLE_SYMBOLS_MIN) { target_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; } + else { target_preamble_symbols = (ceil)(target_preamble_symbols); } + + lora_preamble_symbols = (long)target_preamble_symbols; setPreamble(); + lora_preamble_time_ms = (ceil)(lora_preamble_symbols * lora_symbol_time_ms); + lora_header_time_ms = (ceil)(PHY_HEADER_LORA_SYMBOLS * lora_symbol_time_ms); + } + #endif +} + +void setSpreadingFactor() { + if (radio_online) LoRa->setSpreadingFactor(lora_sf); + updateBitrate(); +} + +void setCodingRate() { + if (radio_online) LoRa->setCodingRate4(lora_cr); + updateBitrate(); +} + +void set_implicit_length(uint8_t len) { + implicit_l = len; + if (implicit_l != 0) { + implicit = true; + } else { + implicit = false; + } +} + +int getTxPower() { + uint8_t txp = LoRa->getTxPower(); + return (int)txp; +} + +#if HAS_LORA_PA + #if BOARD_MODEL == BOARD_HELTEC32_V4 + bool pa_values_determined = false; + int tx_gain[PA_GAIN_POINTS] = {100}; + #else + bool pa_values_determined = true; + const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES}; + #endif +#endif + +extern uint8_t lora_pa_model; +void determine_pa_values() { + #if BOARD_MODEL == BOARD_HELTEC32_V4 + if (lora_pa_model == LORA_PA_GC1109) { + for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_GC1109_VALUES[i]; } + pa_values_determined = true; + for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); } + } else if (lora_pa_model == LORA_PA_KCT8103L) { + for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_KCT8103L_VALUES[i]; } + pa_values_determined = true; + for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); } + } + #endif +} + +int map_target_power_to_modem_output(int target_tx_power) { + #if HAS_LORA_PA + if (!pa_values_determined) { determine_pa_values(); } + int modem_output_dbm = -9; + for (int i = 0; i < PA_GAIN_POINTS; i++) { + int gain = tx_gain[i]; + int effective_output_dbm = i + gain; + if (effective_output_dbm > target_tx_power) { + int diff = effective_output_dbm - target_tx_power; + modem_output_dbm = -1*diff; + break; + } else if (effective_output_dbm == target_tx_power) { + modem_output_dbm = i; break; + } else if (i == PA_GAIN_POINTS-1) { + int diff = target_tx_power - effective_output_dbm; + modem_output_dbm = i+diff; break; + } + } + #else + int modem_output_dbm = target_tx_power; + #endif + + return modem_output_dbm; +} + +int map_modem_output_to_target_power(int modem_output_dbm) { + #if HAS_LORA_PA + if (modem_output_dbm < 0) { modem_output_dbm = 0; } + if (modem_output_dbm >= PA_GAIN_POINTS) { modem_output_dbm = PA_GAIN_POINTS-1; } + int gain = tx_gain[modem_output_dbm]; + int target_tx_power = modem_output_dbm+gain; + #else + int target_tx_power = modem_output_dbm; + #endif + + return target_tx_power; +} + +void setTXPower() { + if (radio_online) { + int mapped_lora_txp = map_target_power_to_modem_output(lora_txp); + + #if HAS_LORA_PA + int real_lora_txp = map_modem_output_to_target_power(mapped_lora_txp); + lora_txp = real_lora_txp; + #endif + + if (model == MODEL_11) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + 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_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); + if (model == MODEL_A3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_A4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_A5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_AA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_AC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_BA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_BB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_C4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_CA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_D4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_D9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_DB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_DC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_DD) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_DE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_E4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_EC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_FE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_FF) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + } +} + + +void getBandwidth() { + if (radio_online) { + lora_bw = LoRa->getSignalBandwidth(); + } + updateBitrate(); +} + +void setBandwidth() { + if (radio_online) { + LoRa->setSignalBandwidth(lora_bw); + getBandwidth(); + } +} + +void getFrequency() { + if (radio_online) { + lora_freq = LoRa->getFrequency(); + } +} + +void setFrequency() { + if (radio_online) { + LoRa->setFrequency(lora_freq); + getFrequency(); + } +} + +uint8_t getRandom() { return random(0xFF); } + +void promisc_enable() { + promisc = true; +} + +void promisc_disable() { + promisc = false; +} + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + bool eeprom_begin() { + InternalFS.begin(); + + file.open(EEPROM_FILE, FILE_O_READ); + if (!file) { + if (file.open(EEPROM_FILE, FILE_O_WRITE)) { + for (uint32_t mapped_addr = 0; mapped_addr < EEPROM_SIZE; mapped_addr++) { file.seek(mapped_addr); file.write(0xFF); } + eeprom_flush(); + return true; + } else { + return false; + } + } else { + file.close(); + file.open(EEPROM_FILE, FILE_O_WRITE); + return true; + } + } + + uint8_t eeprom_read(uint32_t mapped_addr) { + uint8_t byte; + void* byte_ptr = &byte; + file.seek(mapped_addr); + file.read(byte_ptr, 1); + return byte; + } +#endif + +bool eeprom_info_locked() { + #if HAS_EEPROM + uint8_t lock_byte = EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t lock_byte = eeprom_read(eeprom_addr(ADDR_INFO_LOCK)); + #endif + if (lock_byte == INFO_LOCK_BYTE) { + return true; + } else { + return false; + } +} + +void eeprom_dump_info() { + for (int addr = ADDR_PRODUCT; addr <= ADDR_INFO_LOCK; addr++) { + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif + escaped_serial_write(byte); + } +} + +void eeprom_dump_config() { + for (int addr = ADDR_CONF_SF; addr <= ADDR_CONF_OK; addr++) { + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif + escaped_serial_write(byte); + } +} + +void eeprom_dump_all() { + for (int addr = 0; addr < EEPROM_RESERVED; addr++) { + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif + escaped_serial_write(byte); + } +} + +void eeprom_config_dump_all() { + #if MCU_VARIANT == MCU_ESP32 + for (int addr = 0; addr < CONFIG_SIZE; addr++) { + uint8_t byte = EEPROM.read(config_addr(addr)); + escaped_serial_write(byte); + } + #endif +} + +void kiss_dump_eeprom() { + serial_write(FEND); + serial_write(CMD_ROM_READ); + eeprom_dump_all(); + serial_write(FEND); +} + +void kiss_dump_config() { + serial_write(FEND); + serial_write(CMD_CFG_READ); + eeprom_config_dump_all(); + serial_write(FEND); +} + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 +void eeprom_flush() { + file.close(); + file.open(EEPROM_FILE, FILE_O_WRITE); + written_bytes = 0; +} +#endif + +void eeprom_update(int mapped_addr, uint8_t byte) { + #if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + EEPROM.update(mapped_addr, byte); + #elif MCU_VARIANT == MCU_ESP32 + if (EEPROM.read(mapped_addr) != byte) { + EEPROM.write(mapped_addr, byte); + EEPROM.commit(); + } + #elif !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // todo: clean up this implementation, writing one byte and syncing + // each time is really slow, but this is also suboptimal + uint8_t read_byte; + void* read_byte_ptr = &read_byte; + file.seek(mapped_addr); + file.read(read_byte_ptr, 1); + file.seek(mapped_addr); + if (read_byte != byte) { + file.write(byte); + } + written_bytes++; + eeprom_flush(); + #endif +} + +void eeprom_write(uint8_t addr, uint8_t byte) { + if (!eeprom_info_locked() && addr >= 0 && addr < EEPROM_RESERVED) { + eeprom_update(eeprom_addr(addr), byte); + } else { + kiss_indicate_error(ERROR_EEPROM_LOCKED); + } +} + +void eeprom_erase() { + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + InternalFS.format(); + #else + for (int addr = 0; addr < EEPROM_RESERVED; addr++) { + eeprom_update(eeprom_addr(addr), 0xFF); + } + #endif + hard_reset(); +} + +bool eeprom_lock_set() { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) { + #endif + return true; + } else { + return false; + } +} + +bool eeprom_product_valid() { + #if HAS_EEPROM + uint8_t rval = EEPROM.read(eeprom_addr(ADDR_PRODUCT)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t rval = eeprom_read(eeprom_addr(ADDR_PRODUCT)); + #endif + + #if PLATFORM == PLATFORM_AVR + if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW) { + #elif PLATFORM == PLATFORM_ESP32 + if (rval == PRODUCT_RNODE || rval == BOARD_RNODE_NG_20 || rval == BOARD_RNODE_NG_21 || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_10 || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21 || rval == PRODUCT_H32_V2 || rval == PRODUCT_H32_V3 || rval == PRODUCT_H32_V4 || rval == PRODUCT_TDECK_V1 || rval == PRODUCT_TBEAM_S_V1 || rval == PRODUCT_XIAO_S3 || rval == PRODUCT_CARDPUTER_ADV) { + #elif PLATFORM == PLATFORM_NRF52 + if (rval == PRODUCT_RAK4631 || rval == PRODUCT_HELTEC_T114 || rval == PRODUCT_TECHO || rval == PRODUCT_HMBRW) { + #else + if (false) { + #endif + return true; + } else { + return false; + } +} + +bool eeprom_model_valid() { + #if HAS_EEPROM + model = EEPROM.read(eeprom_addr(ADDR_MODEL)); + #elif MCU_VARIANT == MCU_NRF52 + model = eeprom_read(eeprom_addr(ADDR_MODEL)); + #endif + #if BOARD_MODEL == BOARD_RNODE + if (model == MODEL_A4 || model == MODEL_A9 || model == MODEL_FF || model == MODEL_FE) { + #elif BOARD_MODEL == BOARD_RNODE_NG_20 + if (model == MODEL_A3 || model == MODEL_A8) { + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + if (model == MODEL_A2 || model == MODEL_A7) { + #elif BOARD_MODEL == BOARD_T3S3 + if (model == MODEL_A1 || model == MODEL_A6 || model == MODEL_A5 || model == MODEL_AA || model == MODEL_AC) { + #elif BOARD_MODEL == BOARD_HMBRW + if (model == MODEL_FF || model == MODEL_FE) { + #elif BOARD_MODEL == BOARD_TBEAM + if (model == MODEL_E4 || model == MODEL_E9 || model == MODEL_E3 || model == MODEL_E8) { + #elif BOARD_MODEL == BOARD_TDECK + if (model == MODEL_D4 || model == MODEL_D9) { + #elif BOARD_MODEL == BOARD_TECHO + if (model == MODEL_16 || model == MODEL_17) { + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + if (model == MODEL_DB || model == MODEL_DC) { + #elif BOARD_MODEL == BOARD_XIAO_S3 + if (model == MODEL_DD || model == MODEL_DE) { + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + if (model == MODEL_BA || model == MODEL_BB) { + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + if (model == MODEL_B3 || model == MODEL_B8) { + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + if (model == MODEL_B4 || model == MODEL_B9) { + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + if (model == MODEL_C4 || model == MODEL_C9) { + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + if (model == MODEL_C5 || model == MODEL_CA) { + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + if (model == MODEL_C8) { + #elif BOARD_MODEL == BOARD_HELTEC_T114 + if (model == MODEL_C6 || model == MODEL_C7) { + #elif BOARD_MODEL == BOARD_RAK4631 + if (model == MODEL_11 || model == MODEL_12) { + #elif BOARD_MODEL == BOARD_HUZZAH32 + if (model == MODEL_FF) { + #elif BOARD_MODEL == BOARD_CARDPUTER_ADV + if (model == MODEL_EC || model == MODEL_FF || model == MODEL_FE) { + #elif BOARD_MODEL == BOARD_GENERIC_ESP32 + if (model == MODEL_FF || model == MODEL_FE) { + #else + if (false) { + #endif + return true; + } else { + return false; + } +} + +bool eeprom_hwrev_valid() { + #if HAS_EEPROM + hwrev = EEPROM.read(eeprom_addr(ADDR_HW_REV)); + #elif MCU_VARIANT == MCU_NRF52 + hwrev = eeprom_read(eeprom_addr(ADDR_HW_REV)); + #endif + if (hwrev != 0x00 && hwrev != 0xFF) { + return true; + } else { + return false; + } +} + +bool eeprom_checksum_valid() { + char *data = (char*)malloc(CHECKSUMMED_SIZE); + for (uint8_t i = 0; i < CHECKSUMMED_SIZE; i++) { + #if HAS_EEPROM + char byte = EEPROM.read(eeprom_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + char byte = eeprom_read(eeprom_addr(i)); + #endif + data[i] = byte; + } + + unsigned char *hash = MD5::make_hash(data, CHECKSUMMED_SIZE); + bool checksum_valid = true; + for (uint8_t i = 0; i < 16; i++) { + #if HAS_EEPROM + uint8_t stored_chk_byte = EEPROM.read(eeprom_addr(ADDR_CHKSUM+i)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t stored_chk_byte = eeprom_read(eeprom_addr(ADDR_CHKSUM+i)); + #endif + uint8_t calced_chk_byte = (uint8_t)hash[i]; + if (stored_chk_byte != calced_chk_byte) { + checksum_valid = false; + } + } + + free(hash); + free(data); + return checksum_valid; +} + +void wr_conf_save(uint8_t mode) { + eeprom_update(eeprom_addr(ADDR_CONF_WIFI), mode); + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // have to do a flush because we're only writing 1 byte and it syncs after 8 + eeprom_flush(); + #endif +} + +void bt_conf_save(bool is_enabled) { + if (is_enabled) { + eeprom_update(eeprom_addr(ADDR_CONF_BT), BT_ENABLE_BYTE); + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // have to do a flush because we're only writing 1 byte and it syncs after 8 + eeprom_flush(); + #endif + } else { + eeprom_update(eeprom_addr(ADDR_CONF_BT), 0x00); + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // have to do a flush because we're only writing 1 byte and it syncs after 8 + eeprom_flush(); + #endif + } +} + +void di_conf_save(uint8_t dint) { + eeprom_update(eeprom_addr(ADDR_CONF_DINT), dint); +} + +void da_conf_save(uint8_t dadr) { + eeprom_update(eeprom_addr(ADDR_CONF_DADR), dadr); +} + +void db_conf_save(uint8_t val) { + #if HAS_DISPLAY + if (val == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = val*1000; + } + eeprom_update(eeprom_addr(ADDR_CONF_BSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_DBLK), val); + #endif +} + +void drot_conf_save(uint8_t val) { + #if HAS_DISPLAY + if (val >= 0x00 and val <= 0x03) { + eeprom_update(eeprom_addr(ADDR_CONF_DROT), val); + hard_reset(); + } + #endif +} + +void dia_conf_save(uint8_t val) { + if (val > 0x00) { eeprom_update(eeprom_addr(ADDR_CONF_DIA), 0x01); } + else { eeprom_update(eeprom_addr(ADDR_CONF_DIA), 0x00); } + hard_reset(); +} + +void np_int_conf_save(uint8_t p_int) { + eeprom_update(eeprom_addr(ADDR_CONF_PSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_PINT), p_int); +} + + +bool eeprom_have_conf() { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) { + #endif + return true; + } else { + return false; + } +} + +// Self-provisioning shared by the Cardputer Adv and T-Deck dual-boot builds. +#if BOARD_MODEL == BOARD_CARDPUTER_ADV || BOARD_MODEL == BOARD_TDECK + #if BOARD_MODEL == BOARD_TDECK + #define SELF_PROVISION_PRODUCT PRODUCT_TDECK_V1 + #define SELF_PROVISION_MODEL MODEL_D9 + #else + #define SELF_PROVISION_PRODUCT PRODUCT_CARDPUTER_ADV + #define SELF_PROVISION_MODEL MODEL_EC + #endif + + #ifndef CARDPUTER_ADV_DEFAULT_FREQ + #define CARDPUTER_ADV_DEFAULT_FREQ 915000000UL + #endif + + #ifndef CARDPUTER_ADV_DEFAULT_BW + #define CARDPUTER_ADV_DEFAULT_BW 250000UL + #endif + + #ifndef CARDPUTER_ADV_DEFAULT_SF + #define CARDPUTER_ADV_DEFAULT_SF 11 + #endif + + #ifndef CARDPUTER_ADV_DEFAULT_CR + #define CARDPUTER_ADV_DEFAULT_CR 5 + #endif + + #ifndef CARDPUTER_ADV_DEFAULT_TXP + #define CARDPUTER_ADV_DEFAULT_TXP 14 + #endif + + uint8_t cardputer_adv_eeprom_read(uint8_t addr) { + #if HAS_EEPROM + return EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + return eeprom_read(eeprom_addr(addr)); + #else + return 0xFF; + #endif + } + + void cardputer_adv_eeprom_write_u32(uint8_t addr, uint32_t value) { + eeprom_update(eeprom_addr(addr)+0x00, value>>24); + eeprom_update(eeprom_addr(addr)+0x01, value>>16); + eeprom_update(eeprom_addr(addr)+0x02, value>>8); + eeprom_update(eeprom_addr(addr)+0x03, value); + } + + void cardputer_adv_write_info() { + uint8_t info[CHECKSUMMED_SIZE]; + uint64_t mac = 0; + + #if MCU_VARIANT == MCU_ESP32 + mac = ESP.getEfuseMac(); + #endif + + uint32_t serial = (uint32_t)(mac & 0xFFFFFFFF); + + info[ADDR_PRODUCT] = SELF_PROVISION_PRODUCT; + info[ADDR_MODEL] = SELF_PROVISION_MODEL; + info[ADDR_HW_REV] = 0x01; + info[ADDR_SERIAL+0x00] = serial>>24; + info[ADDR_SERIAL+0x01] = serial>>16; + info[ADDR_SERIAL+0x02] = serial>>8; + info[ADDR_SERIAL+0x03] = serial; + info[ADDR_MADE+0x00] = 0x00; + info[ADDR_MADE+0x01] = 0x00; + info[ADDR_MADE+0x02] = 0x00; + info[ADDR_MADE+0x03] = 0x00; + + for (uint8_t i = 0; i < CHECKSUMMED_SIZE; i++) { + eeprom_update(eeprom_addr(i), info[i]); + } + + unsigned char *hash = MD5::make_hash((char*)info, CHECKSUMMED_SIZE); + for (uint8_t i = 0; i < 16; i++) { + eeprom_update(eeprom_addr(ADDR_CHKSUM+i), hash[i]); + } + free(hash); + + eeprom_update(eeprom_addr(ADDR_INFO_LOCK), INFO_LOCK_BYTE); + } + + void cardputer_adv_write_default_conf() { + eeprom_update(eeprom_addr(ADDR_CONF_SF), CARDPUTER_ADV_DEFAULT_SF); + eeprom_update(eeprom_addr(ADDR_CONF_CR), CARDPUTER_ADV_DEFAULT_CR); + eeprom_update(eeprom_addr(ADDR_CONF_TXP), CARDPUTER_ADV_DEFAULT_TXP); + eeprom_update(eeprom_addr(ADDR_CONF_BT), BT_ENABLE_BYTE); + cardputer_adv_eeprom_write_u32(ADDR_CONF_BW, CARDPUTER_ADV_DEFAULT_BW); + cardputer_adv_eeprom_write_u32(ADDR_CONF_FREQ, CARDPUTER_ADV_DEFAULT_FREQ); + eeprom_update(eeprom_addr(ADDR_CONF_OK), CONF_OK_BYTE); + } + + void cardputer_adv_write_default_ui_conf() { + if (cardputer_adv_eeprom_read(ADDR_CONF_DSET) != CONF_OK_BYTE) { + eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF); + } + + if (cardputer_adv_eeprom_read(ADDR_CONF_DIA) == 0xFF) { + eeprom_update(eeprom_addr(ADDR_CONF_DIA), 0x00); + } + } + + bool cardputer_adv_info_valid() { + return eeprom_lock_set() && + cardputer_adv_eeprom_read(ADDR_PRODUCT) == SELF_PROVISION_PRODUCT && + cardputer_adv_eeprom_read(ADDR_MODEL) == SELF_PROVISION_MODEL && + eeprom_hwrev_valid() && + eeprom_checksum_valid(); + } + + void cardputer_adv_self_provision() { + bool info_valid = cardputer_adv_info_valid(); + + if (!info_valid) { + cardputer_adv_write_info(); + cardputer_adv_write_default_conf(); + } + + if (info_valid && !eeprom_have_conf()) { + cardputer_adv_write_default_conf(); + } + + cardputer_adv_write_default_ui_conf(); + } +#endif + +void eeprom_conf_load() { + if (eeprom_have_conf()) { + #if HAS_EEPROM + lora_sf = EEPROM.read(eeprom_addr(ADDR_CONF_SF)); + lora_cr = EEPROM.read(eeprom_addr(ADDR_CONF_CR)); + lora_txp = EEPROM.read(eeprom_addr(ADDR_CONF_TXP)); + lora_freq = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x03); + lora_bw = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x03); + #elif MCU_VARIANT == MCU_NRF52 + lora_sf = eeprom_read(eeprom_addr(ADDR_CONF_SF)); + lora_cr = eeprom_read(eeprom_addr(ADDR_CONF_CR)); + lora_txp = eeprom_read(eeprom_addr(ADDR_CONF_TXP)); + lora_freq = (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x03); + lora_bw = (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x03); + #endif + } +} + +void eeprom_conf_save() { + if (hw_ready && radio_online) { + eeprom_update(eeprom_addr(ADDR_CONF_SF), lora_sf); + eeprom_update(eeprom_addr(ADDR_CONF_CR), lora_cr); + eeprom_update(eeprom_addr(ADDR_CONF_TXP), lora_txp); + + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x00, lora_bw>>24); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x01, lora_bw>>16); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x02, lora_bw>>8); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x03, lora_bw); + + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x00, lora_freq>>24); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x01, lora_freq>>16); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x02, lora_freq>>8); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x03, lora_freq); + + eeprom_update(eeprom_addr(ADDR_CONF_OK), CONF_OK_BYTE); + led_indicate_info(10); + } else { + led_indicate_warning(10); + } +} + +void eeprom_conf_delete() { + eeprom_update(eeprom_addr(ADDR_CONF_OK), 0x00); +} + +void unlock_rom() { + led_indicate_error(50); + eeprom_erase(); +} + +void init_channel_stats() { + #if MCU_VARIANT == MCU_ESP32 + for (uint16_t ai = 0; ai < DCD_SAMPLES; ai++) { util_samples[ai] = false; } + for (uint16_t ai = 0; ai < AIRTIME_BINS; ai++) { airtime_bins[ai] = 0; } + for (uint16_t ai = 0; ai < AIRTIME_BINS; ai++) { longterm_bins[ai] = 0.0; } + local_channel_util = 0.0; + total_channel_util = 0.0; + airtime = 0.0; + longterm_airtime = 0.0; + #endif +} + +typedef struct FIFOBuffer +{ + unsigned char *begin; + unsigned char *end; + unsigned char * volatile head; + unsigned char * volatile tail; +} FIFOBuffer; + +inline bool fifo_isempty(const FIFOBuffer *f) { + return f->head == f->tail; +} + +inline bool fifo_isfull(const FIFOBuffer *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + +inline void fifo_push(FIFOBuffer *f, unsigned char c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + +inline unsigned char fifo_pop(FIFOBuffer *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + +inline void fifo_flush(FIFOBuffer *f) { + f->head = f->tail; +} + +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + static inline bool fifo_isempty_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isempty(f); + } + return result; + } + + static inline bool fifo_isfull_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isfull(f); + } + return result; + } + + static inline void fifo_push_locked(FIFOBuffer *f, unsigned char c) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + fifo_push(f, c); + } + } +#endif + +/* +static inline unsigned char fifo_pop_locked(FIFOBuffer *f) { + unsigned char c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = fifo_pop(f); + } + return c; +} +*/ + +inline void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + +inline size_t fifo_len(FIFOBuffer *f) { + return f->end - f->begin; +} + +typedef struct FIFOBuffer16 +{ + uint16_t *begin; + uint16_t *end; + uint16_t * volatile head; + uint16_t * volatile tail; +} FIFOBuffer16; + +inline bool fifo16_isempty(const FIFOBuffer16 *f) { + return f->head == f->tail; +} + +inline bool fifo16_isfull(const FIFOBuffer16 *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + +inline void fifo16_push(FIFOBuffer16 *f, uint16_t c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + +inline uint16_t fifo16_pop(FIFOBuffer16 *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + +inline void fifo16_flush(FIFOBuffer16 *f) { + f->head = f->tail; +} + +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo16_isempty(f); + } + + return result; + } +#endif + +/* +static inline bool fifo16_isfull_locked(const FIFOBuffer16 *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo16_isfull(f); + } + return result; +} + + +static inline void fifo16_push_locked(FIFOBuffer16 *f, uint16_t c) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + fifo16_push(f, c); + } +} + +static inline size_t fifo16_pop_locked(FIFOBuffer16 *f) { + size_t c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = fifo16_pop(f); + } + return c; +} +*/ + +inline void fifo16_init(FIFOBuffer16 *f, uint16_t *buffer, uint16_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + +inline uint16_t fifo16_len(FIFOBuffer16 *f) { + return (f->end - f->begin); +} + +extern void stopRadio(); +void host_disconnected() { + stopRadio(); + cable_state = CABLE_STATE_DISCONNECTED; + current_rssi = -292; + last_rssi = -292; + last_rssi_raw = 0x00; + last_snr_raw = 0x80; +} diff --git a/vendor/rnode_firmware/arduino-cli.yaml b/vendor/rnode_firmware/arduino-cli.yaml new file mode 100644 index 0000000..d358070 --- /dev/null +++ b/vendor/rnode_firmware/arduino-cli.yaml @@ -0,0 +1,6 @@ +board_manager: + additional_urls: + - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + - 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 diff --git a/vendor/rnode_firmware/esp32_btbufs.py b/vendor/rnode_firmware/esp32_btbufs.py new file mode 100755 index 0000000..ad0ae70 --- /dev/null +++ b/vendor/rnode_firmware/esp32_btbufs.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import sys +import re + +try: + target_path = sys.argv[1] + rxbuf_size = 0; rxbuf_minsize = 6144 + txbuf_size = 0; txbuf_minsize = 384 + line_index = 0 + rx_line_index = 0 + tx_line_index = 0 + with open(target_path) as sf: + for line in sf: + line_index += 1 + if line.startswith("#define RX_QUEUE_SIZE"): + ents = re.sub(" +", " ", line).split(" ") + try: + rxbuf_size = int(ents[2]) + rx_line_index = line_index + except Exception as e: + print(f"Could not parse Bluetooth RX_QUEUE_SIZE: {e}") + + if line.startswith("#define TX_QUEUE_SIZE"): + ents = re.sub(" +", " ", line).split(" ") + try: + txbuf_size = int(ents[2]) + tx_line_index = line_index + except Exception as e: + print(f"Could not parse Bluetooth TX_QUEUE_SIZE: {e}") + + if rxbuf_size != 0 and txbuf_size != 0: + break + + if rxbuf_size < rxbuf_minsize: + print(f"Error: The configured ESP32 Bluetooth RX buffer size is too small, please set it to at least {rxbuf_minsize} and try compiling again.") + print(f"The buffer configuration can be modified in line {rx_line_index} of: {target_path}") + exit(1) + + if txbuf_size < txbuf_minsize: + print(f"Error: The configured ESP32 Bluetooth TX buffer size is too small, please set it to at least {txbuf_minsize} and try compiling again.") + print(f"The buffer configuration can be modified in line {tx_line_index} of: {target_path}") + exit(1) + + exit(0) + +except Exception as e: + print(f"Could not determine ESP32 Bluetooth buffer configuration: {e}") + print("Please fix this error and try again") \ No newline at end of file diff --git a/vendor/rnode_firmware/partition_hashes b/vendor/rnode_firmware/partition_hashes new file mode 100755 index 0000000..9d8db4b --- /dev/null +++ b/vendor/rnode_firmware/partition_hashes @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# Copyright (C) 2024, Mark Qvist + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import sys +import RNS +import json +import hashlib +import subprocess + +major_version = None +minor_version = None +target_version = None + +target_file = os.path.join(sys.argv[1]) + +if sys.argv[1] == "from_device": + from_device = True +else: + from_device = False + +if not from_device: + firmware_data = open(target_file, "rb").read() + calc_hash = hashlib.sha256(firmware_data[0:-32]).digest() + part_hash = firmware_data[-32:] + + if calc_hash == part_hash: + print(RNS.hexrep(part_hash, delimit=False)) + +else: + try: + cmdresult = subprocess.run(["rnodeconf", sys.argv[2], "-L"], stdout=subprocess.PIPE).stdout.decode('utf-8') + part_hash = cmdresult.split("The actual firmware hash is: ")[1].replace("\n", "") + print(part_hash) + except Exception as e: + print("Could not get partition hash from device: "+str(e)) diff --git a/vendor/rnode_firmware/release_hashes.py b/vendor/rnode_firmware/release_hashes.py new file mode 100644 index 0000000..3c08ca9 --- /dev/null +++ b/vendor/rnode_firmware/release_hashes.py @@ -0,0 +1,50 @@ +#!/bin/python + +# Copyright (C) 2024, Mark Qvist + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import json +import hashlib + +major_version = None +minor_version = None +target_version = None + +file = open("Config.h", "rb") +config_data = file.read().splitlines() +for line in config_data: + dline = line.decode("utf-8").strip() + components = dline.split() + if dline.startswith("#define MAJ_VERS"): + major_version = "%01d" % ord(bytes.fromhex(dline.split()[2].split("x")[1])) + if dline.startswith("#define MIN_VERS"): + minor_version = "%02d" % ord(bytes.fromhex(dline.split()[2].split("x")[1])) + +target_version = major_version+"."+minor_version + +release_hashes = {} +target_dir = "./Release" +files = os.listdir(target_dir) +for filename in files: + if os.path.isfile(os.path.join(target_dir, filename)): + if filename.startswith("rnode_firmware"): + file = open(os.path.join(target_dir, filename), "rb") + release_hashes[filename] = { + "hash": hashlib.sha256(file.read()).hexdigest(), + "version": target_version + } + +print(json.dumps(release_hashes)) \ No newline at end of file diff --git a/vendor/rnode_firmware/sx126x.cpp b/vendor/rnode_firmware/sx126x.cpp new file mode 100644 index 0000000..5be7b64 --- /dev/null +++ b/vendor/rnode_firmware/sx126x.cpp @@ -0,0 +1,971 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1262 +#include "sx126x.h" + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + #include + #include +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "soc/rtc_wdt.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +#define OP_RF_FREQ_6X 0x86 +#define OP_SLEEP_6X 0x84 +#define OP_STANDBY_6X 0x80 +#define OP_TX_6X 0x83 +#define OP_RX_6X 0x82 +#define OP_PA_CONFIG_6X 0x95 +#define OP_SET_IRQ_FLAGS_6X 0x08 // Also provides info such as + // preamble detection, etc for + // knowing when it's safe to switch + // antenna modes +#define OP_CLEAR_IRQ_STATUS_6X 0x02 +#define OP_GET_IRQ_STATUS_6X 0x12 +#define OP_RX_BUFFER_STATUS_6X 0x13 +#define OP_PACKET_STATUS_6X 0x14 // Get snr & rssi of last packet +#define OP_CURRENT_RSSI_6X 0x15 +#define OP_MODULATION_PARAMS_6X 0x8B // BW, SF, CR, etc. +#define OP_PACKET_PARAMS_6X 0x8C // CRC, preamble, payload length, etc. +#define OP_STATUS_6X 0xC0 +#define OP_TX_PARAMS_6X 0x8E // Set dbm, etc +#define OP_GET_PACKET_TYPE_6X 0x11 +#define OP_PACKET_TYPE_6X 0x8A +#define OP_BUFFER_BASE_ADDR_6X 0x8F +#define OP_READ_REGISTER_6X 0x1D +#define OP_WRITE_REGISTER_6X 0x0D +#define OP_DIO3_TCXO_CTRL_6X 0x97 +#define OP_DIO2_RF_CTRL_6X 0x9D +#define OP_CAD_PARAMS 0x88 +#define OP_CALIBRATE_6X 0x89 +#define OP_RX_TX_FALLBACK_MODE_6X 0x93 +#define OP_REGULATOR_MODE_6X 0x96 +#define OP_CALIBRATE_IMAGE_6X 0x98 + +#define MASK_CALIBRATE_ALL 0x7f + +#define IRQ_TX_DONE_MASK_6X 0x01 +#define IRQ_RX_DONE_MASK_6X 0x02 +#define IRQ_HEADER_DET_MASK_6X 0x10 +#define IRQ_PREAMBLE_DET_MASK_6X 0x04 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_6X 0x40 +#define IRQ_ALL_MASK_6X 0b0100001111111111 + +#define MODE_LONG_RANGE_MODE_6X 0x01 + +#define OP_FIFO_WRITE_6X 0x0E +#define OP_FIFO_READ_6X 0x1E +#define REG_OCP_6X 0x08E7 +#define REG_LNA_6X 0x08AC // No agc in sx1262 +#define REG_SYNC_WORD_MSB_6X 0x0740 +#define REG_SYNC_WORD_LSB_6X 0x0741 +#define REG_PAYLOAD_LENGTH_6X 0x0702 // https://github.com/beegee-tokyo/SX126x-Arduino/blob/master/src/radio/sx126x/sx126x.h#L98 +#define REG_RANDOM_GEN_6X 0x0819 + +#define MODE_TCXO_3_3V_6X 0x07 +#define MODE_TCXO_3_0V_6X 0x06 +#define MODE_TCXO_2_7V_6X 0x06 +#define MODE_TCXO_2_4V_6X 0x06 +#define MODE_TCXO_2_2V_6X 0x03 +#define MODE_TCXO_1_8V_6X 0x02 +#define MODE_TCXO_1_7V_6X 0x01 +#define MODE_TCXO_1_6V_6X 0x00 + +#define MODE_STDBY_RC_6X 0x00 +#define MODE_STDBY_XOSC_6X 0x01 +#define MODE_FALLBACK_STDBY_RC_6X 0x20 +#define MODE_FALLBACK_STDBY_XOSC_6X 0x30 +#define MODE_IMPLICIT_HEADER 0x01 +#define MODE_EXPLICIT_HEADER 0x00 + +#define SYNC_WORD_6X 0x1424 + +#define XTAL_FREQ_6X (double)32000000 +#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_CARDPUTER_ADV + SPIClass cardputer_lora_spi(FSPI); + #define LORA_SPI cardputer_lora_spi +#elif BOARD_MODEL == BOARD_TECHO + SPIClass spim3 = SPIClass(NRF_SPIM3, pin_miso, pin_sclk, pin_mosi) ; + #define LORA_SPI spim3 + +#elif defined(NRF52840_XXAA) + extern SPIClass spiModem; + #define LORA_SPI spiModem +#else + extern SPIClass SPI; + #define LORA_SPI SPI +#endif + +#if HAS_LORA_PA + uint8_t lora_pa_model = LORA_PA_MODEL; +#endif + +#if HAS_LORA_LNA + int lora_lna_gain = LORA_LNA_GAIN; +#endif + +#define MAX_PKT_LENGTH 255 + +#if BOARD_MODEL == BOARD_CARDPUTER_ADV + static bool cardputer_adv_cap_rf_switch_ready = false; + + static void cardputer_adv_enable_cap_rf_switch() { + if (cardputer_adv_cap_rf_switch_ready) { return; } + + if (!m5::In_I2C.isEnabled()) { + if (!m5::In_I2C.begin(I2C_NUM_0, CARDPUTER_ADV_I2C_SDA, CARDPUTER_ADV_I2C_SCL)) { + return; + } + } + + m5::PI4IOE5V6408_Class ioe(CARDPUTER_ADV_CAP_IOE_ADDR, 400000, &m5::In_I2C); + if (!ioe.begin()) { return; } + + ioe.setDirection(CARDPUTER_ADV_CAP_RF_SW_PIN, true); + ioe.setHighImpedance(CARDPUTER_ADV_CAP_RF_SW_PIN, false); + ioe.digitalWrite(CARDPUTER_ADV_CAP_RF_SW_PIN, true); + delay(5); + cardputer_adv_cap_rf_switch_ready = true; + } +#endif + +sx126x::sx126x() : + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + _spiSettings(8E6, MSBFIRST, SPI_MODE0), + #else + _spiSettings(16E6, MSBFIRST, SPI_MODE0), + #endif + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _busy(LORA_DEFAULT_BUSY_PIN), _rxen(LORA_DEFAULT_RXEN_PIN), + _frequency(0), + _txp(0), + _sf(0x07), + _bw(0x04), + _cr(0x01), + _ldro(0x00), + _packetIndex(0), + _preambleLength(18), + _implicitHeaderMode(0), + _payloadLength(255), + _crcMode(1), + _fifo_tx_addr_ptr(0), + _fifo_rx_addr_ptr(0), + _packet({0}), + _preinit_done(false), + _onReceive(NULL) +{ setTimeout(0); } + +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 || BOARD_MODEL == BOARD_CARDPUTER_ADV + LORA_SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #elif BOARD_MODEL == BOARD_TECHO + LORA_SPI.setPins(pin_miso, pin_sclk, pin_mosi); + LORA_SPI.begin(); + #else + LORA_SPI.begin(); + #endif + + // Check version (retry for up to 2 seconds) + // TODO: Actually read version registers, not syncwords + long start = millis(); + uint8_t syncmsb; + uint8_t synclsb; + while (((millis() - start) < 2000) && (millis() >= start)) { + syncmsb = readRegister(REG_SYNC_WORD_MSB_6X); + synclsb = readRegister(REG_SYNC_WORD_LSB_6X); + if ( uint16_t(syncmsb << 8 | synclsb) == 0x1424 || uint16_t(syncmsb << 8 | synclsb) == 0x4434) { + break; + } + delay(100); + } + if ( uint16_t(syncmsb << 8 | synclsb) != 0x1424 && uint16_t(syncmsb << 8 | synclsb) != 0x4434) { + return false; + } + + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx126x::readRegister(uint16_t address) { + return singleTransfer(OP_READ_REGISTER_6X, address, 0x00); +} + +void sx126x::writeRegister(uint16_t address, uint8_t value) { + singleTransfer(OP_WRITE_REGISTER_6X, address, value); +} + +uint8_t ISR_VECT sx126x::singleTransfer(uint8_t opcode, uint16_t address, uint8_t value) { + waitOnBusy(); + + uint8_t response; + digitalWrite(_ss, LOW); + LORA_SPI.beginTransaction(_spiSettings); + LORA_SPI.transfer(opcode); + LORA_SPI.transfer((address & 0xFF00) >> 8); + LORA_SPI.transfer(address & 0x00FF); + if (opcode == OP_READ_REGISTER_6X) { LORA_SPI.transfer(0x00); } + response = LORA_SPI.transfer(value); + LORA_SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + + return response; +} + +void sx126x::rxAntEnable() { + if (_rxen != -1) { digitalWrite(_rxen, HIGH); } +} + +void sx126x::enableDio2RfSwitch() { + #if DIO2_AS_RF_SWITCH + uint8_t byte = 0x01; + executeOpcode(OP_DIO2_RF_CTRL_6X, &byte, 1); + #endif +} + +bool sx126x::loraMode() { + // Enable LoRa mode on the SX1262 chip. On the Cardputer Adv this is also + // used as a cheap guard against the modem falling back to an unexpected mode. + uint8_t mode = MODE_LONG_RANGE_MODE_6X; + for (uint8_t attempt = 0; attempt < 5; attempt++) { + executeOpcode(OP_PACKET_TYPE_6X, &mode, 1); + delay(2); + + uint8_t packet_type = 0; + executeOpcodeRead(OP_GET_PACKET_TYPE_6X, &packet_type, 1); + if (packet_type == MODE_LONG_RANGE_MODE_6X) { return true; } + } + return false; +} + +void sx126x::waitOnBusy() { + unsigned long time = millis(); + if (_busy != -1) { + while (digitalRead(_busy) == HIGH) { + if (millis() >= (time + 100)) { break; } + yield(); + } + } +} + +void sx126x::executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + LORA_SPI.beginTransaction(_spiSettings); + LORA_SPI.transfer(opcode); + for (int i = 0; i < size; i++) { LORA_SPI.transfer(buffer[i]); } + LORA_SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx126x::executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + LORA_SPI.beginTransaction(_spiSettings); + LORA_SPI.transfer(opcode); + LORA_SPI.transfer(0x00); + for (int i = 0; i < size; i++) { buffer[i] = LORA_SPI.transfer(0x00); } + LORA_SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx126x::writeBuffer(const uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + LORA_SPI.beginTransaction(_spiSettings); + LORA_SPI.transfer(OP_FIFO_WRITE_6X); + LORA_SPI.transfer(_fifo_tx_addr_ptr); + for (int i = 0; i < size; i++) { LORA_SPI.transfer(buffer[i]); _fifo_tx_addr_ptr++; } + LORA_SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx126x::readBuffer(uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + LORA_SPI.beginTransaction(_spiSettings); + LORA_SPI.transfer(OP_FIFO_READ_6X); + LORA_SPI.transfer(_fifo_rx_addr_ptr); + LORA_SPI.transfer(0x00); + for (int i = 0; i < size; i++) { buffer[i] = LORA_SPI.transfer(0x00); } + LORA_SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx126x::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro) { + // Because there is no access to these registers on the sx1262, we have + // to set all these parameters at once or not at all. + uint8_t buf[8]; + buf[0] = sf; + buf[1] = bw; + buf[2] = cr; + buf[3] = ldro; // Low data rate toggle + buf[4] = 0x00; // Unused params in LoRa mode + buf[5] = 0x00; + buf[6] = 0x00; + buf[7] = 0x00; + executeOpcode(OP_MODULATION_PARAMS_6X, buf, 8); +} + +void sx126x::setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc) { + // Because there is no access to these registers on the sx1262, we have + // to set all these parameters at once or not at all. + uint8_t buf[9]; + buf[0] = uint8_t((preamble_symbols & 0xFF00) >> 8); + buf[1] = uint8_t((preamble_symbols & 0x00FF)); + buf[2] = headermode; + buf[3] = payload_length; + buf[4] = crc; + buf[5] = 0x00; // standard IQ setting (no inversion) + buf[6] = 0x00; // unused params + buf[7] = 0x00; + buf[8] = 0x00; + executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); + + // SX1262 errata section 15.4: IQ polarity is inverted compared to + // SX1276. The SetPacketParams command resets register 0x0736 to an + // incorrect default. For standard IQ (no inversion), bit 2 must be + // SET after every SetPacketParams call. For inverted IQ, bit 2 must + // be CLEARED. Without this fix, LoRa RX demodulation fails silently + // while TX continues to work. + uint8_t iqreg = readRegister(0x0736); + if (buf[5] == 0x00) { + // Standard IQ: set bit 2 + writeRegister(0x0736, iqreg | 0x04); + } else { + // Inverted IQ: clear bit 2 + writeRegister(0x0736, iqreg & ~0x04); + } +} + +void sx126x::reset(void) { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } +} + +void sx126x::calibrate(void) { + // Put in STDBY_RC mode before calibration + uint8_t mode_byte = MODE_STDBY_RC_6X; + executeOpcode(OP_STANDBY_6X, &mode_byte, 1); + + // Calibrate RC64k, RC13M, PLL, ADC and image + uint8_t calibrate = MASK_CALIBRATE_ALL; + executeOpcode(OP_CALIBRATE_6X, &calibrate, 1); + + delay(5); + waitOnBusy(); +} + +void sx126x::calibrate_image(long frequency) { + uint8_t image_freq[2] = {0}; + if (frequency >= 430E6 && frequency <= 440E6) { image_freq[0] = 0x6B; image_freq[1] = 0x6F; } + else if (frequency >= 470E6 && frequency <= 510E6) { image_freq[0] = 0x75; image_freq[1] = 0x81; } + else if (frequency >= 779E6 && frequency <= 787E6) { image_freq[0] = 0xC1; image_freq[1] = 0xC5; } + else if (frequency >= 863E6 && frequency <= 870E6) { image_freq[0] = 0xD7; image_freq[1] = 0xDB; } + else if (frequency >= 902E6 && frequency <= 928E6) { image_freq[0] = 0xE1; image_freq[1] = 0xE9; } // TODO: Allow higher freq calibration + executeOpcode(OP_CALIBRATE_IMAGE_6X, image_freq, 2); + waitOnBusy(); +} + +int sx126x::begin(long frequency) { + reset(); + + if (_busy != -1) { pinMode(_busy, INPUT); } + if (!_preinit_done) { if (!preInit()) { return false; } } + if (_rxen != -1) { pinMode(_rxen, OUTPUT); } + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + cardputer_adv_enable_cap_rf_switch(); + #endif + + enableTCXO(); + delay(10); + standby(); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + // M5's Cap LoRa-1262 reference path uses LDO regulator mode. + uint8_t reg_mode = 0x00; + executeOpcode(OP_REGULATOR_MODE_6X, ®_mode, 1); + #endif + + calibrate(); + calibrate_image(frequency); + if (!loraMode()) { return false; } + standby(); + + // Set sync word + setSyncWord(SYNC_WORD_6X); + + enableDio2RfSwitch(); + + rxAntEnable(); + setFrequency(frequency); + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + setTxPower(14); + #else + setTxPower(2); + #endif + enableCrc(); + writeRegister(REG_LNA_6X, 0x96); // Set LNA boost + uint8_t basebuf[2] = {0}; // Set base addresses + executeOpcode(OP_BUFFER_BASE_ADDR_6X, basebuf, 2); + + setModulationParams(_sf, _bw, _cr, _ldro); + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + enableDio2RfSwitch(); + + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + // Keep the TCXO-backed oscillator as the fallback between TX/RX transitions. + uint8_t fallback = MODE_FALLBACK_STDBY_XOSC_6X; + executeOpcode(OP_RX_TX_FALLBACK_MODE_6X, &fallback, 1); + #endif + + #if HAS_LORA_PA + if (lora_pa_model == LORA_PA_UNKNOWN) { + #if BOARD_MODEL == BOARD_HELTEC32_V4 + + pinMode(LORA_PA_PWR_EN, OUTPUT); + pinMode(LORA_PA_CSD, INPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); delay(5); + if (digitalRead(LORA_PA_CSD) == HIGH) { + lora_pa_model = LORA_PA_KCT8103L; + lora_lna_gain = LORA_LNA_KCT8103L_GAIN; + } else { + lora_pa_model = LORA_PA_GC1109; + } + #endif + } + + if (lora_pa_model == LORA_PA_GC1109) { + // Enable Vfem_ctl for supply to + // PA power net. + pinMode(LORA_PA_PWR_EN, OUTPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); + + // Enable PA LNA and TX standby + pinMode(LORA_PA_CSD, OUTPUT); + digitalWrite(LORA_PA_CSD, HIGH); + + // Keep PA CPS low until actual + // transmit. Does it save power? + // Who knows? Will have to measure. + // Note from the future: Nope. + // Power consumption is the same, + // and turning it on and off is + // not something that it likes. + // Keeping it high for now. + pinMode(LORA_PA_CPS, OUTPUT); + digitalWrite(LORA_PA_CPS, HIGH); + + // On Heltec V4, the PA CTX pin + // is driven by the SX1262 DIO2 + // pin directly, so we do not + // need to manually raise this. + + } else if (lora_pa_model == LORA_PA_KCT8103L) { + // Enable Vfem_ctl for supply to + // PA power net. + pinMode(LORA_PA_PWR_EN, OUTPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); + + // Enable KCT8103L chip + pinMode(LORA_PA_CSD, OUTPUT); + digitalWrite(LORA_PA_CSD, HIGH); + + // Enable receive LNA + pinMode(LORA_PA_CTX, OUTPUT); + digitalWrite(LORA_PA_CTX, LOW); + + // On Heltec V4.3, the PA CPS pin + // is driven by the SX1262 DIO2 + // pin directly, so we do not + // need to manually raise this. + } + #endif + + return 1; +} + +void sx126x::end() { sleep(); LORA_SPI.end(); _preinit_done = false; } + +int sx126x::beginPacket(int implicitHeader) { + #if HAS_LORA_PA + if (lora_pa_model == LORA_PA_GC1109) { + // Enable PA CPS for transmit + // digitalWrite(LORA_PA_CPS, HIGH); + // Disabled since we're keeping it + // on permanently as long as the + // radio is powered up. + } else if (lora_pa_model == LORA_PA_KCT8103L) { + digitalWrite(LORA_PA_CTX, HIGH); + } + #endif + + standby(); + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + _payloadLength = 0; + _fifo_tx_addr_ptr = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + + return 1; +} + +int sx126x::endPacket() { + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + enableDio2RfSwitch(); + uint8_t timeout[3] = {0}; // Put in single TX mode + executeOpcode(OP_TX_6X, timeout, 3); + + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + + // Wait for TX done + bool timed_out = false; + uint32_t w_timeout = millis()+LORA_MODEM_TIMEOUT_MS; + while ((millis() < w_timeout) && ((buf[1] & IRQ_TX_DONE_MASK_6X) == 0)) { + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + yield(); + } + + if (!(millis() < w_timeout)) { timed_out = true; } + + // Clear IRQs + uint8_t mask[2]; + mask[0] = 0x00; + mask[1] = IRQ_TX_DONE_MASK_6X; + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, mask, 2); + if (timed_out) { return 0; } else { return 1; } +} + +unsigned long preamble_detected_at = 0; +extern long lora_preamble_time_ms; +extern long lora_header_time_ms; +bool false_preamble_detected = false; + +bool sx126x::dcd() { + uint8_t buf[2] = {0}; executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + uint32_t now = millis(); + + bool header_detected = false; + bool carrier_detected = false; + + if ((buf[1] & IRQ_HEADER_DET_MASK_6X) != 0) { header_detected = true; carrier_detected = true; } + else { header_detected = false; } + + if ((buf[1] & IRQ_PREAMBLE_DET_MASK_6X) != 0) { + carrier_detected = true; + if (preamble_detected_at == 0) { preamble_detected_at = now; } + if (now - preamble_detected_at > lora_preamble_time_ms + lora_header_time_ms) { + preamble_detected_at = 0; + if (!header_detected) { false_preamble_detected = true; } + uint8_t clearbuf[2] = {0}; + clearbuf[1] = IRQ_PREAMBLE_DET_MASK_6X; + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, clearbuf, 2); + } + } + + // TODO: Maybe there's a way of unlatching the RSSI + // status without re-activating receive mode? + if (false_preamble_detected) { sx126x_modem.receive(); false_preamble_detected = false; } + return carrier_detected; +} + +uint8_t sx126x::currentRssiRaw() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); + return byte; +} + +int ISR_VECT sx126x::currentRssi() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); + int rssi = -(int(byte)) / 2; + #if HAS_LORA_LNA + rssi -= lora_lna_gain; + #endif + return rssi; +} + +uint8_t sx126x::packetRssiRaw() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return buf[2]; +} + +int ISR_VECT sx126x::packetRssi() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + int pkt_rssi = -buf[0] / 2; + #if HAS_LORA_LNA + pkt_rssi -= lora_lna_gain; + #endif + return pkt_rssi; +} + +int ISR_VECT sx126x::packetRssi(uint8_t pkt_snr_raw) { + // TODO: May need more calculations here + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + int pkt_rssi = -buf[0] / 2; + return pkt_rssi; +} + +uint8_t ISR_VECT sx126x::packetSnrRaw() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return buf[1]; +} + +float ISR_VECT sx126x::packetSnr() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return float(buf[1]) * 0.25; +} + +long sx126x::packetFrequencyError() { + // TODO: Implement this, no idea how to check it on the sx1262 + const float fError = 0.0; + return static_cast(fError); +} + +size_t sx126x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } +size_t sx126x::write(const uint8_t *buffer, size_t size) { + if ((_payloadLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - _payloadLength; } + writeBuffer(buffer, size); + _payloadLength = _payloadLength + size; + return size; +} + +int ISR_VECT sx126x::available() { + uint8_t buf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, buf, 2); + return buf[0] - _packetIndex; +} + +int ISR_VECT sx126x::read(){ + if (!available()) { return -1; } + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int size = rxbuf[0]; + _fifo_rx_addr_ptr = rxbuf[1]; + readBuffer(_packet, size); + } + + uint8_t byte = _packet[_packetIndex]; + _packetIndex++; + return byte; +} + +int sx126x::peek() { + if (!available()) { return -1; } + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int size = rxbuf[0]; + _fifo_rx_addr_ptr = rxbuf[1]; + readBuffer(_packet, size); + } + + uint8_t b = _packet[_packetIndex]; + return b; +} + +void sx126x::flush() { } + +void sx126x::onReceive(void(*callback)(int)){ + _onReceive = callback; + + if (callback) { + pinMode(_dio0, INPUT); + uint8_t buf[8]; // Set preamble and header detection irqs, plus dio0 mask + buf[0] = 0xFF; // Set irq masks, enable all + buf[1] = 0xFF; + buf[2] = 0x00; // Set dio0 masks + buf[3] = IRQ_RX_DONE_MASK_6X; + buf[4] = 0x00; // Set dio1 masks + buf[5] = 0x00; + buf[6] = 0x00; // Set dio2 masks + buf[7] = 0x00; + executeOpcode(OP_SET_IRQ_FLAGS_6X, buf, 8); + enableDio2RfSwitch(); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + LORA_SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + attachInterrupt(digitalPinToInterrupt(_dio0), sx126x::onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + #ifdef SPI_HAS_NOTUSINGINTERRUPT + LORA_SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx126x::receive(int size) { + #if BOARD_MODEL == BOARD_CARDPUTER_ADV + uint8_t clear[2] = {0xFF, 0xFF}; + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, clear, 2); + #endif + + #if HAS_LORA_PA + if (lora_pa_model == LORA_PA_GC1109) { + // Disable PA CPS for receive + // digitalWrite(LORA_PA_CPS, LOW); + // That turned out to be a bad idea. + // The LNA goes wonky if it's toggled + // on and off too quickly. We'll keep + // it on permanently, as long as the + // radio is powered up. + } else if (lora_pa_model == LORA_PA_KCT8103L) { + digitalWrite(LORA_PA_CTX, LOW); + } + #endif + + if (size > 0) { + implicitHeaderMode(); + _payloadLength = size; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + } else { explicitHeaderMode(); } + + if (_rxen != -1) { rxAntEnable(); } + enableDio2RfSwitch(); + uint8_t mode[3] = {0xFF, 0xFF, 0xFF}; // Continuous mode + executeOpcode(OP_RX_6X, mode, 3); +} + +void sx126x::standby() { + uint8_t byte = MODE_STDBY_XOSC_6X; // STDBY_XOSC + executeOpcode(OP_STANDBY_6X, &byte, 1); +} + +void sx126x::sleep() { uint8_t byte = 0x00; executeOpcode(OP_SLEEP_6X, &byte, 1); } + +void sx126x::enableTCXO() { + #if HAS_TCXO + #if BOARD_MODEL == BOARD_RAK4631 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_XIAO_S3 + uint8_t buf[4] = {MODE_TCXO_3_3V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TBEAM + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TDECK + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_T3S3 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_HELTEC_T114 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TECHO + 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_CARDPUTER_ADV + uint8_t buf[4] = {MODE_TCXO_3_0V_6X, 0x00, 0xA0, 0x00}; + #endif + executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4); + #endif +} + +// TODO: Once enabled, SX1262 needs a complete reset to disable TCXO +void sx126x::disableTCXO() { } + +void sx126x::setTxPower(int level, int outputPin) { + // Currently no low power mode for SX1262 implemented, assuming PA boost + + // WORKAROUND - Better Resistance of the SX1262 Tx to Antenna Mismatch, see DS_SX1261-2_V1.2 datasheet chapter 15.2 + // RegTxClampConfig = @address 0x08D8 + writeRegister(0x08D8, readRegister(0x08D8) | (0x0F << 1)); + + uint8_t pa_buf[4]; + pa_buf[0] = 0x04; // PADutyCycle needs to be 0x04 to achieve 22dBm output, but can be lowered for better efficiency at lower outputs + pa_buf[1] = 0x07; // HPMax at 0x07 is maximum supported for SX1262 + pa_buf[2] = 0x00; // DeviceSel 0x00 for SX1262 (0x01 for SX1261) + pa_buf[3] = 0x01; // PALut always 0x01 (reserved according to datasheet) + executeOpcode(OP_PA_CONFIG_6X, pa_buf, 4); // set pa_config for high power + + if (level > 22) { level = 22; } + else if (level < -9) { level = -9; } + writeRegister(REG_OCP_6X, OCP_TUNED); // Use board-specific tuned OCP + + uint8_t tx_buf[2]; + tx_buf[0] = level; + tx_buf[1] = 0x02; // PA ramping time - 40 microseconds + executeOpcode(OP_TX_PARAMS_6X, tx_buf, 2); + + _txp = level; +} + +uint8_t sx126x::getTxPower() { return _txp; } + +void sx126x::setFrequency(long frequency) { + _frequency = frequency; + uint8_t buf[4]; + uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP_6X); + buf[0] = ((freq >> 24) & 0xFF); + buf[1] = ((freq >> 16) & 0xFF); + buf[2] = ((freq >> 8) & 0xFF); + buf[3] = (freq & 0xFF); + executeOpcode(OP_RF_FREQ_6X, buf, 4); +} + +uint32_t sx126x::getFrequency() { + // We can't read the frequency on the sx1262 / 80 + uint32_t frequency = _frequency; + return frequency; +} + +void sx126x::setSpreadingFactor(int sf) { + if (sf < 5) { sf = 5; } + else if (sf > 12) { sf = 12; } + _sf = sf; + + handleLowDataRate(); + setModulationParams(sf, _bw, _cr, _ldro); +} + +long sx126x::getSignalBandwidth() { + int bw = _bw; + switch (bw) { + case 0x00: return 7.8E3; + case 0x01: return 15.6E3; + case 0x02: return 31.25E3; + case 0x03: return 62.5E3; + case 0x04: return 125E3; + case 0x05: return 250E3; + case 0x06: return 500E3; + case 0x08: return 10.4E3; + case 0x09: return 20.8E3; + case 0x0A: return 41.7E3; + } + return 0; +} + +extern bool lora_low_datarate; +void sx126x::handleLowDataRate() { + if ( long( (1<<_sf) / (getSignalBandwidth()/1000)) > 16) + { _ldro = 0x01; lora_low_datarate = true; } + else { _ldro = 0x00; lora_low_datarate = false; } +} + +// TODO: Check if there's anything the sx1262 can do here +// SX1262 errata section 15.1: Modulation quality with 500 kHz LoRa BW. +// Register 0x0889 bit 2 must be cleared for 500 kHz, set for all other +// bandwidths. Improves receiver sensitivity at non-500 kHz bandwidths. +void sx126x::optimizeModemSensitivity(){ + uint8_t reg = readRegister(0x0889); + if (getSignalBandwidth() == 500E3) { + writeRegister(0x0889, reg & 0xFB); // clear bit 2 + } else { + writeRegister(0x0889, reg | 0x04); // set bit 2 + } +} + +void sx126x::setSignalBandwidth(long sbw) { + if (sbw <= 7.8E3) { _bw = 0x00; } + else if (sbw <= 10.4E3) { _bw = 0x08; } + else if (sbw <= 15.6E3) { _bw = 0x01; } + else if (sbw <= 20.8E3) { _bw = 0x09; } + else if (sbw <= 31.25E3) { _bw = 0x02; } + else if (sbw <= 41.7E3) { _bw = 0x0A; } + else if (sbw <= 62.5E3) { _bw = 0x03; } + else if (sbw <= 125E3) { _bw = 0x04; } + else if (sbw <= 250E3) { _bw = 0x05; } + else { _bw = 0x06; } + + handleLowDataRate(); + setModulationParams(_sf, _bw, _cr, _ldro); + optimizeModemSensitivity(); +} + +void sx126x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + int cr = denominator - 4; + _cr = cr; + setModulationParams(_sf, _bw, cr, _ldro); +} + +void sx126x::setPreambleLength(long preamble_symbols) { + _preambleLength = preamble_symbols; + setPacketParams(preamble_symbols, _implicitHeaderMode, _payloadLength, _crcMode); +} + +void sx126x::setSyncWord(uint16_t sw) { + // TODO: Why was this hardcoded instead of using the config value? + // writeRegister(REG_SYNC_WORD_MSB_6X, (sw & 0xFF00) >> 8); + // writeRegister(REG_SYNC_WORD_LSB_6X, sw & 0x00FF); + writeRegister(REG_SYNC_WORD_MSB_6X, 0x14); + writeRegister(REG_SYNC_WORD_LSB_6X, 0x24); +} + +void sx126x::setPins(int ss, int reset, int dio0, int busy, int rxen) { + _ss = ss; + _reset = reset; + _dio0 = dio0; + _busy = busy; + _rxen = rxen; +} + +void sx126x::dumpRegisters(Stream& out) { + for (int i = 0; i < 128; i++) { + out.print("0x"); + out.print(i, HEX); + out.print(": 0x"); + out.println(readRegister(i), HEX); + } +} + +void ISR_VECT sx126x::handleDio0Rise() { + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, buf, 2); + + if ((buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK_6X) == 0) { + _packetIndex = 0; + uint8_t rxbuf[2] = {0}; // Read packet length + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int packetLength = rxbuf[0]; + if (_onReceive) { _onReceive(packetLength); } + } +} + +void ISR_VECT sx126x::onDio0Rise() { sx126x_modem.handleDio0Rise(); } +void sx126x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx126x::enableCrc() { _crcMode = 1; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::disableCrc() { _crcMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::explicitHeaderMode() { _implicitHeaderMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::implicitHeaderMode() { _implicitHeaderMode = 1; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +byte sx126x::random() { return readRegister(REG_RANDOM_GEN_6X); } + +sx126x sx126x_modem; + +#endif diff --git a/vendor/rnode_firmware/sx126x.h b/vendor/rnode_firmware/sx126x.h new file mode 100644 index 0000000..d8e2b00 --- /dev/null +++ b/vendor/rnode_firmware/sx126x.h @@ -0,0 +1,146 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX126X_H +#define SX126X_H + +#include +#include +#include "Modem.h" + +#define LORA_DEFAULT_SS_PIN 10 +#define LORA_DEFAULT_RESET_PIN 9 +#define LORA_DEFAULT_DIO0_PIN 2 +#define LORA_DEFAULT_RXEN_PIN -1 +#define LORA_DEFAULT_TXEN_PIN -1 +#define LORA_DEFAULT_BUSY_PIN -1 +#define LORA_MODEM_TIMEOUT_MS 20E3 + +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 + +#define RSSI_OFFSET 157 + +class sx126x : public Stream { +public: + sx126x(); + + int begin(long frequency); + void end(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + void reset(void); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(long frequency); + void setSpreadingFactor(int sf); + long getSignalBandwidth(); + void setSignalBandwidth(long sbw); + void setCodingRate4(int denominator); + void setPreambleLength(long preamble_symbols); + void setSyncWord(uint16_t sw); + bool dcd(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + void rxAntEnable(); + bool loraMode(); + void waitOnBusy(); + void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size); + void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size); + void writeBuffer(const uint8_t* buffer, size_t size); + void readBuffer(uint8_t* buffer, size_t size); + void setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); + + void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro); + + // deprecated + void crc() { enableCrc(); } + void noCrc() { disableCrc(); } + + byte random(); + + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN); + void setSPIFrequency(uint32_t frequency); + + void dumpRegisters(Stream& out); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + void handleDio0Rise(); + + uint8_t readRegister(uint16_t address); + void writeRegister(uint16_t address, uint8_t value); + uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + + void calibrate(void); + void calibrate_image(long frequency); + void enableDio2RfSwitch(); + +private: + SPISettings _spiSettings; + int _ss; + int _reset; + int _dio0; + int _rxen; + int _busy; + long _frequency; + int _txp; + uint8_t _sf; + uint8_t _bw; + uint8_t _cr; + uint8_t _ldro; + int _packetIndex; + int _preambleLength; + int _implicitHeaderMode; + int _payloadLength; + int _crcMode; + int _fifo_tx_addr_ptr; + int _fifo_rx_addr_ptr; + uint8_t _packet[255]; + bool _preinit_done; + void (*_onReceive)(int); +}; + +extern sx126x sx126x_modem; + +#endif diff --git a/vendor/rnode_firmware/sx127x.cpp b/vendor/rnode_firmware/sx127x.cpp new file mode 100644 index 0000000..ef9038d --- /dev/null +++ b/vendor/rnode_firmware/sx127x.cpp @@ -0,0 +1,503 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1276 +#include "sx127x.h" + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +// Registers +#define REG_FIFO_7X 0x00 +#define REG_OP_MODE_7X 0x01 +#define REG_FRF_MSB_7X 0x06 +#define REG_FRF_MID_7X 0x07 +#define REG_FRF_LSB_7X 0x08 +#define REG_PA_CONFIG_7X 0x09 +#define REG_OCP_7X 0x0b +#define REG_LNA_7X 0x0c +#define REG_FIFO_ADDR_PTR_7X 0x0d +#define REG_FIFO_TX_BASE_ADDR_7X 0x0e +#define REG_FIFO_RX_BASE_ADDR_7X 0x0f +#define REG_FIFO_RX_CURRENT_ADDR_7X 0x10 +#define REG_IRQ_FLAGS_7X 0x12 +#define REG_RX_NB_BYTES_7X 0x13 +#define REG_MODEM_STAT_7X 0x18 +#define REG_PKT_SNR_VALUE_7X 0x19 +#define REG_PKT_RSSI_VALUE_7X 0x1a +#define REG_RSSI_VALUE_7X 0x1b +#define REG_MODEM_CONFIG_1_7X 0x1d +#define REG_MODEM_CONFIG_2_7X 0x1e +#define REG_PREAMBLE_MSB_7X 0x20 +#define REG_PREAMBLE_LSB_7X 0x21 +#define REG_PAYLOAD_LENGTH_7X 0x22 +#define REG_MODEM_CONFIG_3_7X 0x26 +#define REG_FREQ_ERROR_MSB_7X 0x28 +#define REG_FREQ_ERROR_MID_7X 0x29 +#define REG_FREQ_ERROR_LSB_7X 0x2a +#define REG_RSSI_WIDEBAND_7X 0x2c +#define REG_DETECTION_OPTIMIZE_7X 0x31 +#define REG_HIGH_BW_OPTIMIZE_1_7X 0x36 +#define REG_DETECTION_THRESHOLD_7X 0x37 +#define REG_SYNC_WORD_7X 0x39 +#define REG_HIGH_BW_OPTIMIZE_2_7X 0x3a +#define REG_DIO_MAPPING_1_7X 0x40 +#define REG_VERSION_7X 0x42 +#define REG_TCXO_7X 0x4b +#define REG_PA_DAC_7X 0x4d + +// Modes +#define MODE_LONG_RANGE_MODE_7X 0x80 +#define MODE_SLEEP_7X 0x00 +#define MODE_STDBY_7X 0x01 +#define MODE_TX_7X 0x03 +#define MODE_RX_CONTINUOUS_7X 0x05 +#define MODE_RX_SINGLE_7X 0x06 + +// PA config +#define PA_BOOST_7X 0x80 + +// IRQ masks +#define IRQ_TX_DONE_MASK_7X 0x08 +#define IRQ_RX_DONE_MASK_7X 0x40 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_7X 0x20 + +#define SYNC_WORD_7X 0x12 +#define MAX_PKT_LENGTH 255 + +extern SPIClass SPI; + +sx127x::sx127x() : + _spiSettings(8E6, MSBFIRST, SPI_MODE0), + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), + _frequency(0), _packetIndex(0), _preinit_done(false), _onReceive(NULL) { setTimeout(0); } + +void sx127x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx127x::setPins(int ss, int reset, int dio0, int busy) { _ss = ss; _reset = reset; _dio0 = dio0; _busy = busy; } +uint8_t ISR_VECT sx127x::readRegister(uint8_t address) { return singleTransfer(address & 0x7f, 0x00); } +void sx127x::writeRegister(uint8_t address, uint8_t value) { singleTransfer(address | 0x80, value); } +void sx127x::standby() { writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_STDBY_7X); } +void sx127x::sleep() { writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_SLEEP_7X); } +void sx127x::setSyncWord(uint8_t sw) { writeRegister(REG_SYNC_WORD_7X, sw); } +void sx127x::enableCrc() { writeRegister(REG_MODEM_CONFIG_2_7X, readRegister(REG_MODEM_CONFIG_2_7X) | 0x04); } +void sx127x::disableCrc() { writeRegister(REG_MODEM_CONFIG_2_7X, readRegister(REG_MODEM_CONFIG_2_7X) & 0xfb); } +void sx127x::enableTCXO() { uint8_t tcxo_reg = readRegister(REG_TCXO_7X); writeRegister(REG_TCXO_7X, tcxo_reg | 0x10); } +void sx127x::disableTCXO() { uint8_t tcxo_reg = readRegister(REG_TCXO_7X); writeRegister(REG_TCXO_7X, tcxo_reg & 0xEF); } +void sx127x::explicitHeaderMode() { _implicitHeaderMode = 0; writeRegister(REG_MODEM_CONFIG_1_7X, readRegister(REG_MODEM_CONFIG_1_7X) & 0xfe); } +void sx127x::implicitHeaderMode() { _implicitHeaderMode = 1; writeRegister(REG_MODEM_CONFIG_1_7X, readRegister(REG_MODEM_CONFIG_1_7X) | 0x01); } +byte sx127x::random() { return readRegister(REG_RSSI_WIDEBAND_7X); } +void sx127x::flush() { } + +bool sx127x::preInit() { + pinMode(_ss, OUTPUT); + digitalWrite(_ss, HIGH); + + #if BOARD_MODEL == BOARD_T3S3 + SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #else + SPI.begin(); + #endif + + // Check modem version + uint8_t version; + long start = millis(); + while (((millis() - start) < 500) && (millis() >= start)) { + version = readRegister(REG_VERSION_7X); + if (version == 0x12) { break; } + delay(100); + } + + if (version != 0x12) { return false; } + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx127x::singleTransfer(uint8_t address, uint8_t value) { + uint8_t response; + + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(address); + response = SPI.transfer(value); + SPI.endTransaction(); + digitalWrite(_ss, HIGH); + + return response; +} + +int sx127x::begin(long frequency) { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } + + if (_busy != -1) { pinMode(_busy, INPUT); } + if (!_preinit_done) { if (!preInit()) { return false; } } + + sleep(); + setFrequency(frequency); + + // Set base addresses + writeRegister(REG_FIFO_TX_BASE_ADDR_7X, 0); + writeRegister(REG_FIFO_RX_BASE_ADDR_7X, 0); + + // Set LNA boost and auto AGC + writeRegister(REG_LNA_7X, readRegister(REG_LNA_7X) | 0x03); + writeRegister(REG_MODEM_CONFIG_3_7X, 0x04); + + setSyncWord(SYNC_WORD_7X); + enableCrc(); + setTxPower(2); + + standby(); + + return 1; +} + +void sx127x::end() { sleep(); SPI.end(); _preinit_done = false; } + +int sx127x::beginPacket(int implicitHeader) { + standby(); + + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + // Reset FIFO address and payload length + writeRegister(REG_FIFO_ADDR_PTR_7X, 0); + writeRegister(REG_PAYLOAD_LENGTH_7X, 0); + + return 1; +} + +int sx127x::endPacket() { + // Enter TX mode + writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_TX_7X); + + // Wait for TX completion + while ((readRegister(REG_IRQ_FLAGS_7X) & IRQ_TX_DONE_MASK_7X) == 0) { + yield(); + } + + // Clear TX complete IRQ + writeRegister(REG_IRQ_FLAGS_7X, IRQ_TX_DONE_MASK_7X); + return 1; +} + +bool sx127x::dcd() { + bool carrier_detected = false; + uint8_t status = readRegister(REG_MODEM_STAT_7X); + if ((status & SIG_DETECT) == SIG_DETECT) { carrier_detected = true; } + if ((status & SIG_SYNCED) == SIG_SYNCED) { carrier_detected = true; } + return carrier_detected; +} + +uint8_t sx127x::currentRssiRaw() { + uint8_t rssi = readRegister(REG_RSSI_VALUE_7X); + return rssi; +} + +int ISR_VECT sx127x::currentRssi() { + int rssi = (int)readRegister(REG_RSSI_VALUE_7X) - RSSI_OFFSET; + if (_frequency < 820E6) rssi -= 7; + return rssi; +} + +uint8_t sx127x::packetRssiRaw() { + uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE_7X); + return pkt_rssi_value; +} + +int ISR_VECT sx127x::packetRssi(uint8_t pkt_snr_raw) { + int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE_7X) - RSSI_OFFSET; + int pkt_snr = ((int8_t)pkt_snr_raw)*0.25; + + if (_frequency < 820E6) pkt_rssi -= 7; + + if (pkt_snr < 0) { + pkt_rssi += pkt_snr; + } else { + // Slope correction is (16/15)*pkt_rssi, + // this estimation looses one floating point + // operation, and should be precise enough. + pkt_rssi = (int)(1.066 * pkt_rssi); + } + return pkt_rssi; +} + +int ISR_VECT sx127x::packetRssi() { + int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE_7X) - RSSI_OFFSET; + int pkt_snr = packetSnr(); + + if (_frequency < 820E6) pkt_rssi -= 7; + + if (pkt_snr < 0) { pkt_rssi += pkt_snr; } + else { + // Slope correction is (16/15)*pkt_rssi, + // this estimation looses one floating point + // operation, and should be precise enough. + pkt_rssi = (int)(1.066 * pkt_rssi); + } + return pkt_rssi; +} + +uint8_t ISR_VECT sx127x::packetSnrRaw() { return readRegister(REG_PKT_SNR_VALUE_7X); } + +float ISR_VECT sx127x::packetSnr() { return ((int8_t)readRegister(REG_PKT_SNR_VALUE_7X)) * 0.25; } + +long sx127x::packetFrequencyError() { + int32_t freqError = 0; + freqError = static_cast(readRegister(REG_FREQ_ERROR_MSB_7X) & B111); + freqError <<= 8L; + freqError += static_cast(readRegister(REG_FREQ_ERROR_MID_7X)); + freqError <<= 8L; + freqError += static_cast(readRegister(REG_FREQ_ERROR_LSB_7X)); + + if (readRegister(REG_FREQ_ERROR_MSB_7X) & B1000) { // Sign bit is on + freqError -= 524288; // B1000'0000'0000'0000'0000 + } + + const float fXtal = 32E6; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14) + const float fError = ((static_cast(freqError) * (1L << 24)) / fXtal) * (getSignalBandwidth() / 500000.0f); + + return static_cast(fError); +} + +size_t sx127x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } + +size_t sx127x::write(const uint8_t *buffer, size_t size) { + int currentLength = readRegister(REG_PAYLOAD_LENGTH_7X); + if ((currentLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - currentLength; } + + for (size_t i = 0; i < size; i++) { writeRegister(REG_FIFO_7X, buffer[i]); } + writeRegister(REG_PAYLOAD_LENGTH_7X, currentLength + size); + + return size; +} + +int ISR_VECT sx127x::available() { return (readRegister(REG_RX_NB_BYTES_7X) - _packetIndex); } + +int ISR_VECT sx127x::read() { + if (!available()) { return -1; } + _packetIndex++; + return readRegister(REG_FIFO_7X); +} + +int sx127x::peek() { + if (!available()) { return -1; } + + // Remember current FIFO address, read, and then reset address + int currentAddress = readRegister(REG_FIFO_ADDR_PTR_7X); + uint8_t b = readRegister(REG_FIFO_7X); + writeRegister(REG_FIFO_ADDR_PTR_7X, currentAddress); + + return b; +} + +void sx127x::onReceive(void(*callback)(int)) { + _onReceive = callback; + + if (callback) { + pinMode(_dio0, INPUT); + writeRegister(REG_DIO_MAPPING_1_7X, 0x00); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + + attachInterrupt(digitalPinToInterrupt(_dio0), sx127x::onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx127x::receive(int size) { + if (size > 0) { + implicitHeaderMode(); + writeRegister(REG_PAYLOAD_LENGTH_7X, size & 0xff); + } else { explicitHeaderMode(); } + + writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_RX_CONTINUOUS_7X); +} + +void sx127x::setTxPower(int level, int outputPin) { + // Setup according to RFO or PA_BOOST output pin + if (PA_OUTPUT_RFO_PIN == outputPin) { + if (level < 0) { level = 0; } + else if (level > 14) { level = 14; } + + writeRegister(REG_PA_DAC_7X, 0x84); + writeRegister(REG_PA_CONFIG_7X, 0x70 | level); + + } else { + if (level < 2) { level = 2; } + else if (level > 17) { level = 17; } + + writeRegister(REG_PA_DAC_7X, 0x84); + writeRegister(REG_PA_CONFIG_7X, PA_BOOST_7X | (level - 2)); + } +} + +uint8_t sx127x::getTxPower() { byte txp = readRegister(REG_PA_CONFIG_7X); return txp; } + +void sx127x::setFrequency(unsigned long frequency) { + _frequency = frequency; + uint32_t frf = ((uint64_t)frequency << 19) / 32000000; + + writeRegister(REG_FRF_MSB_7X, (uint8_t)(frf >> 16)); + writeRegister(REG_FRF_MID_7X, (uint8_t)(frf >> 8)); + writeRegister(REG_FRF_LSB_7X, (uint8_t)(frf >> 0)); + + optimizeModemSensitivity(); +} + +uint32_t sx127x::getFrequency() { + uint8_t msb = readRegister(REG_FRF_MSB_7X); + uint8_t mid = readRegister(REG_FRF_MID_7X); + uint8_t lsb = readRegister(REG_FRF_LSB_7X); + + uint32_t frf = ((uint32_t)msb << 16) | ((uint32_t)mid << 8) | (uint32_t)lsb; + uint64_t frm = (uint64_t)frf*32000000; + uint32_t frequency = (frm >> 19); + + return frequency; +} + +void sx127x::setSpreadingFactor(int sf) { + if (sf < 6) { sf = 6; } + else if (sf > 12) { sf = 12; } + + if (sf == 6) { + writeRegister(REG_DETECTION_OPTIMIZE_7X, 0xc5); + writeRegister(REG_DETECTION_THRESHOLD_7X, 0x0c); + } else { + writeRegister(REG_DETECTION_OPTIMIZE_7X, 0xc3); + writeRegister(REG_DETECTION_THRESHOLD_7X, 0x0a); + } + + writeRegister(REG_MODEM_CONFIG_2_7X, (readRegister(REG_MODEM_CONFIG_2_7X) & 0x0f) | ((sf << 4) & 0xf0)); + handleLowDataRate(); +} + +long sx127x::getSignalBandwidth() { + byte bw = (readRegister(REG_MODEM_CONFIG_1_7X) >> 4); + switch (bw) { + case 0: return 7.8E3; + case 1: return 10.4E3; + case 2: return 15.6E3; + case 3: return 20.8E3; + case 4: return 31.25E3; + case 5: return 41.7E3; + case 6: return 62.5E3; + case 7: return 125E3; + case 8: return 250E3; + case 9: return 500E3; } + + return 0; +} + +void sx127x::setSignalBandwidth(long sbw) { + int bw; + if (sbw <= 7.8E3) { + bw = 0; + } else if (sbw <= 10.4E3) { + bw = 1; + } else if (sbw <= 15.6E3) { + bw = 2; + } else if (sbw <= 20.8E3) { + bw = 3; + } else if (sbw <= 31.25E3) { + bw = 4; + } else if (sbw <= 41.7E3) { + bw = 5; + } else if (sbw <= 62.5E3) { + bw = 6; + } else if (sbw <= 125E3) { + bw = 7; + } else if (sbw <= 250E3) { + bw = 8; + } else /*if (sbw <= 250E3)*/ { + bw = 9; + } + + writeRegister(REG_MODEM_CONFIG_1_7X, (readRegister(REG_MODEM_CONFIG_1_7X) & 0x0f) | (bw << 4)); + handleLowDataRate(); + optimizeModemSensitivity(); +} + +void sx127x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + int cr = denominator - 4; + writeRegister(REG_MODEM_CONFIG_1_7X, (readRegister(REG_MODEM_CONFIG_1_7X) & 0xf1) | (cr << 1)); +} + +void sx127x::setPreambleLength(long preamble_symbols) { + long length = preamble_symbols - 4; + writeRegister(REG_PREAMBLE_MSB_7X, (uint8_t)(length >> 8)); + writeRegister(REG_PREAMBLE_LSB_7X, (uint8_t)(length >> 0)); +} + +extern bool lora_low_datarate; +void sx127x::handleLowDataRate() { + int sf = (readRegister(REG_MODEM_CONFIG_2_7X) >> 4); + if ( long( (1< 16) { + // Set auto AGC and LowDataRateOptimize + writeRegister(REG_MODEM_CONFIG_3_7X, (1<<3)|(1<<2)); + lora_low_datarate = true; + } else { + // Only set auto AGC + writeRegister(REG_MODEM_CONFIG_3_7X, (1<<2)); + lora_low_datarate = false; + } +} + +void sx127x::optimizeModemSensitivity() { + byte bw = (readRegister(REG_MODEM_CONFIG_1_7X) >> 4); + uint32_t freq = getFrequency(); + + if (bw == 9 && (410E6 <= freq) && (freq <= 525E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2_7X, 0x7f); + } else if (bw == 9 && (820E6 <= freq) && (freq <= 1020E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2_7X, 0x64); + } else { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x03); + } +} + +void ISR_VECT sx127x::handleDio0Rise() { + int irqFlags = readRegister(REG_IRQ_FLAGS_7X); + + // Clear IRQs + writeRegister(REG_IRQ_FLAGS_7X, irqFlags); + if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK_7X) == 0) { + _packetIndex = 0; + int packetLength = _implicitHeaderMode ? readRegister(REG_PAYLOAD_LENGTH_7X) : readRegister(REG_RX_NB_BYTES_7X); + writeRegister(REG_FIFO_ADDR_PTR_7X, readRegister(REG_FIFO_RX_CURRENT_ADDR_7X)); + if (_onReceive) { _onReceive(packetLength); } + writeRegister(REG_FIFO_ADDR_PTR_7X, 0); + } +} + +void ISR_VECT sx127x::onDio0Rise() { sx127x_modem.handleDio0Rise(); } + +sx127x sx127x_modem; + +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/sx127x.h b/vendor/rnode_firmware/sx127x.h new file mode 100644 index 0000000..7639857 --- /dev/null +++ b/vendor/rnode_firmware/sx127x.h @@ -0,0 +1,114 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX1276_H +#define SX1276_H + +#include +#include +#include "Modem.h" + +#define LORA_DEFAULT_SS_PIN 10 +#define LORA_DEFAULT_RESET_PIN 9 +#define LORA_DEFAULT_DIO0_PIN 2 +#define LORA_DEFAULT_BUSY_PIN -1 + +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 + +#define RSSI_OFFSET 157 + +// Modem status flags +#define SIG_DETECT 0x01 +#define SIG_SYNCED 0x02 +#define RX_ONGOING 0x04 + +class sx127x : public Stream { +public: + sx127x(); + + int begin(long frequency); + void end(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(unsigned long frequency); + void setSpreadingFactor(int sf); + long getSignalBandwidth(); + void setSignalBandwidth(long sbw); + void setCodingRate4(int denominator); + void setPreambleLength(long preamble_symbols); + void setSyncWord(uint8_t sw); + bool dcd(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + byte random(); + + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN); + void setSPIFrequency(uint32_t frequency); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + void handleDio0Rise(); + + uint8_t readRegister(uint8_t address); + void writeRegister(uint8_t address, uint8_t value); + uint8_t singleTransfer(uint8_t address, uint8_t value); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + +private: + SPISettings _spiSettings; + int _ss; + int _reset; + int _dio0; + int _busy; + long _frequency; + int _packetIndex; + int _implicitHeaderMode; + bool _preinit_done; + void (*_onReceive)(int); +}; + +extern sx127x sx127x_modem; + +#endif diff --git a/vendor/rnode_firmware/sx128x.cpp b/vendor/rnode_firmware/sx128x.cpp new file mode 100644 index 0000000..59c464e --- /dev/null +++ b/vendor/rnode_firmware/sx128x.cpp @@ -0,0 +1,887 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1280 +#include "sx128x.h" + +#define MCU_1284P 0x91 +#define MCU_2560 0x92 +#define MCU_ESP32 0x81 +#define MCU_NRF52 0x71 +#if defined(__AVR_ATmega1284P__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_1284P +#elif defined(__AVR_ATmega2560__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_2560 +#elif defined(ESP32) + #define PLATFORM PLATFORM_ESP32 + #define MCU_VARIANT MCU_ESP32 +#elif defined(NRF52840_XXAA) + #define PLATFORM PLATFORM_NRF52 + #define MCU_VARIANT MCU_NRF52 +#endif + +#ifndef MCU_VARIANT + #error No MCU variant defined, cannot compile +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +// SX128x registers +#define OP_RF_FREQ_8X 0x86 +#define OP_SLEEP_8X 0x84 +#define OP_STANDBY_8X 0x80 +#define OP_TX_8X 0x83 +#define OP_RX_8X 0x82 +#define OP_SET_IRQ_FLAGS_8X 0x8D +#define OP_CLEAR_IRQ_STATUS_8X 0x97 +#define OP_GET_IRQ_STATUS_8X 0x15 +#define OP_RX_BUFFER_STATUS_8X 0x17 +#define OP_PACKET_STATUS_8X 0x1D +#define OP_CURRENT_RSSI_8X 0x1F +#define OP_MODULATION_PARAMS_8X 0x8B +#define OP_PACKET_PARAMS_8X 0x8C +#define OP_STATUS_8X 0xC0 +#define OP_TX_PARAMS_8X 0x8E +#define OP_PACKET_TYPE_8X 0x8A +#define OP_BUFFER_BASE_ADDR_8X 0x8F +#define OP_READ_REGISTER_8X 0x19 +#define OP_WRITE_REGISTER_8X 0x18 +#define IRQ_TX_DONE_MASK_8X 0x01 +#define IRQ_RX_DONE_MASK_8X 0x02 +#define IRQ_HEADER_DET_MASK_8X 0x10 +#define IRQ_HEADER_ERROR_MASK_8X 0x20 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_8X 0x40 + +#define MODE_LONG_RANGE_MODE_8X 0x01 + +#define OP_FIFO_WRITE_8X 0x1A +#define OP_FIFO_READ_8X 0x1B +#define IRQ_PREAMBLE_DET_MASK_8X 0x80 + +#define REG_PACKET_SIZE 0x901 +#define REG_FIRM_VER_MSB 0x154 +#define REG_FIRM_VER_LSB 0x153 + +#define XTAL_FREQ_8X (double)52000000 +#define FREQ_DIV_8X (double)pow(2.0, 18.0) +#define FREQ_STEP_8X (double)(XTAL_FREQ_8X / FREQ_DIV_8X) + +#if defined(NRF52840_XXAA) + extern SPIClass spiModem; + #define SPI spiModem +#endif + +extern SPIClass SPI; + +#define MAX_PKT_LENGTH 255 + +sx128x::sx128x() : + _spiSettings(8E6, MSBFIRST, SPI_MODE0), + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _rxen(pin_rxen), _busy(LORA_DEFAULT_BUSY_PIN), _txen(pin_txen), + _frequency(0), _txp(0), _sf(0x05), _bw(0x34), _cr(0x01), _packetIndex(0), _implicitHeaderMode(0), _payloadLength(255), _crcMode(0), _fifo_tx_addr_ptr(0), + _fifo_rx_addr_ptr(0), _rxPacketLength(0), _preinit_done(false), _tcxo(false) { setTimeout(0); } + +bool ISR_VECT sx128x::getPacketValidity() { + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, buf, 2); + if ((buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK_8X) == 0) { return true; } + else { return false; } +} + +void ISR_VECT sx128x::onDio0Rise() { + BaseType_t int_status = taskENTER_CRITICAL_FROM_ISR(); + // On the SX1280, there is a bug which can cause the busy line + // to remain high if a high amount of packets are received when + // in continuous RX mode. This is documented as Errata 16.1 in + // the SX1280 datasheet v3.2 (page 149) + // Therefore, the modem is set into receive mode each time a packet is received. + if (sx128x_modem.getPacketValidity()) { sx128x_modem.receive(); sx128x_modem.handleDio0Rise(); } + else { sx128x_modem.receive(); } + + taskEXIT_CRITICAL_FROM_ISR(int_status); +} + +void sx128x::handleDio0Rise() { + _packetIndex = 0; + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_8X, rxbuf, 2); + + // If implicit header mode is enabled, use pre-set packet length as payload length instead. + // See SX1280 datasheet v3.2, page 92 + if (_implicitHeaderMode == 0x80) { _rxPacketLength = _payloadLength; } + else { _rxPacketLength = rxbuf[0]; } + + if (_receive_callback) { _receive_callback(_rxPacketLength); } +} + +bool sx128x::preInit() { + pinMode(_ss, OUTPUT); + digitalWrite(_ss, HIGH); + + // TODO: Check if this change causes issues on any platforms + #if MCU_VARIANT == MCU_ESP32 + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK + SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #else + SPI.begin(); + #endif + #else + SPI.begin(); + #endif + + // Detect modem (retry for up to 500ms) + long start = millis(); + uint8_t version_msb; + uint8_t version_lsb; + while (((millis() - start) < 500) && (millis() >= start)) { + version_msb = readRegister(REG_FIRM_VER_MSB); + version_lsb = readRegister(REG_FIRM_VER_LSB); + if ((version_msb == 0xB7 && version_lsb == 0xA9) || (version_msb == 0xB5 && version_lsb == 0xA9)) { break; } + delay(100); + } + + if ((version_msb != 0xB7 || version_lsb != 0xA9) && (version_msb != 0xB5 || version_lsb != 0xA9)) { return false; } + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx128x::readRegister(uint16_t address) { return singleTransfer(OP_READ_REGISTER_8X, address, 0x00); } +void sx128x::writeRegister(uint16_t address, uint8_t value) { singleTransfer(OP_WRITE_REGISTER_8X, address, value); } + +uint8_t ISR_VECT sx128x::singleTransfer(uint8_t opcode, uint16_t address, uint8_t value) { + waitOnBusy(); + uint8_t response; + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + SPI.transfer((address & 0xFF00) >> 8); + SPI.transfer(address & 0x00FF); + if (opcode == OP_READ_REGISTER_8X) { SPI.transfer(0x00); } + response = SPI.transfer(value); + SPI.endTransaction(); + digitalWrite(_ss, HIGH); + + return response; +} + +void sx128x::rxAntEnable() { + if (_txen != -1) { digitalWrite(_txen, LOW); } + if (_rxen != -1) { digitalWrite(_rxen, HIGH); } +} + +void sx128x::txAntEnable() { + if (_txen != -1) { digitalWrite(_txen, HIGH); } + if (_rxen != -1) { digitalWrite(_rxen, LOW); } +} + +void sx128x::loraMode() { + uint8_t mode = MODE_LONG_RANGE_MODE_8X; + executeOpcode(OP_PACKET_TYPE_8X, &mode, 1); +} + +void sx128x::waitOnBusy() { + unsigned long time = millis(); + while (digitalRead(_busy) == HIGH) { + if (millis() >= (time + 100)) { break; } + } +} + +void sx128x::executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + for (int i = 0; i < size; i++) { SPI.transfer(buffer[i]); } + SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx128x::executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + SPI.transfer(0x00); + for (int i = 0; i < size; i++) { buffer[i] = SPI.transfer(0x00); } + SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx128x::writeBuffer(const uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_WRITE_8X); + SPI.transfer(_fifo_tx_addr_ptr); + for (int i = 0; i < size; i++) { SPI.transfer(buffer[i]); _fifo_tx_addr_ptr++; } + SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx128x::readBuffer(uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_READ_8X); + SPI.transfer(_fifo_rx_addr_ptr); + SPI.transfer(0x00); + for (int i = 0; i < size; i++) { buffer[i] = SPI.transfer(0x00); } + SPI.endTransaction(); + digitalWrite(_ss, HIGH); +} + +void sx128x::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr) { + // because there is no access to these registers on the sx1280, we have + // to set all these parameters at once or not at all. + uint8_t buf[3]; + buf[0] = sf << 4; + buf[1] = bw; + buf[2] = cr; + executeOpcode(OP_MODULATION_PARAMS_8X, buf, 3); + + if (sf <= 6) { writeRegister(0x925, 0x1E); } + else if (sf <= 8) { writeRegister(0x925, 0x37); } + else if (sf >= 9) { writeRegister(0x925, 0x32); } + writeRegister(0x093C, 0x1); +} + +uint8_t preamble_e = 0; +uint8_t preamble_m = 0; +uint32_t last_me_result_target = 0; +extern long lora_preamble_symbols; +void sx128x::setPacketParams(uint32_t target_preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc) { + if (last_me_result_target != target_preamble_symbols) { + // Calculate exponent and mantissa values for modem + if (target_preamble_symbols >= 0xF000) target_preamble_symbols = 0xF000; + uint32_t calculated_preamble_symbols; + uint8_t e = 1; + uint8_t m = 1; + while (e <= 15) { + while (m <= 15) { + calculated_preamble_symbols = m * (pow(2,e)); + if (calculated_preamble_symbols >= target_preamble_symbols-4) break; + m++; + } + + if (calculated_preamble_symbols >= target_preamble_symbols-4) break; + m = 1; e++; + } + + last_me_result_target = target_preamble_symbols; + lora_preamble_symbols = calculated_preamble_symbols+4; + _preambleLength = lora_preamble_symbols; + + preamble_e = e; + preamble_m = m; + } + + uint8_t buf[7]; + buf[0] = (preamble_e << 4) | preamble_m; + buf[1] = headermode; + buf[2] = payload_length; + buf[3] = crc; + buf[4] = 0x40; // Standard IQ setting (no inversion) + buf[5] = 0x00; // Unused params + buf[6] = 0x00; + + executeOpcode(OP_PACKET_PARAMS_8X, buf, 7); +} + +void sx128x::reset() { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } +} + +int sx128x::begin(unsigned long frequency) { + reset(); + + if (_rxen != -1) { pinMode(_rxen, OUTPUT); } + if (_txen != -1) { pinMode(_txen, OUTPUT); } + if (_busy != -1) { pinMode(_busy, INPUT); } + + if (!_preinit_done) { + if (!preInit()) { + return false; + } + } + + standby(); + loraMode(); + rxAntEnable(); + setFrequency(frequency); + + // TODO: Implement LNA boost + //writeRegister(REG_LNA, 0x96); + + setModulationParams(_sf, _bw, _cr); + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + setTxPower(_txp); + + // Set base addresses + uint8_t basebuf[2] = {0}; + executeOpcode(OP_BUFFER_BASE_ADDR_8X, basebuf, 2); + + _radio_online = true; + return 1; +} + +void sx128x::end() { + sleep(); + SPI.end(); + _bitrate = 0; + _radio_online = false; + _preinit_done = false; +} + +int sx128x::beginPacket(int implicitHeader) { + standby(); + + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + _payloadLength = 0; + _fifo_tx_addr_ptr = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + + return 1; +} + +int sx128x::endPacket() { + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + txAntEnable(); + + // Put in single TX mode + uint8_t timeout[3] = {0}; + executeOpcode(OP_TX_8X, timeout, 3); + + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + + // Wait for TX done + bool timed_out = false; + uint32_t w_timeout = millis()+LORA_MODEM_TIMEOUT_MS; + while ((millis() < w_timeout) && ((buf[1] & IRQ_TX_DONE_MASK_8X) == 0)) { + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + yield(); + } + + if (!(millis() < w_timeout)) { timed_out = true; } + + // clear IRQ's + uint8_t mask[2]; + mask[0] = 0x00; + mask[1] = IRQ_TX_DONE_MASK_8X; + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, mask, 2); + + if (timed_out) { return 0; } + else { return 1; } +} + +unsigned long preamble_detected_at = 0; +extern long lora_preamble_time_ms; +extern long lora_header_time_ms; +bool false_preamble_detected = false; +bool sx128x::dcd() { + uint8_t buf[2] = {0}; executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + uint32_t now = millis(); + + bool header_detected = false; + bool carrier_detected = false; + + if ((buf[1] & IRQ_HEADER_DET_MASK_8X) != 0) { header_detected = true; carrier_detected = true; } + else { header_detected = false; } + + if ((buf[0] & IRQ_PREAMBLE_DET_MASK_8X) != 0) { + carrier_detected = true; + if (preamble_detected_at == 0) { preamble_detected_at = now; } + if (now - preamble_detected_at > lora_preamble_time_ms + lora_header_time_ms) { + preamble_detected_at = 0; + if (!header_detected) { false_preamble_detected = true; } + uint8_t clearbuf[2] = {0}; clearbuf[0] = IRQ_PREAMBLE_DET_MASK_8X; + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, clearbuf, 2); + } + } + + // TODO: Maybe there's a way of unlatching the RSSI + // status without re-activating receive mode? + if (false_preamble_detected) { sx128x_modem.receive(); false_preamble_detected = false; } + return carrier_detected; +} + + +uint8_t sx128x::currentRssiRaw() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_8X, &byte, 1); + return byte; +} + +int ISR_VECT sx128x::currentRssi() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_8X, &byte, 1); + int rssi = -byte / 2; + return rssi; +} + +uint8_t sx128x::packetRssiRaw() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return buf[0]; +} + +int ISR_VECT sx128x::packetRssi(uint8_t pkt_snr_raw) { + // TODO: May need more calculations here + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + int pkt_rssi = -buf[0] / 2; + return pkt_rssi; +} + +uint8_t ISR_VECT sx128x::packetSnrRaw() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return buf[1]; +} + +float ISR_VECT sx128x::packetSnr() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return float(buf[1]) * 0.25; +} + +long sx128x::packetFrequencyError() { + // TODO: Implement this, page 120 of sx1280 datasheet + int32_t freqError = 0; + const float fError = 0.0; + return static_cast(fError); +} + +void sx128x::flush() { } +int ISR_VECT sx128x::available() { return _rxPacketLength - _packetIndex; } +size_t sx128x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } +size_t sx128x::write(const uint8_t *buffer, size_t size) { + if ((_payloadLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - _payloadLength; } + writeBuffer(buffer, size); + _payloadLength = _payloadLength + size; + return size; +} + +int ISR_VECT sx128x::read() { + if (!available()) { return -1; } + + // If received new packet + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_8X, rxbuf, 2); + int size; + + // If implicit header mode is enabled, read packet length as payload length instead. + // See SX1280 datasheet v3.2, page 92 + if (_implicitHeaderMode == 0x80) { + size = _payloadLength; + } else { + size = rxbuf[0]; + } + + _fifo_rx_addr_ptr = rxbuf[1]; + if (size > 255) { size = 255; } + + readBuffer(_packet, size); + } + + uint8_t byte = _packet[_packetIndex]; + _packetIndex++; + return byte; +} + +int sx128x::peek() { + if (!available()) { return -1; } + uint8_t b = _packet[_packetIndex]; + return b; +} + + +void sx128x::onReceive(void(*callback)(int)) { + _receive_callback = callback; + + if (callback) { + pinMode(_dio0, INPUT); + + // Set preamble and header detection irqs, plus dio0 mask + uint8_t buf[8]; + + // Set irq masks, enable all + buf[0] = 0xFF; + buf[1] = 0xFF; + + // On the SX1280, no RxDone IRQ is generated if a packet is received with + // an invalid header, but the modem will be taken out of single RX mode. + // This can cause the modem to not receive packets until it is reset + // again. This is documented as Errata 16.2 in the SX1280 datasheet v3.2 + // (page 150) Below, the header error IRQ is mapped to dio0 so that the + // modem can be set into RX mode again on reception of a corrupted + // header. + // set dio0 masks + buf[2] = 0x00; + buf[3] = IRQ_RX_DONE_MASK_8X | IRQ_HEADER_ERROR_MASK_8X; + + // Set dio1 masks + buf[4] = 0x00; + buf[5] = 0x00; + + // Set dio2 masks + buf[6] = 0x00; + buf[7] = 0x00; + + executeOpcode(OP_SET_IRQ_FLAGS_8X, buf, 8); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + + attachInterrupt(digitalPinToInterrupt(_dio0), onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + #ifdef SPI_HAS_NOTUSINGINTERRUPT + _spiModem->notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx128x::receive(int size) { + if (size > 0) { + implicitHeaderMode(); + // Tell radio payload length + //_rxPacketLength = size; + //_payloadLength = size; + //setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + } else { + explicitHeaderMode(); + } + + rxAntEnable(); + + // On the SX1280, there is a bug which can cause the busy line + // to remain high if a high amount of packets are received when + // in continuous RX mode. This is documented as Errata 16.1 in + // the SX1280 datasheet v3.2 (page 149) + // Therefore, the modem is set to single RX mode below instead. + + // uint8_t mode[3] = {0x03, 0xFF, 0xFF}; // Countinuous RX mode + uint8_t mode[3] = {0}; // single RX mode + executeOpcode(OP_RX_8X, mode, 3); +} + +void sx128x::standby() { + uint8_t byte = 0x01; // Always use STDBY_XOSC + executeOpcode(OP_STANDBY_8X, &byte, 1); +} + +void sx128x::setPins(int ss, int reset, int dio0, int busy, int rxen, int txen) { + _ss = ss; + _reset = reset; + _dio0 = dio0; + _busy = busy; + _rxen = rxen; + _txen = txen; +} + +void sx128x::setTxPower(int level, int outputPin) { + uint8_t tx_buf[2]; + + // RAK4631 with WisBlock SX1280 module (LIBSYS002) + #if BOARD_VARIANT == MODEL_13 || BOARD_VARIANT == MODEL_21 + if (level > 27) { level = 27; } + else if (level < 0) { level = 0; } + + _txp = level; + int reg_value; + switch (level) { + case 0: + reg_value = -18; + break; + case 1: + reg_value = -16; + break; + case 2: + reg_value = -15; + break; + case 3: + reg_value = -14; + break; + case 4: + reg_value = -13; + break; + case 5: + reg_value = -12; + break; + case 6: + reg_value = -11; + break; + case 7: + reg_value = -9; + break; + case 8: + reg_value = -8; + break; + case 9: + reg_value = -7; + break; + case 10: + reg_value = -6; + break; + case 11: + reg_value = -5; + break; + case 12: + reg_value = -4; + break; + case 13: + reg_value = -3; + break; + case 14: + reg_value = -2; + break; + case 15: + reg_value = -1; + break; + case 16: + reg_value = 0; + break; + case 17: + reg_value = 1; + break; + case 18: + reg_value = 2; + break; + case 19: + reg_value = 3; + break; + case 20: + reg_value = 4; + break; + case 21: + reg_value = 5; + break; + case 22: + reg_value = 6; + break; + case 23: + reg_value = 7; + break; + case 24: + reg_value = 8; + break; + case 25: + reg_value = 9; + break; + case 26: + reg_value = 12; + break; + case 27: + reg_value = 13; + break; + default: + reg_value = 0; + break; + } + + tx_buf[0] = reg_value + 18; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + executeOpcode(OP_TX_PARAMS_8X, tx_buf, 2); + + // T3S3 SX1280 PA + #elif BOARD_VARIANT == MODEL_AC + if (level > 20) { level = 20; } + else if (level < 0) { level = 0; } + + _txp = level; + int reg_value; + switch (level) { + case 0: + reg_value = -18; + break; + case 1: + reg_value = -17; + break; + case 2: + reg_value = -16; + break; + case 3: + reg_value = -15; + break; + case 4: + reg_value = -14; + break; + case 5: + reg_value = -13; + break; + case 6: + reg_value = -12; + break; + case 7: + reg_value = -10; + break; + case 8: + reg_value = -9; + break; + case 9: + reg_value = -8; + break; + case 10: + reg_value = -7; + break; + case 11: + reg_value = -6; + break; + case 12: + reg_value = -5; + break; + case 13: + reg_value = -4; + break; + case 14: + reg_value = -3; + break; + case 15: + reg_value = -2; + break; + case 16: + reg_value = -1; + break; + case 17: + reg_value = 0; + break; + case 18: + reg_value = 1; + break; + case 19: + reg_value = 2; + break; + case 20: + reg_value = 3; + break; + default: + reg_value = 0; + break; + } + tx_buf[0] = reg_value; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + + // For SX1280 boards with no specific PA requirements + #else + if (level > 13) { level = 13; } + else if (level < -18) { level = -18; } + _txp = level; + tx_buf[0] = level + 18; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + #endif + + executeOpcode(OP_TX_PARAMS_8X, tx_buf, 2); +} + +void sx128x::setFrequency(uint32_t frequency) { + _frequency = frequency; + uint8_t buf[3]; + uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP_8X); + buf[0] = ((freq >> 16) & 0xFF); + buf[1] = ((freq >> 8) & 0xFF); + buf[2] = (freq & 0xFF); + + executeOpcode(OP_RF_FREQ_8X, buf, 3); +} + +uint32_t sx128x::getFrequency() { + // We can't read the frequency on the sx1280 + uint32_t frequency = _frequency; + return frequency; +} + +void sx128x::setSpreadingFactor(int sf) { + if (sf < 5) { sf = 5; } + else if (sf > 12) { sf = 12; } + _sf = sf; + + setModulationParams(sf, _bw, _cr); + handleLowDataRate(); +} + +uint32_t sx128x::getSignalBandwidth() { + int bw = _bw; + switch (bw) { + case 0x34: return 203.125E3; + case 0x26: return 406.25E3; + case 0x18: return 812.5E3; + case 0x0A: return 1625E3; + } + + return 0; +} + +void sx128x::setSignalBandwidth(uint32_t sbw) { + if (sbw <= 203.125E3) { _bw = 0x34; } + else if (sbw <= 406.25E3) { _bw = 0x26; } + else if (sbw <= 812.5E3) { _bw = 0x18; } + else { _bw = 0x0A; } + + setModulationParams(_sf, _bw, _cr); + handleLowDataRate(); + optimizeModemSensitivity(); +} + +// TODO: add support for new interleaving scheme, see page 117 of sx1280 datasheet +void sx128x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + _cr = denominator - 4; + setModulationParams(_sf, _bw, _cr); +} + +extern bool lora_low_datarate; +void sx128x::handleLowDataRate() { + if (_sf > 10) { lora_low_datarate = true; } + else { lora_low_datarate = false; } +} + +void sx128x::optimizeModemSensitivity() { } // TODO: Check if there's anything the sx1280 can do here +uint8_t sx128x::getCodingRate4() { return _cr + 4; } +void sx128x::setPreambleLength(long preamble_symbols) { setPacketParams(preamble_symbols, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::setSyncWord(int sw) { } // TODO: Implement +void sx128x::enableTCXO() { } // TODO: Need to check how to implement on sx1280 +void sx128x::disableTCXO() { } // TODO: Need to check how to implement on sx1280 +void sx128x::sleep() { uint8_t byte = 0x00; executeOpcode(OP_SLEEP_8X, &byte, 1); } +uint8_t sx128x::getTxPower() { return _txp; } +uint8_t sx128x::getSpreadingFactor() { return _sf; } +void sx128x::enableCrc() { _crcMode = 0x20; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::disableCrc() { _crcMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx128x::explicitHeaderMode() { _implicitHeaderMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::implicitHeaderMode() { _implicitHeaderMode = 0x80; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::dumpRegisters(Stream& out) { for (int i = 0; i < 128; i++) { out.print("0x"); out.print(i, HEX); out.print(": 0x"); out.println(readRegister(i), HEX); } } + +sx128x sx128x_modem; +#endif \ No newline at end of file diff --git a/vendor/rnode_firmware/sx128x.h b/vendor/rnode_firmware/sx128x.h new file mode 100644 index 0000000..ccaeb04 --- /dev/null +++ b/vendor/rnode_firmware/sx128x.h @@ -0,0 +1,146 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX128X_H +#define SX128X_H + +#include +#include +#include "Modem.h" + +#define LORA_DEFAULT_SS_PIN 10 +#define LORA_DEFAULT_RESET_PIN 9 +#define LORA_DEFAULT_DIO0_PIN 2 +#define LORA_DEFAULT_RXEN_PIN -1 +#define LORA_DEFAULT_TXEN_PIN -1 +#define LORA_DEFAULT_BUSY_PIN -1 +#define LORA_MODEM_TIMEOUT_MS 15E3 +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 +#define RSSI_OFFSET 157 + +class sx128x : public Stream { +public: + sx128x(); + + int begin(unsigned long frequency); + void end(); + void reset(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(uint32_t frequency); + void setSpreadingFactor(int sf); + uint8_t getSpreadingFactor(); + uint32_t getSignalBandwidth(); + void setSignalBandwidth(uint32_t sbw); + void setCodingRate4(int denominator); + uint8_t getCodingRate4(); + void setPreambleLength(long preamble_symbols); + void setSyncWord(int sw); + bool dcd(); + void clearIRQStatus(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + void txAntEnable(); + void rxAntEnable(); + void loraMode(); + void waitOnBusy(); + void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size); + void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size); + void writeBuffer(const uint8_t* buffer, size_t size); + void readBuffer(uint8_t* buffer, size_t size); + void setPacketParams(uint32_t target_preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); + void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr); + + void crc() { enableCrc(); } + void noCrc() { disableCrc(); } + + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN, int txen = LORA_DEFAULT_TXEN_PIN); + void setSPIFrequency(uint32_t frequency); + + void dumpRegisters(Stream& out); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + bool getPacketValidity(); + void handleDio0Rise(); + + uint8_t readRegister(uint16_t address); + void writeRegister(uint16_t address, uint8_t value); + uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + +private: + SPISettings _spiSettings; + int _ss; + int _reset; + int _dio0; + int _rxen; + int _txen; + int _busy; + int _modem; + unsigned long _frequency; + int _txp; + uint8_t _sf; + uint8_t _bw; + uint8_t _cr; + int _packetIndex; + uint32_t _preambleLength; + int _implicitHeaderMode; + int _payloadLength; + int _crcMode; + int _fifo_tx_addr_ptr; + int _fifo_rx_addr_ptr; + uint8_t _packet[256]; + bool _preinit_done; + bool _tcxo; + bool _radio_online; + int _rxPacketLength; + uint32_t _bitrate; + void (*_receive_callback)(int); +}; + +extern sx128x sx128x_modem; + +#endif diff --git a/vendor/rnode_firmware/tools/cardputer_adv_firmware_hash.py b/vendor/rnode_firmware/tools/cardputer_adv_firmware_hash.py new file mode 100644 index 0000000..dc44e97 --- /dev/null +++ b/vendor/rnode_firmware/tools/cardputer_adv_firmware_hash.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 + +import argparse +import hashlib +import sys +import time + +try: + import serial +except ImportError: + serial = None + + +FEND = 0xC0 +FESC = 0xDB +TFEND = 0xDC +TFESC = 0xDD + +CMD_FW_HASH = 0x58 +CMD_HASHES = 0x60 +CMD_RESET = 0x55 +CMD_RESET_BYTE = 0xF8 + + +def firmware_hash_from_file(path): + with open(path, "rb") as handle: + data = handle.read() + + if len(data) > 32: + calculated = hashlib.sha256(data[:-32]).digest() + embedded = data[-32:] + if calculated == embedded: + return embedded + + return hashlib.sha256(data).digest() + + +def parse_hash(value): + try: + digest = bytes.fromhex(value) + except ValueError as exc: + raise SystemExit(f"invalid hash hex: {exc}") + + if len(digest) != 32: + raise SystemExit("firmware hash must be exactly 32 bytes / 64 hex characters") + + return digest + + +def escape_kiss(payload): + escaped = bytearray() + for byte in payload: + if byte == FEND: + escaped.extend((FESC, TFEND)) + elif byte == FESC: + escaped.extend((FESC, TFESC)) + else: + escaped.append(byte) + return bytes(escaped) + + +def frame(command, payload=b""): + return bytes((FEND, command)) + escape_kiss(payload) + bytes((FEND,)) + + +def read_frames(port, timeout): + deadline = time.monotonic() + timeout + in_frame = False + escaped = False + command = None + payload = bytearray() + + while time.monotonic() < deadline: + chunk = port.read(1) + if not chunk: + continue + + byte = chunk[0] + if byte == FEND: + if in_frame and command is not None: + yield command, bytes(payload) + in_frame = True + escaped = False + command = None + payload = bytearray() + continue + + if not in_frame: + continue + + if command is None: + command = byte + continue + + if byte == FESC: + escaped = True + continue + + if escaped: + if byte == TFEND: + byte = FEND + elif byte == TFESC: + byte = FESC + escaped = False + + payload.append(byte) + + +def require_serial(): + if serial is None: + raise SystemExit("pyserial is required for serial hash writes") + + +def read_running_firmware_hash(port, timeout): + port.reset_input_buffer() + port.write(frame(CMD_HASHES, bytes((0x02,)))) + port.flush() + + for command, payload in read_frames(port, timeout): + if command == CMD_HASHES and len(payload) == 33 and payload[0] == 0x02: + return payload[1:] + + raise SystemExit("timed out waiting for the device's running firmware hash") + + +def write_firmware_hash(port, digest, reset): + port.write(frame(CMD_FW_HASH, digest)) + port.flush() + time.sleep(0.02) + + if reset: + port.write(frame(CMD_RESET, bytes((CMD_RESET_BYTE,)))) + port.flush() + + +def main(): + parser = argparse.ArgumentParser( + description="Set the RNode firmware hash for the Cardputer Adv build." + ) + source = parser.add_mutually_exclusive_group() + source.add_argument( + "--firmware", + default="build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin", + help="ESP32 app binary to hash", + ) + source.add_argument("--hash", help="64-character firmware hash hex string") + source.add_argument( + "--from-device", + action="store_true", + help="read the running firmware hash from the device and write it back as target", + ) + parser.add_argument("--port", help="serial port for the Cardputer Adv") + parser.add_argument("--baud", type=int, default=115200) + parser.add_argument("--timeout", type=float, default=3.0) + parser.add_argument("--settle", type=float, default=1.5, help="seconds to wait after opening serial") + parser.add_argument("--reset", action="store_true", help="reset after writing the hash") + parser.add_argument("--print-only", action="store_true", help="print the hash and do not write") + args = parser.parse_args() + + if args.hash: + digest = parse_hash(args.hash) + elif args.from_device: + if args.print_only: + raise SystemExit("--from-device requires a serial write session") + require_serial() + if not args.port: + raise SystemExit("--port is required with --from-device") + with serial.Serial(args.port, args.baud, timeout=0.1, write_timeout=args.timeout) as port: + time.sleep(args.settle) + digest = read_running_firmware_hash(port, args.timeout) + write_firmware_hash(port, digest, args.reset) + print(digest.hex()) + return 0 + else: + digest = firmware_hash_from_file(args.firmware) + + if args.print_only: + print(digest.hex()) + return 0 + + require_serial() + if not args.port: + raise SystemExit("--port is required unless --print-only is used") + + with serial.Serial(args.port, args.baud, timeout=0.1, write_timeout=args.timeout) as port: + time.sleep(args.settle) + write_firmware_hash(port, digest, args.reset) + + print(digest.hex()) + return 0 + + +if __name__ == "__main__": + sys.exit(main())