From 6c0da535b84930d9425f9f1fc4b5facc67fa341e Mon Sep 17 00:00:00 2001 From: gjelsoe <36234524+gjelsoe@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:45:59 +0100 Subject: [PATCH 1/2] Add Radiomaster Bandit/Bandit Nano support Added support for 5-Way analog joystick. Added Custom Sh1115 OLED driver. Added NeoPixels support for Radiomaster Bandit. Power output 20-30 dbm (100mW-1000mW). Changed so Analog joystick can be used in UI. Changed so NeoPixels is used for new Message. (Color can be defined). Radiomaster Bandit Micro uses the same code as Nano. --- examples/companion_radio/ui-new/UITask.cpp | 121 +++++++- examples/companion_radio/ui-new/UITask.h | 19 +- src/helpers/ui/AnalogJoystick.cpp | 106 +++++++ src/helpers/ui/AnalogJoystick.h | 37 +++ src/helpers/ui/SH1115Display.cpp | 92 ++++++ src/helpers/ui/SH1115Display.h | 44 +++ variants/radiomaster_900_bandit/BanditBoard.h | 127 ++++++++ .../radiomaster_900_bandit/COLOR_EXAMPLES.md | 276 +++++++++++++++++ .../LED_CONFIGURATION_GUIDE.md | 286 ++++++++++++++++++ .../radiomaster_900_bandit/QUICK_REFERENCE.md | 190 ++++++++++++ .../radiomaster_900_bandit/platformio.ini | 94 ++++++ variants/radiomaster_900_bandit/target.cpp | 160 ++++++++++ variants/radiomaster_900_bandit/target.h | 33 ++ .../BanditNanoBoard.h | 74 +++++ .../platformio.ini | 99 ++++++ .../radiomaster_900_bandit_nano/target.cpp | 160 ++++++++++ variants/radiomaster_900_bandit_nano/target.h | 35 +++ 17 files changed, 1951 insertions(+), 2 deletions(-) create mode 100644 src/helpers/ui/AnalogJoystick.cpp create mode 100644 src/helpers/ui/AnalogJoystick.h create mode 100644 src/helpers/ui/SH1115Display.cpp create mode 100644 src/helpers/ui/SH1115Display.h create mode 100644 variants/radiomaster_900_bandit/BanditBoard.h create mode 100644 variants/radiomaster_900_bandit/COLOR_EXAMPLES.md create mode 100644 variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md create mode 100644 variants/radiomaster_900_bandit/QUICK_REFERENCE.md create mode 100644 variants/radiomaster_900_bandit/platformio.ini create mode 100644 variants/radiomaster_900_bandit/target.cpp create mode 100644 variants/radiomaster_900_bandit/target.h create mode 100644 variants/radiomaster_900_bandit_nano/BanditNanoBoard.h create mode 100644 variants/radiomaster_900_bandit_nano/platformio.ini create mode 100644 variants/radiomaster_900_bandit_nano/target.cpp create mode 100644 variants/radiomaster_900_bandit_nano/target.h diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ae2d93753..0f302e80e 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -19,11 +19,31 @@ #define LONG_PRESS_MILLIS 1200 +#ifdef RADIOMASTER_900_BANDIT +// NeoPixel message notification settings +#define NEOPIXEL_MSG_UPDATE_MILLIS 30 // Update every 30ms for smooth breathing +#define NEOPIXEL_MIN_BRIGHTNESS 5 // Minimum brightness (dim) +#define NEOPIXEL_MAX_BRIGHTNESS 80 // Maximum brightness (bright blue) +#define NEOPIXEL_BRIGHTNESS_STEP 2 // Brightness change per update + +// User-definable message notification color (RGB hex format) +// Examples: 0x0000FF (blue), 0x00FF00 (green), 0xFF0000 (red), +// 0xFF00FF (magenta), 0x00FFFF (cyan), 0xFFFF00 (yellow) +#ifndef NEW_MSG_LED +#define NEW_MSG_LED 0x0000FF // Default: Blue +#endif + +// Extract RGB components from hex color +#define NEOPIXEL_MSG_RED ((NEW_MSG_LED >> 16) & 0xFF) +#define NEOPIXEL_MSG_GREEN ((NEW_MSG_LED >> 8) & 0xFF) +#define NEOPIXEL_MSG_BLUE (NEW_MSG_LED & 0xFF) +#endif + #ifndef UI_RECENT_LIST_SIZE #define UI_RECENT_LIST_SIZE 4 #endif -#if UI_HAS_JOYSTICK +#if defined(UI_HAS_JOYSTICK) || defined(PIN_USER_JOYSTICK) #define PRESS_LABEL "press Enter" #else #define PRESS_LABEL "long press" @@ -549,6 +569,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #if defined(PIN_USER_BTN_ANA) analog_btn.begin(); #endif +#if defined(PIN_USER_JOYSTICK) + analog_joystick.begin(); +#endif _node_prefs = node_prefs; @@ -666,6 +689,58 @@ void UITask::userLedHandler() { #endif } +#ifdef RADIOMASTER_900_BANDIT +void UITask::neopixelMsgHandler() { + unsigned long cur_time = millis(); + + if (_msgcount > 0) { + // We have unread messages - do breathing effect + if (cur_time >= next_neopixel_change) { + // Update brightness + if (neopixel_brightness_increasing) { + neopixel_brightness += NEOPIXEL_BRIGHTNESS_STEP; + if (neopixel_brightness >= NEOPIXEL_MAX_BRIGHTNESS) { + neopixel_brightness = NEOPIXEL_MAX_BRIGHTNESS; + neopixel_brightness_increasing = false; + } + } else { + if (neopixel_brightness <= NEOPIXEL_BRIGHTNESS_STEP) { + neopixel_brightness = NEOPIXEL_MIN_BRIGHTNESS; + neopixel_brightness_increasing = true; + } else { + neopixel_brightness -= NEOPIXEL_BRIGHTNESS_STEP; + } + } + + // Set NeoPixels 2-5 to user-defined color with current brightness + // Leave 0-1 for button backlights + // Scale each RGB component by the brightness level + uint8_t r = (NEOPIXEL_MSG_RED * neopixel_brightness) / NEOPIXEL_MAX_BRIGHTNESS; + uint8_t g = (NEOPIXEL_MSG_GREEN * neopixel_brightness) / NEOPIXEL_MAX_BRIGHTNESS; + uint8_t b = (NEOPIXEL_MSG_BLUE * neopixel_brightness) / NEOPIXEL_MAX_BRIGHTNESS; + + for (int i = 2; i <= 6; i++) { + pixels.setPixelColor(i, pixels.Color(r, g, b)); + } + pixels.show(); + + next_neopixel_change = cur_time + NEOPIXEL_MSG_UPDATE_MILLIS; + } + } else { + // No messages - turn off message notification NeoPixels (2-5) + // Only turn them off if they were previously on + if (neopixel_brightness > 0) { + neopixel_brightness = 0; + neopixel_brightness_increasing = true; + for (int i = 2; i <= 6; i++) { + pixels.setPixelColor(i, pixels.Color(0, 0, 0)); + } + pixels.show(); + } + } +} +#endif + void UITask::setCurrScreen(UIScreen* c) { curr = c; _next_refresh = 100; @@ -701,6 +776,8 @@ void UITask::shutdown(bool restart){ bool UITask::isButtonPressed() const { #ifdef PIN_USER_BTN return user_btn.isPressed(); +#elif defined(PIN_USER_JOYSTICK) + return analog_joystick.isPressed(); #else return false; #endif @@ -758,6 +835,33 @@ void UITask::loop() { _analogue_pin_read_millis = millis(); } #endif +#if defined(PIN_USER_JOYSTICK) + if ((millis() - _analogue_pin_read_millis) > 10) { + uint8_t key = analog_joystick.check(); + if (key != 0) { + // Map joystick directions to key codes and check display + switch (key) { + case KEY_UP: + c = checkDisplayOn(KEY_UP); + break; + case KEY_DOWN: + c = checkDisplayOn(KEY_DOWN); + break; + case KEY_LEFT: + c = checkDisplayOn(KEY_PREV); + break; + case KEY_RIGHT: + c = checkDisplayOn(KEY_NEXT); + break; + case KEY_SELECT: + // For center button, maybe long press support? + c = checkDisplayOn(KEY_ENTER); + break; + } + } + _analogue_pin_read_millis = millis(); + } +#endif #if defined(BACKLIGHT_BTN) if (millis() > next_backlight_btn_check) { bool touch_state = digitalRead(PIN_BUTTON2); @@ -778,6 +882,10 @@ void UITask::loop() { userLedHandler(); +#ifdef RADIOMASTER_900_BANDIT + neopixelMsgHandler(); +#endif + #ifdef PIN_BUZZER if (buzzer.isPlaying()) buzzer.loop(); #endif @@ -806,6 +914,11 @@ void UITask::loop() { #if AUTO_OFF_MILLIS > 0 if (millis() > _auto_off) { _display->turnOff(); +#ifdef RADIOMASTER_900_BANDIT + pixels.setPixelColor(0, pixels.Color(0, 0, 0)); + pixels.setPixelColor(1, pixels.Color(0, 0, 0)); + pixels.show(); +#endif } #endif } @@ -845,6 +958,12 @@ char UITask::checkDisplayOn(char c) { if (!_display->isOn()) { _display->turnOn(); // turn display on and consume event c = 0; +#ifdef RADIOMASTER_900_BANDIT +// Restore backlight for buttons here. +// pixels.setPixelColor(0, pixels.Color(255, 0, 0)); +// pixels.setPixelColor(1, pixels.Color(0, 255, 0)); +// pixels.show(); +#endif } _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _next_refresh = 0; // trigger refresh diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 02c3cafbd..09a286dfd 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -19,6 +19,12 @@ #include #endif +// Add NeoPixel support for Bandit Board +#ifdef RADIOMASTER_900_BANDIT +#include +extern Adafruit_NeoPixel pixels; +#endif + #include "../AbstractUITask.h" #include "../NodePrefs.h" @@ -44,7 +50,15 @@ class UITask : public AbstractUITask { int last_led_increment = 0; #endif -#ifdef PIN_USER_BTN_ANA +#ifdef RADIOMASTER_900_BANDIT + // NeoPixel message notification support + int neopixel_state = 0; + unsigned long next_neopixel_change = 0; + uint8_t neopixel_brightness = 0; + bool neopixel_brightness_increasing = true; +#endif + +#if defined(PIN_USER_JOYSTICK) || defined(PIN_USER_BTN_ANA) unsigned long _analogue_pin_read_millis = millis(); #endif @@ -54,6 +68,9 @@ class UITask : public AbstractUITask { UIScreen* curr; void userLedHandler(); +#ifdef RADIOMASTER_900_BANDIT + void neopixelMsgHandler(); +#endif // Button action handlers char checkDisplayOn(char c); diff --git a/src/helpers/ui/AnalogJoystick.cpp b/src/helpers/ui/AnalogJoystick.cpp new file mode 100644 index 000000000..c8a6524b8 --- /dev/null +++ b/src/helpers/ui/AnalogJoystick.cpp @@ -0,0 +1,106 @@ +#include "AnalogJoystick.h" + +AnalogJoystick::AnalogJoystick(int8_t pin, JoyADCMapping *mappings, uint8_t num_mappings, + uint8_t select_key_code, unsigned long long_press_ms, int tolerance, + unsigned long debounce_ms) { + _pin = pin; + _mappings = mappings; + _num_mappings = num_mappings; + _select_key = select_key_code; + _long_press_ms = long_press_ms; + _tolerance = tolerance; + _debounce_ms = debounce_ms; + prev = 0; + _last_change_time = 0; + _select_press_start = 0; + _long_press_triggered = false; +} + +void AnalogJoystick::begin() { + if (_pin >= 0) { + pinMode(_pin, INPUT); + } +} + +uint8_t AnalogJoystick::findClosestKey(int adc_value) const { + int closest_index = -1; + int min_diff = 32767; + + for (uint8_t i = 0; i < _num_mappings; i++) { + int diff = abs(adc_value - _mappings[i].adc_value); + if (diff < min_diff) { + min_diff = diff; + closest_index = i; + } + } + + if (closest_index >= 0 && min_diff < _tolerance) { + return _mappings[closest_index].key_code; + } + return 0; +} + +uint8_t AnalogJoystick::check() { + if (_pin < 0) return 0; + + int adc_value = analogRead(_pin); + uint8_t key = findClosestKey(adc_value); + + // Handle SELECT button with long press support + if (key == _select_key) { + if (_select_press_start == 0) { + // SELECT just pressed - start tracking + _select_press_start = millis(); + _long_press_triggered = false; + prev = key; + } else if (!_long_press_triggered && (millis() - _select_press_start) >= _long_press_ms) { + // Long press threshold reached + _long_press_triggered = true; + return 0xFF; // Special code for long press (only sent once) + } + // Still holding, waiting for either release or long press + return 0; + + } else if (prev == _select_key && _select_press_start > 0) { + // SELECT was just released + bool was_long_press = _long_press_triggered; + _select_press_start = 0; + _long_press_triggered = false; + prev = key; // Update to new state (likely 0/idle) + + if (!was_long_press) { + // Released before long press - this is a click + _last_change_time = millis(); + return _select_key; + } + // Was long press, already handled, don't send click + return 0; + + } else { + // Not SELECT button - handle other directions with debouncing + if (key != prev) { + unsigned long now = millis(); + if ((now - _last_change_time) > _debounce_ms) { + prev = key; + _last_change_time = now; + return key; + } + } + } + + return 0; +} + +bool AnalogJoystick::isLongPress() { + return _long_press_triggered; +} + +bool AnalogJoystick::isPressed() const { + if (_pin < 0) return false; + + int adc_value = analogRead(_pin); + uint8_t key = findClosestKey(adc_value); + + // Return true if any key is pressed (not idle/released) + return key != 0; +} \ No newline at end of file diff --git a/src/helpers/ui/AnalogJoystick.h b/src/helpers/ui/AnalogJoystick.h new file mode 100644 index 000000000..cce855142 --- /dev/null +++ b/src/helpers/ui/AnalogJoystick.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +class AnalogJoystick { +public: + struct JoyADCMapping { + int adc_value; + uint8_t key_code; + }; + +private: + int8_t _pin; + uint8_t prev; + int _tolerance; + unsigned long _debounce_ms; + unsigned long _last_change_time; // Long press tracking + uint8_t _select_key; + unsigned long _select_press_start; + bool _long_press_triggered; + unsigned long _long_press_ms; + + JoyADCMapping *_mappings; + uint8_t _num_mappings; + + uint8_t findClosestKey(int adc_value) const; + +public: + AnalogJoystick(int8_t pin, JoyADCMapping *mappings, uint8_t num_mappings, uint8_t select_key_code, + unsigned long long_press_ms = 1000, int tolerance = 300, unsigned long debounce_ms = 50); + void begin(); + uint8_t check(); + bool isLongPress(); + bool isPressed() const; + uint8_t getPin() const { return _pin; } + uint8_t getCurrentKey() const { return prev; } +}; \ No newline at end of file diff --git a/src/helpers/ui/SH1115Display.cpp b/src/helpers/ui/SH1115Display.cpp new file mode 100644 index 000000000..328d5e5f5 --- /dev/null +++ b/src/helpers/ui/SH1115Display.cpp @@ -0,0 +1,92 @@ +#include "SH1115Display.h" +#include +//#include "Adafruit_SH110X.h" +#include + +bool SH1115Display::i2c_probe(TwoWire &wire, uint8_t addr) +{ + wire.beginTransmission(addr); + uint8_t error = wire.endTransmission(); + return (error == 0); +} + +bool SH1115Display::begin() +{ + return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS); +} + +void SH1115Display::turnOn() +{ + display.oled_command(SH110X_DISPLAYON); + _isOn = true; +} + +void SH1115Display::turnOff() +{ + display.oled_command(SH110X_DISPLAYOFF); + _isOn = false; +} + +void SH1115Display::clear() +{ + display.clearDisplay(); + display.display(); +} + +void SH1115Display::startFrame(Color bkg) +{ + display.clearDisplay(); // TODO: apply 'bkg' + _color = SH110X_WHITE; + display.setTextColor(_color); + display.setTextSize(1); + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void SH1115Display::setTextSize(int sz) +{ + display.setTextSize(sz); +} + +void SH1115Display::setColor(Color c) +{ + _color = (c != 0) ? SH110X_WHITE : SH110X_BLACK; + display.setTextColor(_color); +} + +void SH1115Display::setCursor(int x, int y) +{ + display.setCursor(x, y); +} + +void SH1115Display::print(const char *str) +{ + display.print(str); +} + +void SH1115Display::fillRect(int x, int y, int w, int h) +{ + display.fillRect(x, y, w, h, _color); +} + +void SH1115Display::drawRect(int x, int y, int w, int h) +{ + display.drawRect(x, y, w, h, _color); +} + +void SH1115Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) +{ + display.drawBitmap(x, y, bits, w, h, SH110X_WHITE); +} + +uint16_t SH1115Display::getTextWidth(const char *str) +{ + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void SH1115Display::endFrame() +{ + display.display(); +} diff --git a/src/helpers/ui/SH1115Display.h b/src/helpers/ui/SH1115Display.h new file mode 100644 index 000000000..fb39d90e7 --- /dev/null +++ b/src/helpers/ui/SH1115Display.h @@ -0,0 +1,44 @@ +#pragma once + +#include "DisplayDriver.h" + +#include +#include +#define SH110X_NO_SPLASH +// #include +#include + +#ifndef PIN_OLED_RESET +#define PIN_OLED_RESET -1 +#endif + +#ifndef DISPLAY_ADDRESS +#define DISPLAY_ADDRESS 0x3C +#endif + +class SH1115Display : public DisplayDriver { + Adafruit_SH1115 display; + bool _isOn; + uint8_t _color; + + bool i2c_probe(TwoWire &wire, uint8_t addr); + +public: + SH1115Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char *str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override; + uint16_t getTextWidth(const char *str) override; + void endFrame() override; +}; diff --git a/variants/radiomaster_900_bandit/BanditBoard.h b/variants/radiomaster_900_bandit/BanditBoard.h new file mode 100644 index 000000000..ada41d2fc --- /dev/null +++ b/variants/radiomaster_900_bandit/BanditBoard.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include + +#define RADIOMASTER_900_BANDIT +#define DISPLAY_CLASS SH1115Display + +extern Adafruit_NeoPixel pixels; + +// User-definable TX LED color (RGB hex format) +// Examples: 0x00FF00 (green), 0xFF0000 (red), 0x0000FF (blue) +#ifndef TX_LED_COLOR +#define TX_LED_COLOR 0x009600 // Default: Green (0, 150, 0) +#endif + +// Extract RGB components from hex color for TX LED +#define TX_LED_RED ((TX_LED_COLOR >> 16) & 0xFF) +#define TX_LED_GREEN ((TX_LED_COLOR >> 8) & 0xFF) +#define TX_LED_BLUE (TX_LED_COLOR & 0xFF) + +/* + 6 x Neopixels, GRB + GPIO 15 + Backgroundlight button 1 at index 0 + Backgroundlight button 2 at index 1 + + Button 1 at GPIO 34 - UNUSED + Button 2 at GPIO 35 - UNUSED + + STK8XXX Accelerometer I2C address 0x18 and Interrupt at GPIO 37 +*/ + +/* + Pin connections from ESP32-D0WDQ6 to SX1276. +*/ +#define P_LORA_DIO_0 22 +#define P_LORA_DIO_1 21 +#define P_LORA_NSS 4 +#define P_LORA_RESET 5 +#define P_LORA_SCLK 18 +#define P_LORA_MISO 19 +#define P_LORA_MOSI 23 +#define SX176X_TXEN 33 + +/* + I2C SDA and SCL. +*/ +#define PIN_BOARD_SDA 14 +#define PIN_BOARD_SCL 12 + +/* + This unit has a FAN built-in. + FAN is active at 250mW on it's ExpressLRS Firmware. + Always ON +*/ +#define PA_FAN_EN 2 // FAN on GPIO 2 + +/* + This module has Skyworks SKY66122 controlled by dacWrite + power rangeing from 100mW to 1000mW. + + Mapping of PA_LEVEL to Power output: GPIO26/dacWrite + 168 -> 100mW -> 2.11v + 148 -> 250mW -> 1.87v + 128 -> 500mW -> 1.63v + 90 -> 1000mW -> 1.16v +*/ +#define DAC_PA_PIN 26 // GPIO 26 enables the PA + +// Configuration - adjust these for your hardware +#define PA_CONSTANT_GAIN 18 // SKY66122 operates at constant 18dB gain +#define MIN_OUTPUT_DBM 20 // 100mW minimum +#define MAX_OUTPUT_DBM 30 // 1000mW maximum + +// Calibration points from manufacturer +struct PowerCalibration { + uint8_t output_dbm; + int8_t sx1278_dbm; + uint8_t dac_value; +}; + +// Values are from Radiomaster. +const PowerCalibration calibration[] = { + { 20, 2, 165 }, // 100mW + { 24, 6, 155 }, // 250mW + { 27, 9, 142 }, // 500mW + { 30, 10, 110 } // 1000mW +}; + +inline Adafruit_NeoPixel pixels(NEOPIXEL_NUM, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); + +const int NUM_CAL_POINTS = sizeof(calibration) / sizeof(calibration[0]); + +class BanditBoard : public ESP32Board { +private: +public: + void begin() { + ESP32Board::begin(); + pixels.begin(); + pixels.clear(); + // pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + // pixels.setPixelColor(1, pixels.Color(0, 255, 0)); + pixels.show(); + } + // Return fake battery status, battery/fixed power is not monitored. + uint16_t getBattMilliVolts() override { return 5000; } + + const char *getManufacturerName() const override { return "RadioMaster Bandit"; } + + void onBeforeTransmit() override { + // Use user-defined TX LED color + for (byte i = 2; i <= 6; i++) { + pixels.setPixelColor(i, pixels.Color(TX_LED_RED, TX_LED_GREEN, TX_LED_BLUE)); + } + pixels.show(); + } + + void onAfterTransmit() override { + for (byte i = 2; i <= 6; i++) { + pixels.setPixelColor(i, pixels.Color(0, 0, 0)); + } + pixels.show(); + } +}; \ No newline at end of file diff --git a/variants/radiomaster_900_bandit/COLOR_EXAMPLES.md b/variants/radiomaster_900_bandit/COLOR_EXAMPLES.md new file mode 100644 index 000000000..379e4e8a9 --- /dev/null +++ b/variants/radiomaster_900_bandit/COLOR_EXAMPLES.md @@ -0,0 +1,276 @@ +# NeoPixel Color Examples & Recommendations + +## ๐ŸŽจ Complete Color Palette + +### Primary Colors +``` +Red: 0xFF0000 ๐Ÿ”ด High urgency, attention-grabbing +Green: 0x00FF00 ๐ŸŸข Success, go, nature +Blue: 0x0000FF ๐Ÿ”ต Calm, default, trust (DEFAULT) +``` + +### Secondary Colors +``` +Yellow: 0xFFFF00 ๐ŸŸก Warning, caution, bright +Cyan: 0x00FFFF โšก Cool, techy, calming +Magenta: 0xFF00FF ๐Ÿ’œ Unique, stands out +``` + +### Extended Palette +``` +Orange: 0xFF8000 ๐ŸŸ  Warm, friendly +Purple: 0x8000FF ๐ŸŸฃ Creative, gentle +Pink: 0xFF1493 ๐Ÿ’— Soft, playful +Hot Pink: 0xFF69B4 ๐ŸŽ€ Very noticeable +Lime: 0xBFFF00 ๐ŸŸข Fresh, energetic +Teal: 0x008080 ๐ŸŒŠ Sophisticated +Navy: 0x000080 ๐ŸŒ‘ Subtle, professional +Maroon: 0x800000 ๐Ÿท Rich, subdued +Olive: 0x808000 ๐Ÿซ’ Natural, earthy +Aqua: 0x00FF88 ๐Ÿ’Ž Tropical, bright +Violet: 0x8800FF ๐Ÿ”ฎ Mystical +Coral: 0xFF7F50 ๐Ÿ  Warm, inviting +Turquoise: 0x40E0D0 ๐Ÿ–๏ธ Refreshing +Gold: 0xFFD700 โญ Valuable, special +Silver: 0xC0C0C0 ๐Ÿค– Modern, sleek +``` + +### White Variants +``` +Pure White: 0xFFFFFF โšช Maximum brightness +Warm White: 0xFFCC88 ๐Ÿ•ฏ๏ธ Cozy, comfortable +Cool White: 0xCCDDFF โ„๏ธ Clinical, clear +Soft White: 0xFFEEDD ๐ŸŒŸ Gentle, easy on eyes +``` + +## ๐Ÿ“‹ Use Case Recommendations + +### High Priority / Urgent Messages +**Best choices:** +- `0xFF0000` (Red) - Classic alert color +- `0xFF00FF` (Magenta) - Unusual, catches attention +- `0xFFFF00` (Yellow) - Warning/caution +- `0xFF1493` (Hot Pink) - Very noticeable + +**Settings for urgent:** +```ini +build_flags = + -DNEW_MSG_LED=0xFF0000 ; Red + -DNEOPIXEL_MAX_BRIGHTNESS=120 ; Brighter + -DNEOPIXEL_MSG_UPDATE_MILLIS=20 ; Faster breathing +``` + +### Normal Priority Messages +**Best choices:** +- `0x0000FF` (Blue) - Default, professional +- `0x00FFFF` (Cyan) - Visible but calm +- `0x8000FF` (Purple) - Distinctive yet gentle +- `0x00FF00` (Green) - Positive feeling + +**Settings for normal:** +```ini +build_flags = + -DNEW_MSG_LED=0x0000FF ; Blue + -DNEOPIXEL_MAX_BRIGHTNESS=80 ; Standard + -DNEOPIXEL_MSG_UPDATE_MILLIS=30 ; Normal speed +``` + +### Low Distraction / Ambient +**Best choices:** +- `0x8000FF` (Purple) - Subtle +- `0x008080` (Teal) - Sophisticated +- `0x40E0D0` (Turquoise) - Calming +- `0xFF7F50` (Coral) - Warm but not harsh + +**Settings for ambient:** +```ini +build_flags = + -DNEW_MSG_LED=0x8000FF ; Purple + -DNEOPIXEL_MAX_BRIGHTNESS=40 ; Dim + -DNEOPIXEL_MSG_UPDATE_MILLIS=50 ; Slow breathing +``` + +### Battery Saving +**Best choices (single LED color):** +- `0xFF0000` (Red) - Only red LED +- `0x00FF00` (Green) - Only green LED +- `0x0000FF` (Blue) - Only blue LED + +**Avoid for battery:** +- โŒ `0xFFFFFF` (White) - All 3 LEDs at full +- โŒ `0xFFFF00` (Yellow) - 2 LEDs at full +- โŒ `0x00FFFF` (Cyan) - 2 LEDs at full +- โŒ `0xFF00FF` (Magenta) - 2 LEDs at full + +**Battery-conscious settings:** +```ini +build_flags = + -DNEW_MSG_LED=0x00FF00 ; Green (single LED) + -DNEOPIXEL_MAX_BRIGHTNESS=60 ; Moderate brightness + -DNEOPIXEL_MIN_BRIGHTNESS=3 ; Very dim minimum +``` + +## ๐ŸŽญ Themed Configurations + +### "Stealth Mode" - Minimal Visibility +```ini +build_flags = + -DNEW_MSG_LED=0x000080 ; Navy blue + -DNEOPIXEL_MAX_BRIGHTNESS=30 + -DNEOPIXEL_MIN_BRIGHTNESS=2 + -DNEOPIXEL_MSG_UPDATE_MILLIS=60 +``` + +### "Party Mode" - Maximum Attention +```ini +build_flags = + -DNEW_MSG_LED=0xFF00FF ; Magenta + -DNEOPIXEL_MAX_BRIGHTNESS=150 + -DNEOPIXEL_MIN_BRIGHTNESS=10 + -DNEOPIXEL_MSG_UPDATE_MILLIS=15 +``` + +### "Professional" - Subtle Business Setting +```ini +build_flags = + -DNEW_MSG_LED=0x0000FF ; Blue + -DNEOPIXEL_MAX_BRIGHTNESS=60 + -DNEOPIXEL_MSG_UPDATE_MILLIS=40 +``` + +### "Emergency Services" - High Visibility Red +```ini +build_flags = + -DNEW_MSG_LED=0xFF0000 ; Red + -DNEOPIXEL_MAX_BRIGHTNESS=120 + -DNEOPIXEL_MSG_UPDATE_MILLIS=15 +``` + +### "Nature Lover" - Earth Tones +```ini +build_flags = + -DNEW_MSG_LED=0x00FF00 ; Green + -DNEOPIXEL_MAX_BRIGHTNESS=70 + -DNEOPIXEL_MSG_UPDATE_MILLIS=35 +``` + +### "Gamer RGB" - Vibrant Cyan +```ini +build_flags = + -DNEW_MSG_LED=0x00FFFF ; Cyan + -DNEOPIXEL_MAX_BRIGHTNESS=100 + -DNEOPIXEL_MSG_UPDATE_MILLIS=25 +``` + +## ๐Ÿ”ฌ Color Mixing Guide + +Want a custom color? Mix Red, Green, and Blue: + +``` +Formula: 0xRRGGBB + +RR = Red intensity (00 to FF in hex, 0 to 255 decimal) +GG = Green intensity (00 to FF in hex, 0 to 255 decimal) +BB = Blue intensity (00 to FF in hex, 0 to 255 decimal) +``` + +### Quick Mix Examples: + +**Orange** (Red + half Green): +``` +0xFF8000 = 0xFF (255 red) + 0x80 (128 green) + 0x00 (0 blue) +``` + +**Purple** (Red + Blue): +``` +0x8000FF = 0x80 (128 red) + 0x00 (0 green) + 0xFF (255 blue) +``` + +**Pink** (Red + some Green + Blue): +``` +0xFF69B4 = 0xFF (255 red) + 0x69 (105 green) + 0xB4 (180 blue) +``` + +### Hex to Decimal Quick Reference: +``` +00 = 0 40 = 64 80 = 128 C0 = 192 +20 = 32 60 = 96 A0 = 160 E0 = 224 +30 = 48 70 = 112 B0 = 176 FF = 255 +``` + +## ๐Ÿ’ก Pro Tips + +### Visibility in Different Lighting +- **Daylight/Bright**: Use high brightness (100-150) and saturated colors +- **Indoor/Office**: Medium brightness (60-80) with any color +- **Dark/Night**: Low brightness (30-50) to avoid being blinding +- **Outdoors**: Blue/Cyan show well in daylight, red at night + +### Color Psychology +- **Red**: Urgency, danger, stop, important +- **Green**: Go, success, safe, nature +- **Blue**: Trust, calm, professional, default +- **Yellow**: Warning, caution, attention +- **Purple**: Creative, unique, mystical +- **Cyan**: Tech, modern, cool +- **Orange**: Friendly, energetic, warm +- **White**: Clean, pure, maximum visibility + +### Multi-User Scenarios +If multiple people use devices, assign colors: +- User 1: Blue `0x0000FF` +- User 2: Green `0x00FF00` +- User 3: Red `0xFF0000` +- User 4: Magenta `0xFF00FF` +- User 5: Cyan `0x00FFFF` + +## ๐Ÿงช Testing Your Color + +1. Choose your hex color +2. Add to platformio.ini: `-DNEW_MSG_LED=0xYOURCOLOR` +3. Build and flash +4. Send yourself a test message +5. Observe the breathing effect +6. Adjust brightness/speed if needed + +## ๐Ÿ“ฑ Web Color Picker + +Use any online color picker (search "RGB color picker") to find your perfect color: +1. Pick your color visually +2. Copy the RGB hex value (usually shown as #RRGGBB) +3. Change # to 0x (e.g., #00FF00 becomes 0x00FF00) +4. Use in your build configuration + +## ๐ŸŽจ Seasonal Colors + +### Spring +``` +Pastel Pink: 0xFFB6C1 +Mint Green: 0x98FF98 +Lavender: 0xE6E6FA +``` + +### Summer +``` +Tropical Blue: 0x00CED1 +Bright Yellow: 0xFFFF00 +Coral: 0xFF7F50 +``` + +### Autumn +``` +Burnt Orange: 0xFF8C00 +Rust: 0xB7410E +Gold: 0xFFD700 +``` + +### Winter +``` +Ice Blue: 0xAFEEEE +Snow White: 0xFFFAFA +Silver: 0xC0C0C0 +``` + +--- + +Remember: The color appears as a breathing/pulsing animation, so it will cycle between dim and bright, making it more dynamic than a static color! diff --git a/variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md b/variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md new file mode 100644 index 000000000..00dcbc63b --- /dev/null +++ b/variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md @@ -0,0 +1,286 @@ +# LED Color Configuration Guide + +## Overview + +Your RadioMaster Bandit now has **two user-configurable LED colors**: + +1. **TX_LED_COLOR** - Color during radio transmission (flash) +2. **NEW_MSG_LED** - Color for new message notifications (breathing effect) + +## Quick Setup + +### platformio.ini Configuration + +```ini +[env:bandit] +build_flags = + -DTX_LED_COLOR=0x00FF00 ; Green transmission flash (default) + -DNEW_MSG_LED=0x0000FF ; Blue message breathing (default) +``` + +### Example Configurations + +**Default (Green TX, Blue Messages):** +```ini +build_flags = + -DTX_LED_COLOR=0x009600 ; Green (0, 150, 0) - slightly dimmed + -DNEW_MSG_LED=0x0000FF ; Blue +``` + +**All Red:** +```ini +build_flags = + -DTX_LED_COLOR=0xFF0000 ; Red transmission + -DNEW_MSG_LED=0xFF0000 ; Red messages +``` + +**High Contrast (Red TX, Blue Messages):** +```ini +build_flags = + -DTX_LED_COLOR=0xFF0000 ; Red transmission + -DNEW_MSG_LED=0x0000FF ; Blue messages +``` + +**Professional (Blue TX, Cyan Messages):** +```ini +build_flags = + -DTX_LED_COLOR=0x0000FF ; Blue transmission + -DNEW_MSG_LED=0x00FFFF ; Cyan messages +``` + +**Stealth (Dim Purple for both):** +```ini +build_flags = + -DTX_LED_COLOR=0x400040 ; Dim purple transmission + -DNEW_MSG_LED=0x8000FF ; Purple messages +``` + +**High Visibility (Yellow TX, Magenta Messages):** +```ini +build_flags = + -DTX_LED_COLOR=0xFFFF00 ; Yellow transmission + -DNEW_MSG_LED=0xFF00FF ; Magenta messages +``` + +## Color Behavior + +### TX_LED_COLOR (Transmission Flash) +- **When**: During radio transmission +- **NeoPixels**: 2-5 (indices 2, 3, 4, 5) +- **Duration**: Brief flash while transmitting +- **Effect**: Solid color, no animation +- **Default**: Green (0x009600 = RGB 0, 150, 0) + +### NEW_MSG_LED (Message Notification) +- **When**: Unread messages exist +- **NeoPixels**: 2-5 (indices 2, 3, 4, 5) +- **Duration**: Continuous until messages read +- **Effect**: Smooth breathing/pulsing +- **Default**: Blue (0x0000FF) + +### NeoPixel Layout +``` +Index 0: Button 1 backlight (not controlled by these settings) +Index 1: Button 2 backlight (not controlled by these settings) +Index 2: TX flash / Message notification +Index 3: TX flash / Message notification +Index 4: TX flash / Message notification +Index 5: TX flash / Message notification +``` + +## Color Format + +Both use RGB hex format: `0xRRGGBB` +- RR = Red (00-FF hex / 0-255 decimal) +- GG = Green (00-FF hex / 0-255 decimal) +- BB = Blue (00-FF hex / 0-255 decimal) + +### Common Colors + +| Color | Hex | Decimal RGB | +|-------|-----|-------------| +| Red | `0xFF0000` | 255, 0, 0 | +| Green | `0x00FF00` | 0, 255, 0 | +| Blue | `0x0000FF` | 0, 0, 255 | +| Yellow | `0xFFFF00` | 255, 255, 0 | +| Cyan | `0x00FFFF` | 0, 255, 255 | +| Magenta | `0xFF00FF` | 255, 0, 255 | +| White | `0xFFFFFF` | 255, 255, 255 | +| Orange | `0xFF8000` | 255, 128, 0 | +| Purple | `0x8000FF` | 128, 0, 255 | + +### Brightness Control + +You can dim colors by reducing the values: + +**Full Brightness Green:** `0x00FF00` (0, 255, 0) +**Medium Green:** `0x008000` (0, 128, 0) +**Dim Green:** `0x004000` (0, 64, 0) +**Very Dim Green:** `0x002000` (0, 32, 0) + +**Current default TX color:** `0x009600` (0, 150, 0) - medium-bright green + +## Use Case Examples + +### Emergency Services / High Visibility +```ini +build_flags = + -DTX_LED_COLOR=0xFF0000 ; Red transmission (urgent) + -DNEW_MSG_LED=0xFF0000 ; Red messages (urgent) +``` + +### Professional / Office +```ini +build_flags = + -DTX_LED_COLOR=0x004080 ; Dim blue transmission + -DNEW_MSG_LED=0x0000FF ; Blue messages +``` + +### Battery Saver (Single color LEDs) +```ini +build_flags = + -DTX_LED_COLOR=0x00FF00 ; Green only (1 LED per pixel) + -DNEW_MSG_LED=0x00FF00 ; Green only (1 LED per pixel) +``` + +### Nighttime / Low Light +```ini +build_flags = + -DTX_LED_COLOR=0x400000 ; Very dim red + -DNEW_MSG_LED=0x400000 ; Very dim red +``` + +### Color-Coded Operations +```ini +build_flags = + -DTX_LED_COLOR=0xFFFF00 ; Yellow = transmitting + -DNEW_MSG_LED=0x00FF00 ; Green = new message received +``` + +### Rainbow Theme +```ini +build_flags = + -DTX_LED_COLOR=0xFF00FF ; Magenta TX + -DNEW_MSG_LED=0x00FFFF ; Cyan messages +``` + +## Recommended Color Combinations + +### Same Color (Unified Look) +- Both Blue: `TX=0x0000FF, MSG=0x0000FF` +- Both Green: `TX=0x00FF00, MSG=0x00FF00` +- Both Red: `TX=0xFF0000, MSG=0xFF0000` + +### Contrasting (Easy to Distinguish) +- Red TX, Blue MSG: `TX=0xFF0000, MSG=0x0000FF` +- Green TX, Magenta MSG: `TX=0x00FF00, MSG=0xFF00FF` +- Yellow TX, Cyan MSG: `TX=0xFFFF00, MSG=0x00FFFF` + +### Subtle (Professional) +- Dim Blue TX, Blue MSG: `TX=0x000080, MSG=0x0000FF` +- Dim Green TX, Cyan MSG: `TX=0x004000, MSG=0x00FFFF` +- Dim Purple TX, Purple MSG: `TX=0x400040, MSG=0x8000FF` + +## Power Consumption Notes + +**Most Efficient (Single LED color):** +- Red: `0xFF0000` +- Green: `0x00FF00` +- Blue: `0x0000FF` + +**Medium Efficiency (Two LED colors):** +- Yellow: `0xFFFF00` (Red + Green) +- Cyan: `0x00FFFF` (Green + Blue) +- Magenta: `0xFF00FF` (Red + Blue) + +**Least Efficient (All LEDs):** +- White: `0xFFFFFF` (Red + Green + Blue) + +Lower brightness values also save power: +- `0x00FF00` = 255 brightness +- `0x008000` = 128 brightness (half power) +- `0x004000` = 64 brightness (quarter power) + +## Testing Your Configuration + +1. Set your colors in `platformio.ini` +2. Build and flash: `pio run -t upload` +3. **Test TX LED**: Send a message - should see brief flash in TX_LED_COLOR +4. **Test MSG LED**: Receive a message - should see breathing effect in NEW_MSG_LED +5. Adjust brightness/colors as needed + +## Troubleshooting + +**Colors look wrong:** +- Verify hex format: `0xRRGGBB` with `0x` prefix +- Check build flags are being applied (view compiler output) +- NeoPixels are GRB format, but code handles conversion + +**TX flash too bright:** +- Reduce TX_LED_COLOR values (e.g., `0x00FF00` โ†’ `0x008000`) + +**Message breathing too dim:** +- Adjust `NEOPIXEL_MAX_BRIGHTNESS` in UITask.cpp (default: 80) + +**Want different brightness for TX vs MSG:** +- TX: Adjust the color value directly (e.g., `0x009600` is dimmer than `0x00FF00`) +- MSG: Adjust `NEOPIXEL_MAX_BRIGHTNESS` in code + +## Advanced: Hex to RGB Conversion + +If you want a specific RGB value: + +**Formula:** `0xRRGGBB` + +**Example - RGB(255, 128, 64) = Orange:** +1. Red = 255 = 0xFF +2. Green = 128 = 0x80 +3. Blue = 64 = 0x40 +4. Result = `0xFF8040` + +**Hex conversion chart:** +``` +0 = 0x00 64 = 0x40 128 = 0x80 192 = 0xC0 +32 = 0x20 96 = 0x60 160 = 0xA0 224 = 0xE0 +48 = 0x30 112 = 0x70 176 = 0xB0 255 = 0xFF +``` + +## Files Modified + +1. **BanditBoard.h** - Added TX_LED_COLOR support +2. **UITask.h** - NeoPixel message notification support +3. **UITask.cpp** - NEW_MSG_LED color system + +--- + +## Quick Copy-Paste Examples + +### Default Configuration +```ini +-DTX_LED_COLOR=0x009600 -DNEW_MSG_LED=0x0000FF +``` + +### All Green +```ini +-DTX_LED_COLOR=0x00FF00 -DNEW_MSG_LED=0x00FF00 +``` + +### All Red +```ini +-DTX_LED_COLOR=0xFF0000 -DNEW_MSG_LED=0xFF0000 +``` + +### Red TX, Blue Messages +```ini +-DTX_LED_COLOR=0xFF0000 -DNEW_MSG_LED=0x0000FF +``` + +### Yellow TX, Magenta Messages +```ini +-DTX_LED_COLOR=0xFFFF00 -DNEW_MSG_LED=0xFF00FF +``` + +### Dim Everything (Stealth) +```ini +-DTX_LED_COLOR=0x202020 -DNEW_MSG_LED=0x404040 +``` diff --git a/variants/radiomaster_900_bandit/QUICK_REFERENCE.md b/variants/radiomaster_900_bandit/QUICK_REFERENCE.md new file mode 100644 index 000000000..7bf9b4263 --- /dev/null +++ b/variants/radiomaster_900_bandit/QUICK_REFERENCE.md @@ -0,0 +1,190 @@ +# Quick Reference: NeoPixel LED Configuration + +## ๐ŸŽจ Two Configurable LED Colors + +### TX_LED_COLOR - Radio Transmission Flash +Color shown on NeoPixels 2-5 during transmission + +### NEW_MSG_LED - Message Notification Breathing +Color shown on NeoPixels 2-5 when you have unread messages + +## โšก Quick Setup + +### platformio.ini +```ini +build_flags = + -DTX_LED_COLOR=0x00FF00 ; Green transmission flash + -DNEW_MSG_LED=0x0000FF ; Blue message breathing +``` + +### In Code +```cpp +#define TX_LED_COLOR 0x00FF00 // Green transmission +#define NEW_MSG_LED 0x0000FF // Blue messages +``` + +## ๐ŸŒˆ Popular Colors + +| Color | Hex | Use For | +|-------|-----|---------| +| Blue | `0x0000FF` | Professional, calm | +| Green | `0x00FF00` | Success, go, battery-friendly | +| Red | `0xFF0000` | Urgent, stop, attention | +| Yellow | `0xFFFF00` | Warning, caution | +| Cyan | `0x00FFFF` | Modern, cool | +| Magenta | `0xFF00FF` | Unique, stands out | +| Orange | `0xFF8000` | Friendly, warm | +| Purple | `0x8000FF` | Subtle, creative | +| White | `0xFFFFFF` | Maximum visibility | + +## ๐Ÿ“‹ Recommended Combinations + +### Defaults +```ini +-DTX_LED_COLOR=0x009600 -DNEW_MSG_LED=0x0000FF +``` +Green TX flash (dimmed), Blue message breathing + +### All Green (Battery Saver) +```ini +-DTX_LED_COLOR=0x00FF00 -DNEW_MSG_LED=0x00FF00 +``` + +### All Red (High Urgency) +```ini +-DTX_LED_COLOR=0xFF0000 -DNEW_MSG_LED=0xFF0000 +``` + +### High Contrast +```ini +-DTX_LED_COLOR=0xFF0000 -DNEW_MSG_LED=0x0000FF +``` +Red TX, Blue messages - easy to distinguish + +### Professional +```ini +-DTX_LED_COLOR=0x0000FF -DNEW_MSG_LED=0x00FFFF +``` +Blue TX, Cyan messages - business-friendly + +### Stealth Mode +```ini +-DTX_LED_COLOR=0x202020 -DNEW_MSG_LED=0x404040 +``` +Very dim for low visibility + +## ๐Ÿ”ง Files Modified + +1. **BanditBoard.h** - TX_LED_COLOR support +2. **UITask.h** - NEW_MSG_LED variables +3. **UITask.cpp** - NEW_MSG_LED color system + +## ๐Ÿ“Š LED Behavior + +| Event | NeoPixels 0-1 | NeoPixels 2-5 | +|-------|---------------|---------------| +| Radio TX | Unchanged | TX_LED_COLOR (flash) | +| New messages | Unchanged | NEW_MSG_LED (breathing) | +| No messages | Unchanged | OFF | + +## ๐ŸŽ›๏ธ Advanced Tweaks + +### Dim TX Flash +```cpp +#define TX_LED_COLOR 0x004000 // Dim green (was 0x00FF00) +``` + +### Brighter Messages +```cpp +#define NEOPIXEL_MAX_BRIGHTNESS 120 // In UITask.cpp (was 80) +``` + +### Faster Breathing +```cpp +#define NEOPIXEL_MSG_UPDATE_MILLIS 20 // In UITask.cpp (was 30) +``` + +### Smoother Breathing +```cpp +#define NEOPIXEL_BRIGHTNESS_STEP 1 // In UITask.cpp (was 2) +``` + +## ๐Ÿ”‹ Battery Considerations + +**Most Efficient (single LED):** +- `0xFF0000` (Red only) +- `0x00FF00` (Green only) +- `0x0000FF` (Blue only) + +**Less Efficient (multiple LEDs):** +- `0xFFFF00` (Yellow = Red + Green) +- `0x00FFFF` (Cyan = Green + Blue) +- `0xFF00FF` (Magenta = Red + Blue) +- `0xFFFFFF` (White = all three) + +**Brightness matters:** +- `0x00FF00` = full brightness +- `0x008000` = half brightness (50% power) +- `0x004000` = quarter brightness (25% power) + +## ๐Ÿ’ก Color Format + +`0xRRGGBB` where: +- RR = Red (00-FF hex) +- GG = Green (00-FF hex) +- BB = Blue (00-FF hex) + +Examples: +- Orange: `0xFF8000` = RGB(255, 128, 0) +- Pink: `0xFF69B4` = RGB(255, 105, 180) +- Teal: `0x008080` = RGB(0, 128, 128) + +## ๐Ÿงช Testing + +1. Set colors in platformio.ini +2. Build: `pio run` +3. Flash: `pio run -t upload` +4. Test TX: Send a message โ†’ see TX_LED_COLOR flash +5. Test MSG: Have unread messages โ†’ see NEW_MSG_LED breathing +6. Adjust as needed + +## โš ๏ธ Troubleshooting + +**Wrong color?** +โ†’ Check hex format: `0xRRGGBB` with `0x` prefix + +**TX too bright?** +โ†’ Lower TX_LED_COLOR values: `0x00FF00` โ†’ `0x008000` + +**Messages too dim?** +โ†’ Increase NEOPIXEL_MAX_BRIGHTNESS in UITask.cpp + +**Colors not applying?** +โ†’ Check compiler output for build flags + +## ๐Ÿ“ Quick Copy Examples + +### Green TX, Blue MSG (default) +``` +-DTX_LED_COLOR=0x009600 -DNEW_MSG_LED=0x0000FF +``` + +### Red TX, Red MSG +``` +-DTX_LED_COLOR=0xFF0000 -DNEW_MSG_LED=0xFF0000 +``` + +### Yellow TX, Magenta MSG +``` +-DTX_LED_COLOR=0xFFFF00 -DNEW_MSG_LED=0xFF00FF +``` + +### Cyan TX, Purple MSG +``` +-DTX_LED_COLOR=0x00FFFF -DNEW_MSG_LED=0x8000FF +``` + +### Dim White TX, Bright White MSG +``` +-DTX_LED_COLOR=0x404040 -DNEW_MSG_LED=0xFFFFFF +``` diff --git a/variants/radiomaster_900_bandit/platformio.ini b/variants/radiomaster_900_bandit/platformio.ini new file mode 100644 index 000000000..08321bbfa --- /dev/null +++ b/variants/radiomaster_900_bandit/platformio.ini @@ -0,0 +1,94 @@ +[radiomaster_900_bandit] +extends = esp32_base +board = esp32dev +#upload_flags = --before default_reset +build_flags = + ${esp32_base.build_flags} + -I variants/radiomaster_900_bandit + -D RADIOMASTER_900_BANDIT + -D SX127X_CURRENT_LIMIT=120 + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -D DISPLAY_CLASS=SH1115Display ; I've added the SH1115 driver to Adafruit_SH110x that is avaible on ny github @ https://github.com/gjelsoe/Adafruit_SH110x + ; The SH1106 will work, but there are some small pixels misplacement. Haven't testet if SH1107 will work better. + -D NEW_MSG_LED=0x0000FF ; Blue New message notification LED color, to diable enter 0x000000. + -D TX_LED_COLOR=0x009600 ; Green TX LED color, to diable enter 0x000000. + -D PIN_NEOPIXEL=15 ; Data GPIO of the NEOPIXELS. + -D NEOPIXEL_NUM=6 ; Number of NEOPIXELS. + -D SX176X_TXEN=33 + -D MAX_LORA_TX_POWER=30 + -D LORA_TX_POWER=20 + -D PIN_BOARD_SDA=14 + -D PIN_BOARD_SCL=12 + -D PIN_USER_JOYSTICK=39 + -D ENV_INCLUDE_GPS=0 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/radiomaster_900_bandit> + + + + + + + +board_upload.flash_size = 8MB +board_build.partitions = default_8MB.csv + +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit NeoPixel@^1.10.0 + https://github.com/gjelsoe/Adafruit_SH110x + +[env:radiomaster_900_bandit_companion_radio_ble] +extends = radiomaster_900_bandit +board_build.upload.maximum_ram_size=2000000 +build_flags = + ${radiomaster_900_bandit.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=160 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D RADIOLIB_DEBUG_BASIC=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${radiomaster_900_bandit.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:radiomaster_900_bandit_repeater] +extends = radiomaster_900_bandit +build_flags = + ${radiomaster_900_bandit.build_flags} + -D ADVERT_NAME='"Radiomaster Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${radiomaster_900_bandit.lib_deps} + ${esp32_ota.lib_deps} + +[env:radiomaster_900_bandit_room_server] +extends = radiomaster_900_bandit +build_flags = + ${radiomaster_900_bandit.build_flags} + -D ADVERT_NAME='"Radiomaster Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${radiomaster_900_bandit.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/radiomaster_900_bandit/target.cpp b/variants/radiomaster_900_bandit/target.cpp new file mode 100644 index 000000000..b9981d378 --- /dev/null +++ b/variants/radiomaster_900_bandit/target.cpp @@ -0,0 +1,160 @@ +#include "target.h" + +#include +#include + +BanditBoard board; + +#if defined(P_LORA_SCLK) +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); +#else +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifndef PIN_USER_BTN +#define PIN_USER_BTN (-1) +#endif + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +// MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#if defined(PIN_USER_JOYSTICK) +static AnalogJoystick::JoyADCMapping joystick_mappings[] = { + { 0, KEY_DOWN }, { 1290, KEY_SELECT }, { 1961, KEY_LEFT }, + { 2668, KEY_RIGHT }, { 3227, KEY_UP }, { 4095, 0 } // IDLE +}; + +AnalogJoystick analog_joystick(PIN_USER_JOYSTICK, joystick_mappings, 6, KEY_SELECT); +#endif +#endif + +bool radio_init() { + +#ifdef PA_FAN_EN + pinMode(PA_FAN_EN, OUTPUT); + digitalWrite(PA_FAN_EN, 1); +#endif + + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +/** + * Linear interpolation helper for integers + */ +int16_t lerp_int(uint8_t x, uint8_t x0, uint8_t x1, int16_t y0, int16_t y1) { + if (x1 == x0) return y0; + return y0 + ((int16_t)(x - x0) * (y1 - y0)) / (x1 - x0); +} + +/** + * Set PA output power with automatic SX1276 and DAC calculation + * + * @param target_output_dbm Desired PA output power in dBm (20-30 dBm for 100-1000mW) + * @param clamp_to_range If true, clamp out-of-range values to min/max instead of failing + * @return true if successful, false if out of range and clamp_to_range is false + */ +bool setPAOutputPower(uint8_t target_output_dbm, bool clamp_to_range = true) { + // Validate and clamp range + if (target_output_dbm < MIN_OUTPUT_DBM) { + if (clamp_to_range) { + MESH_DEBUG_PRINT("Warning: Target %u dBm too low, clamping to min %u dBm\n", target_output_dbm, + MIN_OUTPUT_DBM); + target_output_dbm = MIN_OUTPUT_DBM; + } else { + MESH_DEBUG_PRINT("Error: Target %u dBm below minimum %u dBm\n", target_output_dbm, MIN_OUTPUT_DBM); + return false; + } + } + + if (target_output_dbm > MAX_OUTPUT_DBM) { + if (clamp_to_range) { + MESH_DEBUG_PRINT("Warning: Target %u dBm too high, clamping to max %u dBm\n", target_output_dbm, + MAX_OUTPUT_DBM); + target_output_dbm = MAX_OUTPUT_DBM; + } else { + MESH_DEBUG_PRINT("Error: Target %u dBm above maximum %u dBm\n", target_output_dbm, MAX_OUTPUT_DBM); + return false; + } + } + + // Find the calibration points to interpolate between + int lower_idx = 0; + int upper_idx = NUM_CAL_POINTS - 1; + + for (int i = 0; i < NUM_CAL_POINTS - 1; i++) { + if (target_output_dbm >= calibration[i].output_dbm && + target_output_dbm <= calibration[i + 1].output_dbm) { + lower_idx = i; + upper_idx = i + 1; + break; + } + } + + // Handle exact matches + if (target_output_dbm == calibration[lower_idx].output_dbm) { + int8_t sx1278_power = calibration[lower_idx].sx1278_dbm; + uint8_t dac_value = calibration[lower_idx].dac_value; + + radio.setOutputPower(sx1278_power, true); // RFO output + dacWrite(DAC_PA_PIN, dac_value); + + MESH_DEBUG_PRINT("Set power: %u dBm (SX1278: %d dBm, DAC: %u)\n", target_output_dbm, sx1278_power, + dac_value); + return true; + } + + // Linear interpolation between calibration points + uint8_t lower_out = calibration[lower_idx].output_dbm; + uint8_t upper_out = calibration[upper_idx].output_dbm; + + // Interpolate SX1278 power (maintaining 18dB gain relationship) + int16_t sx1278_power = lerp_int(target_output_dbm, lower_out, upper_out, calibration[lower_idx].sx1278_dbm, + calibration[upper_idx].sx1278_dbm); + + // Interpolate DAC value + int16_t dac_value = lerp_int(target_output_dbm, lower_out, upper_out, calibration[lower_idx].dac_value, + calibration[upper_idx].dac_value); + + // Set the calculated values + radio.setOutputPower((int8_t)sx1278_power, true); // RFO output + dacWrite(DAC_PA_PIN, (uint8_t)dac_value); + + MESH_DEBUG_PRINT("Set power: %u dBm (SX1278: %d dBm, DAC: %u)\n", target_output_dbm, sx1278_power, + dac_value); + + return true; +} + +void radio_set_tx_power(uint8_t dbm) { + setPAOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/radiomaster_900_bandit/target.h b/variants/radiomaster_900_bandit/target.h new file mode 100644 index 000000000..b25c05ad5 --- /dev/null +++ b/variants/radiomaster_900_bandit/target.h @@ -0,0 +1,33 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +// #include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS +#include +#include +#endif + +extern BanditBoard board; + +extern WRAPPER_CLASS radio_driver; +extern SensorManager sensors; +extern AutoDiscoverRTCClock rtc_clock; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#if defined(PIN_USER_JOYSTICK) +extern AnalogJoystick analog_joystick; +#endif +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h b/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h new file mode 100644 index 000000000..8fabcc237 --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#define RADIOMASTER_900_BANDIT_NANO +/* + Pin connections from ESP32-D0WDQ6 to SX1276. +*/ +#define P_LORA_DIO_0 22 +#define P_LORA_DIO_1 21 +#define P_LORA_NSS 4 +#define P_LORA_RESET 5 +#define P_LORA_SCLK 18 +#define P_LORA_MISO 19 +#define P_LORA_MOSI 23 +#define SX176X_TXEN 33 + +/* + I2C SDA and SCL. +*/ +#define PIN_BOARD_SDA 14 +#define PIN_BOARD_SCL 12 + +/* + This unit has a FAN built-in. + FAN is active at 250mW on it's ExpressLRS Firmware. + Always ON +*/ +#define PA_FAN_EN 2 // FAN on GPIO 2 + +/* + This module has Skyworks SKY66122 controlled by dacWrite + power rangeing from 100mW to 1000mW. + + Mapping of PA_LEVEL to Power output: GPIO26/dacWrite + 168 -> 100mW -> 2.11v + 148 -> 250mW -> 1.87v + 128 -> 500mW -> 1.63v + 90 -> 1000mW -> 1.16v +*/ +#define DAC_PA_PIN 26 // GPIO 26 controls the PA gain. + +// Configuration - adjust these for your hardware +#define PA_CONSTANT_GAIN 18 // SKY66122 operates at constant 18dB gain +#define MIN_OUTPUT_DBM 20 // 100mW minimum +#define MAX_OUTPUT_DBM 30 // 1000mW maximum + +// Calibration points from manufacturer +struct PowerCalibration { + uint8_t output_dbm; + int8_t sx1278_dbm; + uint8_t dac_value; +}; + +// Values are from Radiomaster. +const PowerCalibration calibration[] = { + { 20, 2, 168 }, // 100mW + { 24, 6, 148 }, // 250mW + { 27, 9, 128 }, // 500mW + { 30, 12, 90 } // 1000mW +}; + +const int NUM_CAL_POINTS = sizeof(calibration) / sizeof(calibration[0]); + +class BanditNanoBoard : public ESP32Board { +private: +public: + // Return fake battery status, battery/fixed power is not monitored. + uint16_t getBattMilliVolts() override { return 5000; } + + const char *getManufacturerName() const override { return "RadioMaster Bandit Nano"; } +}; diff --git a/variants/radiomaster_900_bandit_nano/platformio.ini b/variants/radiomaster_900_bandit_nano/platformio.ini new file mode 100644 index 000000000..14d191c35 --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/platformio.ini @@ -0,0 +1,99 @@ +[radiomaster_900_bandit_nano] +extends = esp32_base +board = esp32dev +build_flags = + ${esp32_base.build_flags} + -I variants/radiomaster_900_bandit_nano + -D RADIOMASTER_900_BANDIT_NANO + -D SX127X_CURRENT_LIMIT=120 + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -D DISPLAY_CLASS=SSD1306Display +; -D P_LORA_DIO_1=21 +; -D P_LORA_DIO_0=22 +; -D P_LORA_NSS=4 +; -D P_LORA_RESET=5 +; -D P_LORA_SCLK=18 +; -D P_LORA_MISO=19 +; -D P_LORA_MOSI=23 + -D SX176X_TXEN=33 + -D MAX_LORA_TX_POWER=30 + -D LORA_TX_POWER=20 + -D P_LORA_TX_LED=15 + -D PIN_BOARD_SDA=14 + -D PIN_BOARD_SCL=12 + -D PIN_USER_JOYSTICK=39 +; -D PA_FAN_EN=2 +; -D PIN_PA_EN=26 + -D DISPLAY_ROTATION=2 ; Display is flipped + -D ENV_INCLUDE_GPS=0 + +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/radiomaster_900_bandit_nano> + + + + + + + +board_upload.flash_size = 8MB +board_build.partitions = default_8MB.csv + +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:radiomaster_900_bandit_nano_companion_radio_ble] +extends = radiomaster_900_bandit_nano +;board_build.upload.maximum_ram_size=2000000 +build_flags = + ${radiomaster_900_bandit_nano.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=160 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D RADIOLIB_DEBUG_BASIC=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit_nano.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${radiomaster_900_bandit_nano.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:radiomaster_900_bandit_nano_repeater] +extends = radiomaster_900_bandit_nano +build_flags = + ${radiomaster_900_bandit_nano.build_flags} + -D ADVERT_NAME='"Radiomaster Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit_nano.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${radiomaster_900_bandit_nano.lib_deps} + ${esp32_ota.lib_deps} + +[env:radiomaster_900_bandit_nano_room_server] +extends = radiomaster_900_bandit_nano +build_flags = + ${radiomaster_900_bandit_nano.build_flags} + -D ADVERT_NAME='"Radiomaster Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${radiomaster_900_bandit_nano.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${radiomaster_900_bandit_nano.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/radiomaster_900_bandit_nano/target.cpp b/variants/radiomaster_900_bandit_nano/target.cpp new file mode 100644 index 000000000..649059892 --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/target.cpp @@ -0,0 +1,160 @@ +#include "target.h" + +#include +#include + +BanditNanoBoard board; + +#if defined(P_LORA_SCLK) +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); +#else +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifndef PIN_USER_BTN +#define PIN_USER_BTN (-1) +#endif + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +// MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#if defined(PIN_USER_JOYSTICK) +static AnalogJoystick::JoyADCMapping joystick_mappings[] = { + { 0, KEY_DOWN }, { 1290, KEY_SELECT }, { 1961, KEY_LEFT }, + { 2668, KEY_RIGHT }, { 3227, KEY_UP }, { 4095, 0 } // IDLE +}; + +AnalogJoystick analog_joystick(PIN_USER_JOYSTICK, joystick_mappings, 6, KEY_SELECT); +#endif +#endif + +bool radio_init() { + +#ifdef PA_FAN_EN + pinMode(PA_FAN_EN, OUTPUT); + digitalWrite(PA_FAN_EN, 1); +#endif + + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +/** + * Linear interpolation helper for integers + */ +int16_t lerp_int(uint8_t x, uint8_t x0, uint8_t x1, int16_t y0, int16_t y1) { + if (x1 == x0) return y0; + return y0 + ((int16_t)(x - x0) * (y1 - y0)) / (x1 - x0); +} + +/** + * Set PA output power with automatic SX1276 and DAC calculation + * + * @param target_output_dbm Desired PA output power in dBm (20-30 dBm for 100-1000mW) + * @param clamp_to_range If true, clamp out-of-range values to min/max instead of failing + * @return true if successful, false if out of range and clamp_to_range is false + */ +bool setPAOutputPower(uint8_t target_output_dbm, bool clamp_to_range = true) { + // Validate and clamp range + if (target_output_dbm < MIN_OUTPUT_DBM) { + if (clamp_to_range) { + MESH_DEBUG_PRINT("Warning: Target %u dBm too low, clamping to min %u dBm\n", target_output_dbm, + MIN_OUTPUT_DBM); + target_output_dbm = MIN_OUTPUT_DBM; + } else { + MESH_DEBUG_PRINT("Error: Target %u dBm below minimum %u dBm\n", target_output_dbm, MIN_OUTPUT_DBM); + return false; + } + } + + if (target_output_dbm > MAX_OUTPUT_DBM) { + if (clamp_to_range) { + MESH_DEBUG_PRINT("Warning: Target %u dBm too high, clamping to max %u dBm\n", target_output_dbm, + MAX_OUTPUT_DBM); + target_output_dbm = MAX_OUTPUT_DBM; + } else { + MESH_DEBUG_PRINT("Error: Target %u dBm above maximum %u dBm\n", target_output_dbm, MAX_OUTPUT_DBM); + return false; + } + } + + // Find the calibration points to interpolate between + int lower_idx = 0; + int upper_idx = NUM_CAL_POINTS - 1; + + for (int i = 0; i < NUM_CAL_POINTS - 1; i++) { + if (target_output_dbm >= calibration[i].output_dbm && + target_output_dbm <= calibration[i + 1].output_dbm) { + lower_idx = i; + upper_idx = i + 1; + break; + } + } + + // Handle exact matches + if (target_output_dbm == calibration[lower_idx].output_dbm) { + int8_t sx1278_power = calibration[lower_idx].sx1278_dbm; + uint8_t dac_value = calibration[lower_idx].dac_value; + + radio.setOutputPower(sx1278_power, true); // RFO output + dacWrite(DAC_PA_PIN, dac_value); + + MESH_DEBUG_PRINT("Set power: %u dBm (SX1278: %d dBm, DAC: %u)\n", target_output_dbm, sx1278_power, + dac_value); + return true; + } + + // Linear interpolation between calibration points + uint8_t lower_out = calibration[lower_idx].output_dbm; + uint8_t upper_out = calibration[upper_idx].output_dbm; + + // Interpolate SX1278 power (maintaining 18dB gain relationship) + int16_t sx1278_power = lerp_int(target_output_dbm, lower_out, upper_out, calibration[lower_idx].sx1278_dbm, + calibration[upper_idx].sx1278_dbm); + + // Interpolate DAC value + int16_t dac_value = lerp_int(target_output_dbm, lower_out, upper_out, calibration[lower_idx].dac_value, + calibration[upper_idx].dac_value); + + // Set the calculated values + radio.setOutputPower((int8_t)sx1278_power, true); // RFO output + dacWrite(DAC_PA_PIN, (uint8_t)dac_value); + + MESH_DEBUG_PRINT("Set power: %u dBm (SX1278: %d dBm, DAC: %u)\n", target_output_dbm, sx1278_power, + dac_value); + + return true; +} + +void radio_set_tx_power(uint8_t dbm) { + setPAOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/radiomaster_900_bandit_nano/target.h b/variants/radiomaster_900_bandit_nano/target.h new file mode 100644 index 000000000..51042f4ef --- /dev/null +++ b/variants/radiomaster_900_bandit_nano/target.h @@ -0,0 +1,35 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +// #include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS +#include +// #include +#include +#endif + +extern BanditNanoBoard board; + +extern WRAPPER_CLASS radio_driver; +extern SensorManager sensors; +extern AutoDiscoverRTCClock rtc_clock; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +// extern MomentaryButton user_btn; +#if defined(PIN_USER_JOYSTICK) +extern AnalogJoystick analog_joystick; +#endif +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 88337fc05850cf8bfa279c98ae2fa3ec31e45e05 Mon Sep 17 00:00:00 2001 From: gjelsoe <36234524+gjelsoe@users.noreply.github.com> Date: Sat, 14 Feb 2026 12:19:32 +0100 Subject: [PATCH 2/2] Added Wakeup If put in Hibernation it would not wakeup --- variants/radiomaster_900_bandit/BanditBoard.h | 12 ++++++++++++ .../radiomaster_900_bandit_nano/BanditNanoBoard.h | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/variants/radiomaster_900_bandit/BanditBoard.h b/variants/radiomaster_900_bandit/BanditBoard.h index ada41d2fc..b4de06045 100644 --- a/variants/radiomaster_900_bandit/BanditBoard.h +++ b/variants/radiomaster_900_bandit/BanditBoard.h @@ -110,6 +110,18 @@ class BanditBoard : public ESP32Board { const char *getManufacturerName() const override { return "RadioMaster Bandit"; } + // Add wake-enabled power off + void powerOff() override { +#if defined(PIN_USER_BTN) + esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_USER_BTN, LOW); // Wake when button pressed (LOW) +#elif defined(PIN_USER_JOYSTICK) + // For analog joystick, you'd need to use the center button GPIO + esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_USER_JOYSTICK, LOW); +#endif + // Enter deep sleep + esp_deep_sleep_start(); + } + void onBeforeTransmit() override { // Use user-defined TX LED color for (byte i = 2; i <= 6; i++) { diff --git a/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h b/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h index 8fabcc237..eea09de8b 100644 --- a/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h +++ b/variants/radiomaster_900_bandit_nano/BanditNanoBoard.h @@ -67,6 +67,18 @@ const int NUM_CAL_POINTS = sizeof(calibration) / sizeof(calibration[0]); class BanditNanoBoard : public ESP32Board { private: public: + // Add wake-enabled power off + void powerOff() override { +#if defined(PIN_USER_BTN) + esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_USER_BTN, LOW); // Wake when button pressed (LOW) +#elif defined(PIN_USER_JOYSTICK) + // For analog joystick, you'd need to use the center button GPIO + esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_USER_JOYSTICK, LOW); +#endif + // Enter deep sleep + esp_deep_sleep_start(); + } + // Return fake battery status, battery/fixed power is not monitored. uint16_t getBattMilliVolts() override { return 5000; }