Skip to content
Open
408 changes: 266 additions & 142 deletions examples/companion_radio/ui-new/UITask.cpp

Large diffs are not rendered by default.

62 changes: 39 additions & 23 deletions examples/companion_radio/ui-new/UITask.h
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
#pragma once

#include <Arduino.h>
#include <MeshCore.h>
#include <helpers/ui/DisplayDriver.h>
#include <helpers/ui/UIScreen.h>
#include <helpers/SensorManager.h>
#include <helpers/BaseSerialInterface.h>
#include <Arduino.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/LPPDataHelpers.h>
#include <helpers/ui/DisplayDriver.h>
#include <helpers/ui/UIScreen.h>

#ifndef LED_STATE_ON
#define LED_STATE_ON 1
#endif

#ifdef PIN_BUZZER
#include <helpers/ui/buzzer.h>
#include <helpers/ui/buzzer.h>
#endif
#ifdef PIN_VIBRATION
#include <helpers/ui/GenericVibration.h>
#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"

class UITask : public AbstractUITask {
DisplayDriver* _display;
SensorManager* _sensors;
DisplayDriver *_display;
SensorManager *_sensors;
#ifdef PIN_BUZZER
genericBuzzer buzzer;
#endif
#ifdef PIN_VIBRATION
GenericVibration vibration;
#endif
unsigned long _next_refresh, _auto_off;
NodePrefs* _node_prefs;
NodePrefs *_node_prefs;
char _alert[80];
unsigned long _alert_expiry;
int _msgcount;
Expand All @@ -44,50 +50,60 @@ 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

UIScreen* splash;
UIScreen* home;
UIScreen* msg_preview;
UIScreen* curr;
UIScreen *splash;
UIScreen *home;
UIScreen *msg_preview;
UIScreen *curr;

void userLedHandler();
#ifdef RADIOMASTER_900_BANDIT
void neopixelMsgHandler();
#endif

// Button action handlers
char checkDisplayOn(char c);
char handleLongPress(char c);
char handleDoubleClick(char c);
char handleTripleClick(char c);

void setCurrScreen(UIScreen* c);
void setCurrScreen(UIScreen *c);

public:

UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) {
UITask(mesh::MainBoard *board, BaseSerialInterface *serial)
: AbstractUITask(board, serial), _display(NULL), _sensors(NULL) {
next_batt_chck = _next_refresh = 0;
ui_started_at = 0;
curr = NULL;
}
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs);
void begin(DisplayDriver *display, SensorManager *sensors, NodePrefs *node_prefs);

void gotoHomeScreen() { setCurrScreen(home); }
void showAlert(const char* text, int duration_millis);
int getMsgCount() const { return _msgcount; }
void showAlert(const char *text, int duration_millis);
int getMsgCount() const { return _msgcount; }
bool hasDisplay() const { return _display != NULL; }
bool isButtonPressed() const;

void toggleBuzzer();
bool getGPSState();
void toggleGPS();


// from AbstractUITask
void msgRead(int msgcount) override;
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
void newMsg(uint8_t path_len, const char *from_name, const char *text, int msgcount) override;
void notify(UIEventType t = UIEventType::none) override;
void loop() override;

void shutdown(bool restart = false);
};
};
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; }
};
92 changes: 92 additions & 0 deletions src/helpers/ui/SH1115Display.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
Loading