Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 120 additions & 1 deletion examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -778,6 +882,10 @@ void UITask::loop() {

userLedHandler();

#ifdef RADIOMASTER_900_BANDIT
neopixelMsgHandler();
#endif

#ifdef PIN_BUZZER
if (buzzer.isPlaying()) buzzer.loop();
#endif
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion examples/companion_radio/ui-new/UITask.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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

Expand All @@ -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);
Expand Down
106 changes: 106 additions & 0 deletions src/helpers/ui/AnalogJoystick.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 37 additions & 0 deletions src/helpers/ui/AnalogJoystick.h
Original file line number Diff line number Diff line change
@@ -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; }
};
Loading