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
82 changes: 60 additions & 22 deletions examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,50 @@ class HomeScreen : public UIScreen {


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;

// battery outline
display.drawRect(iconX, iconY, iconWidth, iconHeight);
String batteryPercText = String(batteryPercentage) + "%";
int battTextStartPosX = display.width() - 5;

display.setColor(DisplayDriver::DARK);
display.fillRect(battBackStartPosX, battBackStartPosY, battBackWidth, battBackHeight);
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.drawTextRightAlign(battTextStartPosX, 1, batteryPercText.c_str());

#else
// ===== ICON BATTERY =====
int iconWidth = 24;
int iconHeight = 10;
int iconX = display.width() - iconWidth - 5;
int iconY = 0;

display.setColor(DisplayDriver::GREEN);

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

// fill the battery based on the percentage
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
// 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 +563,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 @@ -739,6 +763,18 @@ 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){
c = checkDisplayOn(KEY_SELECT);
} else if (enc_ev == ENC_EVENT_LONG_PRESS){
c = handleLongPress(KEY_ENTER);
}
#endif
#if defined(PIN_USER_BTN_ANA)
if (abs(millis() - _analogue_pin_read_millis) > 10) {
ev = analog_btn.check();
Expand Down Expand Up @@ -892,13 +928,14 @@ void UITask::toggleGPS() {
_sensors->setSettingValue("gps", "0");
_node_prefs->gps_enabled = 0;
notify(UIEventType::ack);
showAlert("GPS: Disabled", 800);
} else {
_sensors->setSettingValue("gps", "1");
_node_prefs->gps_enabled = 1;
notify(UIEventType::ack);
showAlert("GPS: Enabled", 800);
}
the_mesh.savePrefs();
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
_next_refresh = 0;
break;
}
Expand All @@ -912,12 +949,13 @@ void UITask::toggleBuzzer() {
if (buzzer.isQuiet()) {
buzzer.quiet(false);
notify(UIEventType::ack);
showAlert("Buzzer: ON", 800);
} else {
buzzer.quiet(true);
showAlert("Buzzer: OFF", 800);
}
_node_prefs->buzzer_quiet = buzzer.isQuiet();
the_mesh.savePrefs();
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
_next_refresh = 0; // trigger refresh
#endif
}
}
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();
};
9 changes: 7 additions & 2 deletions variants/promicro/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,17 @@ build_flags = ${Promicro.build_flags}
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=SSD1306Display
; -D MESH_PACKET_LOGGING=1
-D MESH_DEBUG=1
; -D HAS_ENCODER
; -D PIN_ENC_A=0
; -D PIN_ENC_B=1
; -D PIN_ENCODER_BTN=5
;-D MESH_PACKET_LOGGING=1
;-D MESH_DEBUG=1
build_src_filter = ${Promicro.build_src_filter}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/ui/EncoderAndButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps = ${Promicro.lib_deps}
Expand Down
5 changes: 5 additions & 0 deletions variants/promicro/target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock);
#ifdef DISPLAY_CLASS
DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
#define UI_HAS_DISPLAY 1
#endif

#if defined(UI_HAS_DISPLAY) && defined(HAS_ENCODER)
EncoderAndButton encoder(PIN_ENC_A, PIN_ENC_B, PIN_ENCODER_BTN, 1200);
#endif

bool radio_init() {
Expand Down
6 changes: 6 additions & 0 deletions variants/promicro/target.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
#include <helpers/ui/MomentaryButton.h>
#define UI_HAS_DISPLAY 1
#endif

#if defined(UI_HAS_DISPLAY) && defined(HAS_ENCODER)
#include <helpers/ui/EncoderAndButton.h>
extern EncoderAndButton encoder;
#endif

#include <helpers/sensors/EnvironmentSensorManager.h>
Expand Down