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
172 changes: 145 additions & 27 deletions examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ class SplashScreen : public UIScreen {
display.drawTextCentered(display.width()/2, 22, _version_info);

display.setTextSize(1);
#ifdef OLED_RU
char filtered_date[sizeof(FIRMWARE_BUILD_DATE)];
display.translateUTF8ToBlocks(filtered_date, FIRMWARE_BUILD_DATE, sizeof(filtered_date));
display.drawTextCentered(display.width()/2, 42, filtered_date);
#else
display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE);
#endif

return 1000;
}
Expand Down Expand Up @@ -100,31 +106,51 @@ class HomeScreen : public UIScreen {
bool _shutdown_init;
AdvertPath recent[UI_RECENT_LIST_SIZE];


void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
// Convert millivolts to percentage
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V)
int minMilliVolts = 3000;
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
minMilliVolts = AUTO_SHUTDOWN_MILLIVOLTS;
#endif

const int maxMilliVolts = 4200;
int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts);
if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0%
if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100%

// battery icon
int iconWidth = 24;
int iconHeight = 10;
int iconX = display.width() - iconWidth - 5; // Position the icon near the top-right corner
int iconY = 0;
display.setColor(DisplayDriver::GREEN);
batteryPercentage = constrain(batteryPercentage, 0, 100);

#ifdef TEXT_BATTERY
// ===== TEXT BATTERY =====
int battBackWidth = 24;
int battBackHeight = 10;
int battBackStartPosY = 0;
int battBackStartPosX = display.width() - battBackWidth - 5;

String batteryPercText = String(batteryPercentage) + "%";
int battTextStartPosX = display.width() - 5;

// battery outline
display.drawRect(iconX, iconY, iconWidth, iconHeight);
display.setColor(DisplayDriver::DARK);
display.fillRect(battBackStartPosX, battBackStartPosY, battBackWidth, battBackHeight);
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.drawTextRightAlign(battTextStartPosX, 1, batteryPercText.c_str());

// battery "cap"
display.fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);
#else
// ===== ICON BATTERY =====
int iconWidth = 24;
int iconHeight = 10;
int iconX = display.width() - iconWidth - 5;
int iconY = 0;

display.setColor(DisplayDriver::GREEN);

// fill the battery based on the percentage
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
// outline
display.drawRect(iconX, iconY, iconWidth, iconHeight);

// cap
display.fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);

// fill
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
#endif
}

CayenneLPP sensors_lpp;
Expand Down Expand Up @@ -542,6 +568,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#if defined(PIN_USER_BTN)
user_btn.begin();
#endif
#if defined(HAS_ENCODER)
encoder.begin();
#endif
#if defined(PIN_USER_BTN_ANA)
analog_btn.begin();
#endif
Expand Down Expand Up @@ -631,14 +660,24 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
setCurrScreen(msg_preview);

if (_display != NULL) {
if (!_display->isOn() && !hasConnection()) {
_display->turnOn();
}
if (_display->isOn()) {
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
_next_refresh = 100; // trigger refresh

#ifdef DISPLAY_TOGGLE
if (_displayWakeOnMsg) {
#endif

if (!_display->isOn() && !hasConnection()) {
_display->turnOn();
}

#ifdef DISPLAY_TOGGLE
}
#endif

if (_display->isOn()) {
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 100;
}
}
}
}

void UITask::userLedHandler() {
Expand Down Expand Up @@ -739,6 +778,21 @@ void UITask::loop() {
c = handleTripleClick(KEY_SELECT);
}
#endif
#if defined(HAS_ENCODER)
int enc_ev = encoder.check();
if (enc_ev == ENC_EVENT_CW) {
c = checkDisplayOn(KEY_RIGHT);
} else if (enc_ev == ENC_EVENT_CCW) {
c = checkDisplayOn(KEY_LEFT);
} else if (enc_ev == ENC_EVENT_BUTTON) {
toggleDisplayWakeupOnMsg();
c = 0;
} else if (enc_ev == ENC_EVENT_LONG_PRESS) {
turnOnDisplayWakeupOnMsg();
c = 0;
}
#endif

#if defined(PIN_USER_BTN_ANA)
if (abs(millis() - _analogue_pin_read_millis) > 10) {
ev = analog_btn.check();
Expand Down Expand Up @@ -766,6 +820,33 @@ void UITask::loop() {
}
#endif

#if defined(DISPLAY_TOGGLE)
bool disp_state = (digitalRead(DISPLAY_TOGGLE) == LOW); // ACTIVE LOW

// edge: press
if (disp_state && !_dispTglPrevState) {
_dispTglPressStart = millis();
_dispTglLongHandled = false;
}

// hold → long press
if (disp_state && !_dispTglLongHandled) {
if (millis() - _dispTglPressStart >= LONG_PRESS_MILLIS) {
turnOnDisplayWakeupOnMsg();
_dispTglLongHandled = true;
}
}

// release → click
if (!disp_state && _dispTglPrevState) {
if (!_dispTglLongHandled) {
toggleDisplayWakeupOnMsg();
}
}

_dispTglPrevState = disp_state;
#endif

if (c != 0 && curr) {
curr->handleInput(c);
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
Expand Down Expand Up @@ -921,3 +1002,40 @@ void UITask::toggleBuzzer() {
_next_refresh = 0; // trigger refresh
#endif
}

void UITask::toggleDisplayWakeupOnMsg() {
if (_display && !_display->isOn()) {
// Display is turned off, only wakeup on the first press
_display->turnOn();
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 0;
return;
}

// Display is on wake, toggle the flag
_displayWakeOnMsg = !_displayWakeOnMsg;

showAlert(
_displayWakeOnMsg ? "Msg wake: ON" : "Msg wake: OFF",
800
);
notify(UIEventType::ack);

_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 0;
}

void UITask::turnOnDisplayWakeupOnMsg() {
// Принудительно включаем wake-on-msg
_displayWakeOnMsg = true;

if (_display && !_display->isOn()) {
_display->turnOn();
}

showAlert("Msg wake: ON", 800);
notify(UIEventType::ack);

_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 0;
}
11 changes: 11 additions & 0 deletions examples/companion_radio/ui-new/UITask.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ class UITask : public AbstractUITask {
unsigned long _analogue_pin_read_millis = millis();
#endif

bool _displayWakeOnMsg = true;

#if defined(DISPLAY_TOGGLE)
bool _dispTglPrevState = false;
uint32_t _dispTglPressStart = 0;
bool _dispTglLongHandled = false;
#endif


UIScreen* splash;
UIScreen* home;
UIScreen* msg_preview;
Expand All @@ -56,6 +65,8 @@ class UITask : public AbstractUITask {
void userLedHandler();

// Button action handlers
void turnOnDisplayWakeupOnMsg();
void toggleDisplayWakeupOnMsg();
char checkDisplayOn(char c);
char handleLongPress(char c);
char handleDoubleClick(char c);
Expand Down
99 changes: 99 additions & 0 deletions src/helpers/ui/EncoderAndButton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "EncoderAndButton.h"

#define ENC_DEBOUNCE_US 800
#define BTN_DEBOUNCE_MS 25

// Valid quadrature transitions table
static const int8_t enc_table[16] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0
};

EncoderAndButton::EncoderAndButton(
int8_t pinA,
int8_t pinB,
int8_t btnPin,
uint16_t longPressMs,
bool pullups
) :
_pinA(pinA),
_pinB(pinB),
_btnPin(btnPin),
_longPressMs(longPressMs)
{
_state = 0;
_delta = 0;
_btnState = false;
_btnLast = false;
_lastEncTime = 0;
_btnDownAt = 0;
_lastBtnChange = 0;
}

void EncoderAndButton::begin() {
pinMode(_pinA, INPUT_PULLUP);
pinMode(_pinB, INPUT_PULLUP);
pinMode(_btnPin, INPUT_PULLUP);

_state = (digitalRead(_pinA) << 1) | digitalRead(_pinB);
}

bool EncoderAndButton::buttonPressed() const {
return !_btnState;
}

void EncoderAndButton::readEncoder() {
unsigned long now = micros();
if (now - _lastEncTime < ENC_DEBOUNCE_US) return;

_lastEncTime = now;

_state = ((_state << 2) |
(digitalRead(_pinA) << 1) |
digitalRead(_pinB)) & 0x0F;

_delta += enc_table[_state];
}

int EncoderAndButton::check() {
int event = ENC_EVENT_NONE;

// --- Encoder ---
readEncoder();
if (_delta >= 4) {
_delta = 0;
event = ENC_EVENT_CW;
} else if (_delta <= -4) {
_delta = 0;
event = ENC_EVENT_CCW;
}

// --- Button ---
bool raw = digitalRead(_btnPin);
unsigned long now = millis();

if (raw != _btnLast && (now - _lastBtnChange) > BTN_DEBOUNCE_MS) {
_lastBtnChange = now;
_btnLast = raw;

if (!raw) {
_btnDownAt = now;
} else {
if (_btnDownAt &&
(now - _btnDownAt) < _longPressMs) {
event = ENC_EVENT_BUTTON;
}
_btnDownAt = 0;
}
}

if (_btnDownAt &&
(now - _btnDownAt) >= _longPressMs) {
event = ENC_EVENT_LONG_PRESS;
_btnDownAt = 0;
}

return event;
}
40 changes: 40 additions & 0 deletions src/helpers/ui/EncoderAndButton.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once
#include <Arduino.h>

#define ENC_EVENT_NONE 0
#define ENC_EVENT_CW 1
#define ENC_EVENT_CCW 2
#define ENC_EVENT_BUTTON 3
#define ENC_EVENT_LONG_PRESS 4

class EncoderAndButton {
public:
EncoderAndButton(
int8_t pinA,
int8_t pinB,
int8_t btnPin,
uint16_t longPressMs = 1000,
bool pullups = true
);

void begin();
int check(); // returns ENC_EVENT_*
bool buttonPressed() const;

private:
// encoder
int8_t _pinA, _pinB;
uint8_t _state;
int8_t _delta;
unsigned long _lastEncTime;

// button
int8_t _btnPin;
bool _btnState;
bool _btnLast;
unsigned long _btnDownAt;
uint16_t _longPressMs;
unsigned long _lastBtnChange;

void readEncoder();
};
Loading