mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-20 22:13:47 +00:00
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.
This commit is contained in:
parent
e33d93dc7f
commit
6c0da535b8
17 changed files with 1951 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@
|
|||
#include <helpers/ui/GenericVibration.h>
|
||||
#endif
|
||||
|
||||
// Add NeoPixel support for Bandit Board
|
||||
#ifdef RADIOMASTER_900_BANDIT
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
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);
|
||||
|
|
|
|||
106
src/helpers/ui/AnalogJoystick.cpp
Normal file
106
src/helpers/ui/AnalogJoystick.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
37
src/helpers/ui/AnalogJoystick.h
Normal file
37
src/helpers/ui/AnalogJoystick.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
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; }
|
||||
};
|
||||
92
src/helpers/ui/SH1115Display.cpp
Normal file
92
src/helpers/ui/SH1115Display.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#include "SH1115Display.h"
|
||||
#include <Adafruit_GrayOLED.h>
|
||||
//#include "Adafruit_SH110X.h"
|
||||
#include <Adafruit_SH1115.h>
|
||||
|
||||
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();
|
||||
}
|
||||
44
src/helpers/ui/SH1115Display.h
Normal file
44
src/helpers/ui/SH1115Display.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Wire.h>
|
||||
#define SH110X_NO_SPLASH
|
||||
// #include <Adafruit_SH110X.h>
|
||||
#include <Adafruit_SH1115.h>
|
||||
|
||||
#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;
|
||||
};
|
||||
127
variants/radiomaster_900_bandit/BanditBoard.h
Normal file
127
variants/radiomaster_900_bandit/BanditBoard.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#pragma once
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/ui/AnalogJoystick.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
};
|
||||
276
variants/radiomaster_900_bandit/COLOR_EXAMPLES.md
Normal file
276
variants/radiomaster_900_bandit/COLOR_EXAMPLES.md
Normal file
|
|
@ -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!
|
||||
286
variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md
Normal file
286
variants/radiomaster_900_bandit/LED_CONFIGURATION_GUIDE.md
Normal file
|
|
@ -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
|
||||
```
|
||||
190
variants/radiomaster_900_bandit/QUICK_REFERENCE.md
Normal file
190
variants/radiomaster_900_bandit/QUICK_REFERENCE.md
Normal file
|
|
@ -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
|
||||
```
|
||||
94
variants/radiomaster_900_bandit/platformio.ini
Normal file
94
variants/radiomaster_900_bandit/platformio.ini
Normal file
|
|
@ -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>
|
||||
+<helpers/ui/AnalogJoystick.cpp>
|
||||
+<helpers/ui/SH1115Display.cpp>
|
||||
+<helpers/sensors>
|
||||
|
||||
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}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
160
variants/radiomaster_900_bandit/target.cpp
Normal file
160
variants/radiomaster_900_bandit/target.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ui/UIScreen.h>
|
||||
|
||||
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
|
||||
}
|
||||
33
variants/radiomaster_900_bandit/target.h
Normal file
33
variants/radiomaster_900_bandit/target.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
// #include <RadioLib.h>
|
||||
#include <BanditBoard.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/AnalogJoystick.h>
|
||||
#include <helpers/ui/SH1115Display.h>
|
||||
#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();
|
||||
74
variants/radiomaster_900_bandit_nano/BanditNanoBoard.h
Normal file
74
variants/radiomaster_900_bandit_nano/BanditNanoBoard.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/ui/AnalogJoystick.h>
|
||||
|
||||
#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"; }
|
||||
};
|
||||
99
variants/radiomaster_900_bandit_nano/platformio.ini
Normal file
99
variants/radiomaster_900_bandit_nano/platformio.ini
Normal file
|
|
@ -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>
|
||||
+<helpers/ui/AnalogJoystick.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/sensors>
|
||||
|
||||
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}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
160
variants/radiomaster_900_bandit_nano/target.cpp
Normal file
160
variants/radiomaster_900_bandit_nano/target.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ui/UIScreen.h>
|
||||
|
||||
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
|
||||
}
|
||||
35
variants/radiomaster_900_bandit_nano/target.h
Normal file
35
variants/radiomaster_900_bandit_nano/target.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
// #include <RadioLib.h>
|
||||
#include <BanditNanoBoard.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/AnalogJoystick.h>
|
||||
// #include <helpers/ui/MomentaryButton.h>
|
||||
#include <helpers/ui/SSD1306Display.h>
|
||||
#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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue