mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-20 22:13:47 +00:00
Merge 194d25ddfb into dee3e26ac0
This commit is contained in:
commit
c71331d885
18 changed files with 2016 additions and 2 deletions
37
boards/radiomaster_bandit.json
Normal file
37
boards/radiomaster_bandit.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32_out.ld",
|
||||
"partitions": "default_8MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DARDUINO_RADIOMASTER_BANDIT -DARDUINO_ARCH_ESP32",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "esp32"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"bluetooth",
|
||||
"ethernet"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_board": "esp-wroom-32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Radiomaster Bandit",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://www.espressif.com/en/products/socs/esp32",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -557,6 +577,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;
|
||||
|
||||
|
|
@ -662,6 +685,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 < NEOPIXEL_NUM; 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 < NEOPIXEL_NUM; i++) {
|
||||
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
|
||||
}
|
||||
pixels.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void UITask::setCurrScreen(UIScreen* c) {
|
||||
curr = c;
|
||||
_next_refresh = 100;
|
||||
|
|
@ -697,6 +772,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
|
||||
|
|
@ -754,6 +831,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);
|
||||
|
|
@ -774,6 +878,10 @@ void UITask::loop() {
|
|||
|
||||
userLedHandler();
|
||||
|
||||
#ifdef RADIOMASTER_900_BANDIT
|
||||
neopixelMsgHandler();
|
||||
#endif
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
if (buzzer.isPlaying()) buzzer.loop();
|
||||
#endif
|
||||
|
|
@ -802,6 +910,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
|
||||
}
|
||||
|
|
@ -841,6 +954,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,14 @@ class UITask : public AbstractUITask {
|
|||
int last_led_increment = 0;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_USER_BTN_ANA
|
||||
#ifdef RADIOMASTER_900_BANDIT
|
||||
// NeoPixel message notification support
|
||||
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 +67,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;
|
||||
};
|
||||
137
variants/radiomaster_900_bandit/BanditBoard.h
Normal file
137
variants/radiomaster_900_bandit/BanditBoard.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#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
|
||||
};
|
||||
|
||||
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"; }
|
||||
|
||||
// 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 < NEOPIXEL_NUM; 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 < NEOPIXEL_NUM; 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
|
||||
```
|
||||
96
variants/radiomaster_900_bandit/platformio.ini
Normal file
96
variants/radiomaster_900_bandit/platformio.ini
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
[radiomaster_900_bandit]
|
||||
extends = esp32_base
|
||||
board = radiomaster_bandit
|
||||
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>
|
||||
|
||||
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
|
||||
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}
|
||||
163
variants/radiomaster_900_bandit/target.cpp
Normal file
163
variants/radiomaster_900_bandit/target.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ui/UIScreen.h>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
Adafruit_NeoPixel pixels(NEOPIXEL_NUM, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
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();
|
||||
86
variants/radiomaster_900_bandit_nano/BanditNanoBoard.h
Normal file
86
variants/radiomaster_900_bandit_nano/BanditNanoBoard.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#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:
|
||||
// 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; }
|
||||
|
||||
const char *getManufacturerName() const override { return "RadioMaster Bandit Nano"; }
|
||||
};
|
||||
101
variants/radiomaster_900_bandit_nano/platformio.ini
Normal file
101
variants/radiomaster_900_bandit_nano/platformio.ini
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
[radiomaster_900_bandit_nano]
|
||||
extends = esp32_base
|
||||
board = radiomaster_bandit
|
||||
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>
|
||||
|
||||
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
|
||||
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